首先来看一下实例化一个HashSet时候,那么底层做了那些事情呢?
/** * Constructs a new, empty set; the backing HashMap instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap(); }
原来Set的底层实现是一个HashMap,接下来看一下add方法的实现
public boolean add(E e) { return map.put(e, PRESENT)==null; }
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();实际上,调用的是map的put方法,这时我们发现我们添加的元素作为map的key,而value则是一个静态的Object类型的常量,而这个常量我们用不到
好了,那么接下来我们有必要研究一下HashMap的底层实现
/** * Constructs an empty HashMap with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
我们发现原来HashMap的底层实际上是一个Entry类型的数组,我们向HashMap中所放置的对象实际上是存储在该数组当中
当向map中添加数据时
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entrye = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
void addEntry(int hash, K key, V value, int bucketIndex) { Entrye = table[bucketIndex]; table[bucketIndex] = new Entry (hash, key, value, e); if (size++ >= threshold) resize(2 * table.length);}
当向HashMap中put一对键值时,它会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(Entry类有一个Entry类型的next成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用equals方法进行比较,如果对此链上的某个对象的equals方法比较为false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象链接到此对象的后面。原理图如下: