今天来分析一下JDK中的HashMap类的源代码.
HashMap不同于list、set,它又称为关联数组,它会同时存储一对值,让其之间形成一种关联.
?
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
?
?可以看到HashMap实现了Map接口,Map是集合类中的另外一个接口,它规定了所有map的实现类只能存储键值对
?
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR;//初始化装载因子 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//初始化容量 table = new Entry[DEFAULT_INITIAL_CAPACITY];//初始化entry数组,用来存放数据 init();//调用init方法,该方法暂未实现 }
?
?默认的构造方法会使用默认值来初始化装载因子、容量、以及底层的entry数组
?
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); //capacity必须是小于初始化容量的最大2的冥值 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); }
?
?该构造方法传入了两个参数,分别是初始化容量、装载因子,其中初始化容量必须是2的冥值
?
下面分析一下put方法:
?
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 (Entry<K,V> e = 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; }
?
?首先判断一下存放入的key是否为空,若为空,则调用putForNullKey方法:
?
private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) {// 循环遍历entry链 if (e.key == null) {//如果key为空,则用带待加的value替换原来的value V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; }
?该方法是一个私有方法,首先取出数组中第0个位置上的entry,而此时可以看到循环条件中有一个e = e.next的条件,明明是数组,那怎么会出现这样的条件呢?其实除了用entry数组存放数据外,在每个数组元素中,都有
一个指向下一个entry的引用.当key相同时.用新的value替代原来的value,最后构造一个entry添加进去entry链中,新添加的entry在整个链的末尾,他的下一个引用指向第0个索引上的entry.
接着分析put方法,上面是key为null时的处理,若key不为空,就根据key的hashcode取得一个hash值,然后计算出该hashcode对应的数组下标,若该下标对应的位置没有数据,则直接构造entry(通过addEntry方法)加入,否则 遍历entry链,
如果正在遍历的entry的key的hash值与待添加的key的hash值相等并且key值相等,则进行value替换.
?
?
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); }
?
?remove方法用了删除entry,底层调用了removeEntryForKey方法:
?
final Entry<K,V> removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode());//根据key得到hash值 int i = indexFor(hash, table.length);//根据hash值得到索引下标 Entry<K,V> prev = table[i];//prev代表的i个元素 Entry<K,V> e = prev;//e指向prev while (e != null) { Entry<K,V> next = e.next;//next代表下一个entry Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {//如果haah相等并且key值相等 modCount++; size--; if (prev == e)//该链表中只剩有一个entry table[i] = next;//让第i个索引上的entry直接指向next else prev.next = next;//让前面的entry的下一个索引指向带删除entry的下一个索引 e.recordRemoval(this); return e; } prev = e;//记录前面的entry e = next;//e指向下一个entry } return e; }
?该方法表示通过key删除entry
?