Java magic decryption: The underlying mechanism of HashMap is revealed

Insert image description here

1. In-depth analysis of source code

1.1 A peek at the design ideas in the Java collection framework

The Java collection framework is a very important part of Java programming, providing various data structures and algorithms to enable developers to organize and manipulate data efficiently.

The design ideas of Java collection framework mainly include the following aspects:

  1. Generality:
    • The Java collection framework is designed as general-purpose, reusable components. In this way, the same collection framework can be used to store and process various types of objects.
  2. Scalability:
    • The collection framework provides a series of interfaces and classes that allow developers to implement custom collection types. This extensibility enables developers to create new collection classes based on specific needs.
  3. Performance:
    • The collection framework is designed with performance factors in mind and improves the efficiency of operations by selecting appropriate data structures and algorithms. For example, ArrayList and LinkedList are both implementations of the List interface, but they have different performance in insertion and deletion operations.
  4. Interoperability:
    • All interfaces and classes in the collection framework are designed to interoperate with each other. This interoperability enables easy conversion and use between different collection classes.
  5. Readability and Simplicity:
    • The design of the collection framework pursues code readability and simplicity. By providing a clear set of interfaces and methods, developers can more easily understand and use the collections framework.
  6. Thread Safety:
    • Some collection classes (such asHashtable、Vector) are designed to be thread-safe and suitable for multi-threaded environments. In addition, Java provides some concurrent collection classes that are safer to use in a multi-threaded environment, such as ConcurrentHashMap.
  7. Fail-Fast mechanism (fast failure mechanism):
    • The collection framework uses a fast failure mechanism during the iteration process. When multiple threads modify the same collection, it may be thrownConcurrentModificationException to remind the developer of the iteration process. Do not modify the collection.

Generally speaking, the design ideas of the Java collection framework focus on versatility, performance, scalability and simplicity, providing developers with rich and powerful tools for processing various types of data.

1.2 Interpret the source code of HashMap line by line

1.2.1 Class information

Insert image description here

1.2.2 Constant attributes

Insert image description here

1.2.3 Variable attributes

Insert image description here

1.2.4 Node information

Insert image description here

1.2.5 Construction method

1. Construct an empty HashMap with default initial capacity (16) and default loading factor (0.75).

Insert image description here

2. Construct an empty HashMap with specified initial capacity and default loading factor (0.75).
Insert image description here

3. Construct an empty HashMap with specified initial capacity and loading factor.

Insert image description here

4. Construct a new HashMap with the same mapping relationship as the specified Map.

Insert image description here

5. Expand LRU implementation methods and ideas.

When implementing a cache based on LRU策略, a data structure is usually used to store the data in the cache, and data access needs to be recorded Order. A common data structure is a combination of a doubly linked list and a hash table.

Implementation based on doubly linked list and hash table:

  1. UseDouble linked list: The doubly linked list can record the access sequence of data. When a certain data is accessed, it can be moved to the linked list. head or tail. 头部表示最近访问的数据, 尾部表示最久未被访问的数据.
  2. Use hash table: Hash table is used for快速查找缓存中的数据, which can map the key of data to the corresponding linked list node, so as to Enables fast search and insert operations.

The basic idea of ​​​​implementing LRU cache is as follows:

  • When the data in 访问缓存 is needed, first check whether the data exists in the hash table.
  • If存在, move the data to the head of the linked list, indicating that it has been accessed recently.
  • If不存在, the data needs to be loaded from the back-end storage, inserted into the head of the linked list, and the hash table updated at the same time.
  • When缓存已满, the data at the end of the linked list needs to be eliminated and the hash table updated at the same time.

In this way, a cache system based on the LRU elimination strategy is implemented, which can ensure that the least recently used data will be eliminated in a timely manner, thus keeping the data in the cache the most useful.

1.2.6 put method

Since the put method has a large amount of code, the source code is interpreted in the form of 代码+注释.

1.2.6.1 putVal method
/**
 * HashMap的put操作源码+注释
 */
public V put(K key, V value) {
    
    
    // 先根据hash()方法,计算存放元素在table数组中的下标
    return putVal(hash(key), key, value, false, true);
}

// hash方法
static final int hash(Object key) {
    
    
    int h;
    // 如果key为null,返回hash=0
    // 如果key不为null,那么进行key的hashCode值的高低位异或运算,返回结果作为hash值
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

// putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    
    
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 如果第一次插入,table为空或者length等于0
    if ((tab = table) == null || (n = tab.length) == 0)
        // 调用resize方法进行初始化
        n = (tab = resize()).length;
    // 通过hash值计算索引位置,将该索引位置的头节点赋值给p且p为空
    // 实则进行取余操作 == p=tab[hash%length]
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 在该索引位置新增一个节点
        tab[i] = newNode(hash, key, value, null);
    else {
    
    
        // table表该索引位置不为空,则会发生hash冲突
        Node<K,V> e; K k;
        // 当p节点的hash值和key跟传入的相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            // p节点即为要查找的目标节点,将p节点赋值给e节点
            e = p;
        // 当p节点为TreeNode
        else if (p instanceof TreeNode)
            // 调用红黑树的putTreeVal方法查找目标节点
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
    
    
            // 判断完,则p节点为普通链表节点
            //遍历节点操作,使用binCount统计链表的节点数
            for (int binCount = 0; ; ++binCount) {
    
    
                // p的next节点为空时,代表找不到目标节点
                if ((e = p.next) == null) {
    
    
                    // 新增一个节点并插入链表尾部
                    p.next = newNode(hash, key, value, null);
                    // 当binCount节点数超过8个
                    // TREEIFY_THRESHOLD - 1:由于循环是从p节点的下一个节点开始的
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        // 调用treeifyBin方法将链表节点转为红黑树节点
                        treeifyBin(tab, hash);
                    break;
                }
                // e节点的hash值和key值与传入的相同
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // e节点即为目标节点,跳出循环
                    break;
                // 将p指向下一个节点,继续往后遍历
                p = e;  
            }
        }
        // e节点不为空,代表目标节点存在
        if (e != null) {
    
    
            // 使用传入的value覆盖该节点的value,即保留原来元素的value
            V oldValue = e.value;
            // 当oldValue为空
            if (!onlyIfAbsent || oldValue == null)
                // 替换value
                e.value = value;
            // 用于LinkedHashMap
            afterNodeAccess(e);
            // 返回一个原有的value
            return oldValue;
        }
    }
    // 添加操作执行后,对modCount、size做加一操作
    ++modCount;
    // 插入节点后节点数超过阈值
    if (++size > threshold)
        // 调用resize方法进行扩容
        resize();
    // 用于LinkedHashMap
    afterNodeInsertion(evict); 
    return null;
}
1.2.6.2 putTreeVal method
/**
 * 当阈值达到触发红黑树的put的源码+注释
 */
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                               int h, K k, V v) {
    
    
    Class<?> kc = null;
    boolean searched = false;
    // TreeNode<K,V>继承于LinkedHashMap.Entry<K,V>
    // parent节点不等于空,则查找根节点
    // 即得出索引位置的头节点并不一定为红黑树的根节点
    TreeNode<K,V> root = (parent != null) ? root() : this;
    // 将根节点赋值给p节点,开始进行查找
    for (TreeNode<K,V> p = root;;) {
    
    
        int dir, ph; K pk;
        // 当传入的hash值小于p节点的hash值
        if ((ph = p.hash) > h)
            // 将dir赋值为-1,代表向p的左边查找树
            dir = -1;
        // 否则传入的hash值大于p节点的hash值
        else if (ph < h)
            // 将dir赋值为1,代表向p的右边查找树
            dir = 1;
        // 当传入的key值等于p节点的key值
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            // 即p节点为目标节点, 返回p节点
            return p;
        // 当k所属的类没有实现Comparable接口 或 k和p节点的key相等
        else if ((kc == null &&
                  (kc = comparableClassFor(k)) == null) ||
                 (dir = compareComparables(kc, k, pk)) == 0) {
    
    
            // 由于局部变量初始为false,即当第一次判断是符合条件的
            if (!searched) {
    
    
                TreeNode<K,V> q, ch;
                // 查找过后标记为true
                searched = true;
                // 从p节点的左节点和右节点分别调用find方法进行查找
                if (((ch = p.left) != null &&
                     (q = ch.find(h, k, kc)) != null) ||
                    ((ch = p.right) != null &&
                     (q = ch.find(h, k, kc)) != null))
                    // 查找到目标节点则返回
                    return q;
            }
            // 否则使用定义的一套规则来比较k和p节点的key的大小, 用来决定向左还是向右查找
            // dir<0则代表k<pk,则向p左边查找;反之亦然
            dir = tieBreakOrder(k, pk); 
        }
 		// xp赋值为x的父节点,中间变量,用于下面给x的父节点赋值
        TreeNode<K,V> xp = p;   
        // dir<=0则向p左边查找,否则向p右边查找,如果为null
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
    
    
            // xp的next节点
            Node<K,V> xpn = xp.next;    
            // 创建新的节点, 其中x的next节点为xpn, 即将x节点插入xp与xpn之间
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
            // 调整x、xp、xpn之间的属性关系
            // 如果dir <= 0
            if (dir <= 0) 
                //  代表x节点为xp的左节点
                xp.left = x;
            else        
                // 如果dir> 0, 则代表x节点为xp的右节点
                xp.right = x;
            // 将xp的next节点设置为x
            xp.next = x;    
            // 将x的parent和prev节点设置为xp
            x.parent = x.prev = xp; 
            // 当xpn不为空
            if (xpn != null)
                // 将xpn的prev节点设置为x节点,与上文的x节点的next节点对应
                ((TreeNode<K,V>)xpn).prev = x;
            // 进行红黑树的插入平衡调整
            moveRootToFront(tab, balanceInsertion(root, x));
            return null;
        }
    }
}
1.2.6.3 tieBreakOrder method
/**
 * 使用定义的一套规则来比较k和p节点的key的大小
 */
static int tieBreakOrder(Object a, Object b) {
    
      
    int d;
    if (a == null || b == null ||
        (d = a.getClass().getName().
         compareTo(b.getClass().getName())) == 0)
        // a < b为-1,a > b为1
        d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
             -1 : 1);
    return d;
}
1.2.6.4 treeifyBin method
/**
 * 将链表转为红黑树
 */
final void treeifyBin(Node<K,V>[] tab, int hash) {
    
    
    int n, index; Node<K,V> e;
    // 当tab为空或者tab的长度小于64
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        // 进行扩容
        resize();
    // 根据hash值计算索引值,将该索引位置的节点赋值给e且e不为null
    else if ((e = tab[index = (n - 1) & hash]) != null) {
    
    
        TreeNode<K,V> hd = null, tl = null;
        // 从e开始遍历该索引位置的链表
        do {
    
    
            // 将链表节点转红黑树节点
            TreeNode<K,V> p = replacementTreeNode(e, null);
            // tl为空代表为第一次循环
            if (tl == null)	
                // 将头节点赋值给hd
                hd = p;
            else {
    
    
                // 如果不是第一次遍历
                // 当前节点的prev属性设为上一个节点
                p.prev = tl;  
                // 上一个节点的next属性设置为当前节点
                tl.next = p;    
            }
            // 将p节点赋值给tl,用于在下一次循环中作为上一个节点进行一些链表的关联操作(p.prev = tl 和 tl.next = p)
            tl = p;
        } while ((e = e.next) != null);
        // 将tab该索引位置赋值为新的TreeNode的头节点,如果该节点不为空
        if ((tab[index] = hd) != null)
            // 以头节点(hd)为根节点, 构建红黑树
            hd.treeify(tab);
    }
}

// 将链表节点转红黑树节点
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
    
    
   return new TreeNode<>(p.hash, p.key, p.value, next);
}
1.2.6.5 treeify method
/**
 * 将哈希表中的链表结构转换为树形结构,以提高查找效率
 */
final void treeify(Node<K,V>[] tab) {
    
    
    TreeNode<K,V> root = null;
    // 开始一个循环,初始化变量x为当前节点,然后在每次迭代中将x更新为next
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
    
    
        // next赋值为x的下个节点
        next = (TreeNode<K,V>)x.next;  
        // 将x的左右节点设置为空
        x.left = x.right = null;    
        // 如果还没有根节点
        if (root == null) {
    
    
            // 根节点没有父节点,设为null
            x.parent = null; 
            // 根节点必须为黑色,false为黑色
            x.red = false; 
            // 在将x设置为根节点
            root = x;   
        }
        // 如果有根节点
        else {
    
    
            // k赋值为x的key
            K k = x.key;
            // h赋值为x的hash值
            int h = x.hash;	
            // 定义一个类型未知的变量kc
            Class<?> kc = null;
            // 开始一个无限循环,初始化变量p为root节点,表示对树进行遍历
            for (TreeNode<K,V> p = root;;) {
    
    
                int dir, ph;
                K pk = p.key;
                // 比较当前节点x的哈希值h与节点p的哈希值ph,根据比较结果给变量dir赋值
                // 如果x节点的hash值小于p节点的hash值,则将dir赋值为-1, 代表向p的左边查找
                if ((ph = p.hash) > h)
                    dir = -1;
                // 如果x节点的hash值大于p节点的hash值,则将dir赋值为1, 代表向p的右边查找
                else if (ph < h)
                    dir = 1;
                // x的hash值和p的hash值相等,则比较key值
                // 如果k没有实现Comparable接口 或者 x节点的key和p节点的key相等
                else if ((kc == null && 
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    // 使用定义的一套规则来比较x节点和p节点的大小,用来决定向左还是向右查找
                    dir = tieBreakOrder(k, pk);
 
                // 将p节点的父节点赋值给xp,中间变量用于下面给x的父节点赋值
                TreeNode<K,V> xp = p;   
                // dir<=0则向p左边查找,否则向p右边查找,如果为null,则代表该位置即为x的目标位置
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
    
    
                    // x的父节点即为最后一次遍历的p节点
                    x.parent = xp; 
                    // 如果dir <= 0, 代表x节点为父节点的左节点
                    if (dir <= 0)   
                        xp.left = x;
                    // 如果dir > 0, 代表x节点为父节点的右节点
                    else    
                        xp.right = x;
                    // 进行红黑树的插入平衡(通过左旋、右旋和改变节点颜色来保证当前树符合红黑树的要求)
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    // 如果root节点不在tab索引位置的头节点, 则将其调整为头节点
    moveRootToFront(tab, root);
}
1.2.7 get method
/**
 * HashMap的get操作源码+注释
 */
public V get(Object key) {
    
    
    Node<K,V> e;
    // 调用hash(key)方法计算键key的哈希值,然后调用getNode方法获取与该键对应的节点,将结果赋给变量e
    // 如果e为null,则返回null;否则返回e节点的值e.value
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
 
// 获取与该键对应的节点
final Node<K,V> getNode(int hash, Object key) {
    
    
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    // 对table进行校验:table不为空 && table长度大于0 && table索引位置(使用table.length - 1和hash值进行位与运算)的节点不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
    
    
        // 检查first节点的hash值和key是否和入参的一样,如果一样则first即为目标节点,直接返回first节点
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        // 如果first不是目标节点,并且first的next节点不为空则继续遍历
        if ((e = first.next) != null) {
    
    
            if (first instanceof TreeNode)
                // 如果是红黑树节点,则调用红黑树的查找目标节点方法getTreeNode
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            // 继续遍历
            do {
    
    
                // 执行链表节点的查找,向下遍历链表, 直至找到节点的key和入参的key相等时,返回该节点
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    // 找不到符合的返回空
    return null;
}
1.2.8 remove method
/**
 * HashMap的remove操作源码+注释
 */
public V remove(Object key) {
    
    
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}
 
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    
    
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    // 如果table不为空 && table长度大于0 && 根据hash值计算出来的索引位置不为空, 将该位置的节点赋值给p
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
    
    
        Node<K,V> node = null, e; K k; V v;
        // 如果p的hash值和key都与入参的相同, 则p即为目标节点, 赋值给node
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        // 否则将p.next赋值给e,向下遍历节点
        else if ((e = p.next) != null) {
    
    
            // 如果p是TreeNode则调用红黑树的方法查找节点
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
    
    
                // 否则,进行普通链表节点的查找
                do {
    
    
                    // 当节点的hash值和key与传入的相同,则该节点即为目标节点
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
    
    
                        // 赋值给node, 并跳出循环
                        node = e;	
                        break;
                    }
                    // p节点赋值为本次结束的e,在下一次循环中,e为p的next节点
                    p = e;  
                    // e指向下一个节点
                } while ((e = e.next) != null); 
            }
        }
        // 如果node不为空,即根据传入key和hash值查找到目标节点,则进行移除操作
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
    
    
            // 如果是TreeNode则调用红黑树的移除方法
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            // 如果node是该索引位置的头节点则直接将该索引位置的值赋值为node的next节点
            // “node == p”只会出现在node是头节点的时候,如果node不是头节点,则node为p的next节点
            else if (node == p)
                tab[index] = node.next;
            // 否则将node的上一个节点的next属性设置为node的next节点
            // 即将node节点移除, 将node的上下节点进行关联(链表的移除)
            else
                p.next = node.next;
            ++modCount;
            --size;
            // 供LinkedHashMap使用
            afterNodeRemoval(node); 
            // 返回被移除的节点
            return node;
        }
    }
    return null;
}
1.2.9 resize method
/**
 * HashMap的resize操作源码+注释
 */
final Node<K,V>[] resize() {
    
    
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 旧表的容量不为0
    if (oldCap > 0) {
    
    
        // 判断旧表的容量是否超过最大容量值
        if (oldCap >= MAXIMUM_CAPACITY) {
    
    
            // 将阈值设置为Integer.MAX_VALUE,并直接返回旧表
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 将newCap赋值为oldCap的2倍,如果newCap<最大容量小于最大容量值并且oldCap>=16
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 将新阈值设置为原来的两倍
            newThr = oldThr << 1; // double threshold
    }
    // 如果旧表的容量的阈值大于0, 是因为初始容量被放入阈值,则将新表的容量设置为旧表的阈值
    else if (oldThr > 0)
        newCap = oldThr;
    else {
    
    
        // 旧表的容量为0, 旧表的阈值为0,这种情况是没有传初始容量的new方法创建的空表,将阈值和容量设置为默认值
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 如果新表的阈值为空, 则通过新的容量*负载因子获得阈值
    if (newThr == 0) {
    
    
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    // 将当前阈值设置为刚计算出来的新的阈值,定义新表,容量为刚计算出来的新容量,将table设置为新定义的表
    threshold = newThr;
    @SuppressWarnings({
    
    "rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 如果旧表不为空,则需遍历所有节点,将节点赋值给新表
    if (oldTab != null) {
    
    
        for (int j = 0; j < oldCap; ++j) {
    
    
            Node<K,V> e;
            // 将索引值为j的旧表头节点赋值给e
            if ((e = oldTab[j]) != null) {
    
      
                // 将旧表的节点设置为空, 以便垃圾收集器回收空间
                oldTab[j] = null; 
                // 如果e.next为空, 则代表旧表的该位置只有1个节点,计算新表的索引位置, 直接将该节点放在该位置
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                // 如果是红黑树节点,则进行红黑树的重hash分布(跟链表的hash分布基本相同)
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else {
    
     // preserve order
                    // 如果是普通的链表节点,则进行普通的重hash分布
                    // 存储索引位置为:“原索引位置”的节点
                    Node<K,V> loHead = null, loTail = null; 
                    // 存储索引位置为:“原索引位置+oldCap”的节点
                    Node<K,V> hiHead = null, hiTail = null; 
                    Node<K,V> next;
                    do {
    
    
                        next = e.next;
                        // 如果e的hash值与旧表的容量进行与运算为0,则扩容后的索引位置跟旧表的索引位置一样
                        if ((e.hash & oldCap) == 0) {
    
    
                            // 如果loTail为空
                            if (loTail == null) 
                                // 将loHead赋值为第一个节点
                                loHead = e; 
                            else
                                // 否则将节点添加在loTail后面
                                loTail.next = e;  
                            // 并将loTail赋值为新增的节点
                            loTail = e; 
                        }
                        else {
    
    
                             // 如果hiTail为空, 代表该节点为第一个节点
                            if (hiTail == null)
                                // 将hiHead赋值为第一个节点
                                hiHead = e; 
                            else
                                // 否则将节点添加在hiTail后面
                                hiTail.next = e;   
                            // 并将hiTail赋值为新增的节点
                            hiTail = e; 
                        }
                    } while ((e = next) != null);
                    // 如果loTail不为空(说明旧表的数据有分布到新表上“原索引位置”的节点),则将最后一个节点的next设为空,并将新表上索引位置为“原索引位置”的节点设置为对应的头节点
                    if (loTail != null) {
    
    
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 如果hiTail不为空(说明旧表的数据有分布到新表上“原索引+oldCap位置”的节点),则将最后一个节点的next设为空,并将新表上索引位置为“原索引+oldCap”的节点设置为对应的头节点
                    if (hiTail != null) {
    
    
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    // 返回新表
    return newTab;
}

2. Applications and Best Practices

2.1 How to use HashMap reasonably in actual projects

Suggestions for reasonable use of HashMap:

  1. Cache data: You can use HashMap as the cache data structure, and store 计算结果 or 频繁访问的数据 in HashMap , to improve data access speed.
  2. Data Index: In scenarios where you need to quickly find data according to a certain field, you can use HashMap to build an index to quickly locate the data object.
  3. Configuration information storage: You can use HashMap to store the configuration information of the application, for example键值对形式的配置参数.
  4. Data grouping: When data needs to be grouped, HashMap can be used for group storage to quickly obtain specific grouped data.
  5. Cache calculation results: In some scenarios where 频繁计算 is required, HashMap can be used to cache calculation results to avoid repeated calculations.
  6. Quick access and modification: HashMap provides fast查找、插入和删除 operations, which is suitable for scenarios where these operations need to be performed frequently.
  7. Replacing multi-level nested conditional judgments: Sometimes HashMap can be used to replace multi-level nested conditional judgments to improve the readability and maintainability of the code.
  8. Event processing: You can use HashMap to save event processors in event-driven systems, and quickly find the corresponding processor for processing according to the event type.
  9. Routing table: In network-related applications, HashMap can be used to build a routing table and quickly find the routing information corresponding to the target address.

2.2 Best practices and considerations

Best Practices:

  1. Initial capacity: When creating a HashMap, try to provide initial capacity to reduce the frequency of expansion operations. This can be done via parameters in the constructor, such as HashMap(int initialCapacity).

    Map<String, Integer> map = new HashMap<>(16); // 初始化容量为16
    
  2. Load factor: Load factor is a measure of how full a hash table can be before it is automatically expanded. The default load factor is 0.75, you can adjust it according to your needs.

    Map<String, Integer> map = new HashMap<>(16, 0.8f); // 指定负载因子为0.8
    
    
  3. Choose a good hash function: If you use custom objects as keys, make sure you implement the appropriate hashCode() and < a i=3>method. equals()

  4. Thread safety: If using HashMap in a multi-threaded environment, consider using ConcurrentHashMap, which provides better concurrency performance.

    Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
    
    
  5. Traversal method: Use entrySet() to traverse HashMap instead of using keySet() and < respectively a i=4>. This avoids calculating the hash code multiple times. values()

    for (Map.Entry<String, Integer> entry : map.entrySet()) {
          
          
        // 处理每个键值对
    }
    
    

Precautions:

  1. Null pointer check: Before using the get() method to get the value, it is best to check if the key exists to avoid null pointer exceptions.

    if (map.containsKey(key)) {
          
          
        // 避免空指针异常
        Integer value = map.get(key);
        // 处理值
    }
    
    
  2. Key and value types: Pay attention to the HashMap key and value types and make sure they match your expectations. Use泛型to provide type checking in编译时.

  3. Expansion cost: HashMap will automatically expand when it reaches a certain load factor, which may lead to performance loss. In performance-sensitive scenarios, you can consider手动调整容量 to reduce the occurrence of capacity expansion.

  4. Don't overuse HashMap: In some cases, there may be more suitable data structures, such as LinkedHashMap, TreeMap, etc., depending on your needs.

  5. Understand concurrency limitations: If you use HashMap in a concurrent environment, pay attention to possible concurrency limitations and ensure that your code is thread-safe.

3. Conclusion

3.1 Comprehensive summary of HashMap

Comprehensive summary of HashMap:

  1. Overview:
  • Definition: HashMap is part of the Java collection framework, which implements the Map interface and is used to store key-value pairs.
  • Features: allows storing null keys and null values, asynchronously (not thread-safe).
  1. Basic operations:
  • Insert element: put(key, value) The method is used to insert key-value pairs.
  • Get element: get(key) The method is used to get the value based on the key.
  • Delete element: remove(key) The method is used to delete key-value pairs based on key.
  1. Internal implementation:
  • Array + linked list/red-black tree: HashMap internally uses an array to store buckets. Each bucket is a linked list or red-black tree, used to solve hashes. conflict.
  • Hash algorithm: Determine the position of the key in the array by operating on the hash code of the key.
  1. Hash collision:
  • Linked list resolves conflicts: Key-value pairs with the same hash code are stored in the same bucket in the form of a linked list.
  • Red-black tree optimization: When the length of the linked list is too long, the linked list will be converted into a red-black tree to improve retrieval efficiency.
  1. Scaling and Load Factor:
  • Load factor: The default is 0.75, which means that when the number of elements in the HashMap reaches the capacity multiplied by the load factor, the capacity expansion operation will be performed.
  • Expansion: When the load factor is reached, HashMap will create a new array with twice the original capacity and reallocate the original elements to the new array.
  1. performance:
  • Average time complexity: The average time complexity of insertion, deletion, and search is O(1), under ideal circumstances.
  • Note: Performance may be degraded due to hash collisions and scaling operations.
  1. Precautions for use:
  • Thread safety: HashMap is not thread-safe. If you need to use it in a multi-threaded environment, you can consider using ConcurrentHashMap.
  • equals and hashCode methods: In order to store and retrieve objects correctly, the key's class must correctly implement equals and hashCodeMethod.
  1. JDK version changes:
  • JDK 8: Introduces red-black tree optimization to improve performance.
  • JDK 9: Elements in tree-based buckets (bins) are now sorted in insertion order.

3.2 Encourage readers to study and practice in depth

  • Source code analysis: Reading the source code of HashMap is an effective way to learn its implementation principles. By viewing the HashMap source code of the Java standard library, you can gain an in-depth understanding of how it handles hash conflicts, calculates hash codes, and expands its capacity.
  • Practical application: Apply HashMap to actual projects and observe its performance in different scenarios. It is important to understand when to use a HashMap and how to adjust parameters such as its initial capacity.
  • In-depth study of data structures and algorithms: Understand how hash tables work in computer science, and learn other data structures and algorithms to help better understand HashMap advantages and limitations.
  • Participate in open source projects: If possible, participate in open source projects, especially those related to data structures and algorithms. Through actual coding and communication with other developers, you can deepen your understanding of HashMap and other data structures.

盈若安好,便是晴天

Guess you like

Origin blog.csdn.net/qq_51601665/article/details/134415097