HashSet源码解析
2016-07-22 13:27
267 查看
Set接口
在讲HashSet之前先说一下Set接口。见源码:
package java.util; //这里我截取部分源码中的注释 /** * A collection that contains no duplicate elements. More formally, sets * contain no pair of elements <code>e1</code> and <code>e2</code> such that * <code>e1.equals(e2)</code>, and at most one null element. As implied by * its name, this interface models the mathematical <i>set</i> abstraction. */ //set是一个不包含重复元素的集合。更正式一点来说,set不能包含这样的一对元素:通过equals方法比较返回true。并且至多有一个null元素。 /** * @author Josh Bloch(集合框架的作者,牛人) * @author Neal Gafter * @see Collection * @see List * @see SortedSet * @see HashSet * @see TreeSet * @see AbstractSet * @see Collections#singleton(java.lang.Object) * @see Collections#EMPTY_SET * @since 1.2 */ public interface Set<E> extends Collection<E> { // Query Operations int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); // Modification Operations boolean add(E e); boolean remove(Object o); // Bulk Operations boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean retainAll(Collection<?> c); boolean removeAll(Collection<?> c); void clear(); // Comparison and hashing boolean equals(Object o);//这个方法在Set中要特别注意! int hashCode();//这个方法在Set中要特别注意! //1.8之后的新特性 @Override default Spliterator<E> spliterator() { return Spliterators.spliterator(this, Spliterator.DISTINCT); } }
因为在AbstractSet中已经实现了大部分方法,因此,在HashSet中我们会感觉代码少很多。下面是HashSet的源码。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } public Iterator<E> iterator() { return map.keySet().iterator(); } public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Object o) { return map.containsKey(o); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; } public void clear() { map.clear(); } @SuppressWarnings("unchecked") public Object clone() { try { HashSet<E> newSet = (HashSet<E>) super.clone(); newSet.map = (HashMap<E, Object>) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(e); } } private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden serialization magic s.defaultWriteObject(); s.writeInt(map.capacity()); s.writeFloat(map.loadFactor()); s.writeInt(map.size()); for (E e : map.keySet()) s.writeObject(e); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); int capacity = s.readInt(); if (capacity < 0) { throw new InvalidObjectException("Illegal capacity: " + capacity); } float loadFactor = s.readFloat(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new InvalidObjectException("Illegal load factor: " + loadFactor); } int size = s.readInt(); if (size < 0) { throw new InvalidObjectException("Illegal size: " + size); } capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), HashMap.MAXIMUM_CAPACITY); map = (((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor)); for (int i=0; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); } } public Spliterator<E> spliterator() { return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0); } }
我们可以看到,HashSet是在HashMap的基础上,利用HashMap的key不能“相同”的特性实现的。其对应的HashMap的value值被“PRESENT”对象填充。
由于HashSet的特性是内部的元素不同,因此我们在这里再讲一下HashMap中key的特性:
这里我们先来看一下HashMap中的添加方法:
/** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))//这里是保持Set特性关键,因此equals()方法的实现是很重要的。 e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
从上面我们可以看到,Set保持“无重复”的特性,需要依赖equals()方法比较的结果。
这里我来简要说一下HashMap中寻找value值的流程,首先根据传进来的key值,计算出其hash散列值,然后通过hash值找到位置,在存储位置上再进行equals()的比较最后得到想寻找的值。从这个过程我们可以看到,hashcode()方法和equals()方法之间的相关联系,我们必须维持。
在JAVA编程中,有三个约定,这三个约定是使用集合框架的基础。
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- 浅析Ruby的源代码布局及其编程风格
- asp.net 抓取网页源码三种实现方法
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- 深入浅析knockout源码分析之订阅
- jQuery源码分析之jQuery中的循环技巧详解
- 本人自用的global.js库源码分享
- java中原码、反码与补码的问题分析
- ASP.NET使用HttpWebRequest读取远程网页源代码
- PHP网页游戏学习之Xnova(ogame)源码解读(六)
- C#获取网页HTML源码实例
- PHP网页游戏学习之Xnova(ogame)源码解读(八)
- PHP网页游戏学习之Xnova(ogame)源码解读(四)
- 深入理解PHP之源码目录结构与功能说明
- JS小游戏之极速快跑源码详解
- JS小游戏之象棋暗棋源码详解
- android源码探索之定制android关机界面的方法
- 基于Android设计模式之--SDK源码之策略模式的详解
- Android游戏源码分享之2048