put(K key, V value)方法将传入的键值对存到HashMap中,如果HashMap存在键相同的键值对,就将这个键值对覆盖,返回被覆盖键值对的值。首先检查键值对的键是否为null,是的话调用putForNullKey方法。如果不是通过hash方法得到键key的值,通过indexFor方法得到键值对映射到桶的索引,在得到了这个索引后,使用for循环去这个索引找这个桶里是否已经存放了键值对,如果已经有键值对,就对这个单向链表遍历,看是否有键值对的键与传入的key相同,判断条件:e.hash == hash && ((k = e.key) == key || key.equals(k)),找到了就替换,找不到调用addEntry方法插入到桶中。
通过以上分析,HashMap在发生了哈希冲突后是以链表的形式仍然保存到桶中,就是我们俗称的拉链法。对于这里的哈希冲突指的是键值对键的哈希值通过indexFor方法得到一个索引值(其实就是哈希值对桶的容量取余),放到同一个桶的键值对并不一定哈希值就相等,但哈希值相等一定会放到同一个桶中。当然我会在indexFor这个方法分析的,putForNullKey方法与put方法流程大致相同,只不过null的哈希值不需要计算,直接规定为0。
indexFor方法传入键的哈希值和桶的容量,那么是怎么映射得到在桶中的索引呢?其实就一句话:h & (length-1)。注意length一定是2的幂次,我们假设为16,那么哈希值就要与15每一位相与,15的二进制形式为1111,就相当于保留哈希值第一位到第四位,就是哈希值与16取余的效果。而桶的容量始终为2的幂次,那么哈希值都要与每一位都是一的二进制做&
即相当于:h%length,所以发生哈希冲突并不代表哈希值相同,只是哈希值与桶的容量取余的所得到的索引相同
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); //如果table[i]这个桶中存放有元素,查找有没有含有键为key的键值对, //存在则替换 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方法添加向桶中添加新结点 addEntry(hash, key, value, i); return null; } private V putForNullKey(V value) { //如果有key为null的键值对,替换值 for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; //桶中不存在null键,则调用addEntry方法添加向桶中添加新结点 addEntry(0, null, value, 0); return null; } final int hash(Object k) { int h = 0; if (useAltHashing) { if (k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h = hashSeed; } //一次散列,调用k的hashCode方法,与hashSeed做异或操作 h ^= k.hashCode(); //二次散列 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } static int indexFor(int h, int length) { return h & (length-1); } void addEntry(int hash, K key, V value, int bucketIndex) { //如果HashMap中条目的数量达到了重构阈值且指定的桶不为null,则对HashMap进行扩容(即增加桶的数量) if ((size >= threshold) && (null != table[bucketIndex])) { //调用resize方法对HashMap进行扩容 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; //桶的数量增加,重新计算索引 bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { //保存桶中已有元素e Entry<K,V> e = table[bucketIndex]; //新元素插入桶中,把next指向 table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } putAll(Map<? extends K, ? extends V> m)