叟减 发表于 2025-6-30 10:41:02

8.Java SDK源码分析系列笔记-JDK1.7 HashMap

目录

[*]1. 问题

[*]1.1. 手写HashMap

[*]1.1.1. Entry
[*]1.1.2. put操作
[*]1.1.3. get操作
[*]1.1.4. remove操作

[*]1.2. 为什么构造函数中需要把capacity转成2的power
[*]1.3. 扩容操作

[*]1.3.1. 扩容的逻辑

[*]1.4. 如何解决Hash冲突的?
[*]1.5. 发生Hash冲突的时候是头插入还是尾插入?
[*]1.6. 并发put的后get发生死循环?

[*]1.6.1. 参考

[*]1.7. key可以为NULL么
[*]1.8. ConcurrentModificationException

[*]2. 参考

1. 问题

1.1. 手写HashMap

1.1.1. Entry

class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;//链地址法
    }1.1.2. put操作

public V put(K key, V value) {
        //计算Hash并计算数组中的下标
    int hash = hash(key);
    int i = indexFor(hash, table.length);
        //先遍历链表查看是否有相等的key,有替换value
    for (Entry<K,V> e = table; 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;
      }
    }
        //没有则使用头插法
    addEntry(hash, key, value, i);
    return null;
}1.1.3. get操作

final Entry<K,V> getEntry(Object key) {
                                //计算hash值
    int hash = (key == null) ? 0 : hash(key);
                    //计算数组下标
                    //遍历链表中的每个元素判断key相等
    for (Entry<K,V> e = table;
         e != null;
         e = e.next) {
      Object k;
      if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}1.1.4. remove操作

final Entry<K,V> removeEntryForKey(Object key) {
                        //找到相应的链表
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);
    Entry<K,V> prev = table;
    Entry<K,V> e = prev;

                        //遍历链表删除(其实就是链表的删除操作,需要一个prev和一个current)
    while (e != null) {
      Entry<K,V> next = e.next;
      Object k;
      if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)
                table = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
      }
      prev = e;
      e = next;
    }

    return e;
}1.2. 为什么构造函数中需要把capacity转成2的power

跟indexFor计算数组下标有关
static int indexFor(int h, int length) {
    return h & (length-1);
}不是计算Hash后对table.length取余,而是与操作。
h:                        1001 1001
length:                0001 0000
length-1:        0000 1111
&:                        0000 1001如上&的结果保留的是h的最后4bit。
1.3. 扩容操作

什么时候发生扩容
//当前hashmap中的数量(包括链表中的)已经超过阈值时扩容
if ((size >= threshold) && (null != table)) {
      resize(2 * table.length);//两倍的数组长度
    }1.3.1. 扩容的逻辑

参考
并发put的后get发生死循环
1.4. 如何解决Hash冲突的?

链地址法。即数组+链表
1.5. 发生Hash冲突的时候是头插入还是尾插入?

头插入。这样效率是最高的。
void createEntry(int hash, K key, V value, int bucketIndex) {
                        //保存旧的链表头部
    Entry<K,V> e = table;
                    //创建一个新的entry,并把next指向旧的链表头部。
                    //新的链表头部指向这个新的entry
    table = new Entry<>(hash, key, value, e);
    size++;
}1.6. 并发put的后get发生死循环?

1.7的put操作是头插入的。两个线程同时put并且resize的时候会出现环形链表的情况,所以get操作会出现死循环
resize中有个transfer,需要把旧数组中的所有元素转移到新的数组中
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
                            //遍历数组中的每个元素(链表)
    for (Entry<K,V> e : table) {
                                            //遍历链表中的每个元素
      while(null != e) {
            Entry<K,V> next = e.next;
                                              //重新计算hash
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
                                                    //计算新的下标
            int i = indexFor(e.hash, newCapacity);
                                                    //头插法
            e.next = newTable;
            newTable = e;
            
            e = next;
      }
    }
}1.6.1. 参考

https://blog.csdn.net/zhuqiuhui/article/details/51849692
1.7. key可以为NULL么

可以。        1.7中为table中的第一个元素。
put操作中判断key为NULL的时候,会调用putForNullKey
private V putForNullKey(V value) {
                        //在数组第一个元素的链表上遍历,找到key为null的则替换value并返回旧value
    for (Entry<K,V> e = table; e != null; e = e.next) {
      if (e.key == null) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
      }
    }
    modCount++;
                    //没有则插入到数组第一个位置
    addEntry(0, null, value, 0);
    return null;
}1.8. ConcurrentModificationException

参考:fail-fast.md
遍历时(get)执行删除操作(remove)会抛出这个异常,这叫做fast-fail机制
这是modCount和expectedCount不相等导致的
2. 参考


[*]java - Why is a ConcurrentModificationException thrown and how to debug it - Stack Overflow
[*]Avoiding the ConcurrentModificationException in Java | Baeldung

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 8.Java SDK源码分析系列笔记-JDK1.7 HashMap