美文网首页Java
「深度剖析」一文搞定!从初识HashMap到剖析实现原理

「深度剖析」一文搞定!从初识HashMap到剖析实现原理

作者: 小吴不睡觉 | 来源:发表于2023-05-23 21:02 被阅读0次

    1. 前言

    HashMap是Java中最常用的数据结构之一,它提供了一种键值对映射的存储方式,可以非常快速地进行数据的查找和插入操作。在Java的集合框架中,HashMap是使用最广泛的一种数据结构,几乎在所有的Java项目中都会用到。

    本文将从基础概念、数据结构、源码实现等方面,深入分析HashMap的实现原理,帮助读者更好地理解HashMap的使用和优化。

    2. 基础概念

    HashMap是一种基于哈希表的数据结构,它允许将键和值进行映射,键和值都可以是任意类型的对象。在HashMap中,每个键都会对应一个唯一的值,而且键和值之间的映射关系是一一对应的,即每个键只能对应一个值。

    HashMap的基本操作包括插入、查找、删除等,其中插入和查找操作的时间复杂度都是O(1),即常数时间,非常高效。这是因为HashMap内部采用了哈希表的数据结构,可以通过哈希函数将键转换成对应的哈希值,并将哈希值对数组长度取模后,得到键值对在数组中存储的位置。由于哈希函数的设计,不同的键的哈希值是不同的,因此不同的键值对可以被存储在不同的位置上,避免了冲突。

    但是,由于哈希函数的设计难度较大,如果哈希函数不够好,就容易导致冲突。当两个不同的键的哈希值相同,就会发生冲突。在HashMap中,可以使用链表或红黑树等数据结构来解决冲突。

    3. 数据结构

    在JDK1.7中,HashMap的数据结构是由数组和链表组成的,称为“数组+链表”结构。具体来说,HashMap内部维护了一个Entry数组,每个Entry节点包含了键值对信息和指向下一个节点的指针,如果同一个数组位置上有多个节点,就会形成一个链表。

    当插入一个新的键值对时,首先会根据键的哈希值计算出数组位置,如果该位置上已经有了一个节点,就需要遍历链表,查找是否已经有相同键的节点。如果没有找到相同键的节点,就将新节点插入到链表的末尾。

    在JDK1.8中,HashMap的数据结构发生了变化,采用了“数组+链表/红黑树”结构。具体来说,当链表长度超过8时,链表会自动转化为红黑树,以提高查找效率。当红黑树节点个数小于6时,红黑树会自动转化为链表,以避免频繁的树形结构操作。

    4. 源码实现

    在JDK1.7中,HashMap的源码实现主要包括以下几个类:

    • HashMap:HashMap的主要类,实现了Map接口,提供了键值对的存储、查找、删除等操作。
    • Entry:HashMap中的节点类,包含了键值对信息和指向下一个节点的指针。
    • HashIterator:HashMap的迭代器类,实现了Iterator接口,可以遍历HashMap中的所有键值对。

    在JDK1.8中,HashMap的源码实现也包括以上三个类,但是在实现方式上有所不同。在JDK1.8中,HashMap的主要类是Node,而不是Entry。Node类继承了Map.Entry接口,并包含了键值对信息、指向下一个节点的指针、节点的哈希值等信息。此外,HashMap中还引入了红黑树的实现类TreeNode,用于存储链表转换成红黑树后的节点信息。

    HashMap的源码实现中,最重要的方法之一是put方法,它用于插入新的键值对。在put方法中,会先根据键的哈希值计算出数组位置,然后在该位置上查找是否已经有相同键的节点。如果没有找到相同键的节点,就会创建一个新的Entry节点,并将其插入到链表的末尾。如果已经存在相同键的节点,就将其值更新为新的值。如果链表长度超过了8,就会将链表转换成红黑树。在插入节点时,HashMap会自动调整数组大小,以保证哈希表的负载因子在0.75以下,避免哈希表过度填充。

    除了put方法,HashMap还包括了其他常用的方法,例如get方法、remove方法、size方法等。在JDK1.8中,HashMap还引入了一些新的方法,例如forEach、replace等方法,支持更多的函数式编程操作。

    下面我将通过代码来讲解HashMap的实现原理。
    在JDK1.7中,HashMap的源码实现中,Entry类是HashMap的节点类,它包含了键值对信息和指向下一个节点的指针。在HashMap中,数组的每个元素都是一个Entry节点,当哈希值相同时,会将新的元素插入到链表的末尾。

    下面是Entry类的定义:

    class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
    
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
    
        public final K getKey() {
            return key;
        }
    
        public final V getValue() {
            return value;
        }
    
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
    
        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2|| (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }
    
        public final int hashCode() {
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }
    
        public final String toString() {
            return getKey() + "=" + getValue();
        }
    }
    

    在JDK1.8中,HashMap的源码实现中,Node类是HashMap的节点类,它继承了Map.Entry接口,并包含了键值对信息、指向下一个节点的指针、节点的哈希值等信息。此外,HashMap中还引入了红黑树的实现类TreeNode,用于存储链表转换成红黑树后的节点信息。

    下面是Node类的定义:

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    
        public finalK getKey() {
            return key;
        }
    
        public final V getValue() {
            return value;
        }
    
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
    
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
    
        public final String toString() {
            return key + "=" + value;
        }
    }
    

    5. JDK1.7和JDK1.8的不同点

    在JDK1.7和JDK1.8中,HashMap的实现方式有所不同。在JDK1.7中,HashMap的数据结构是由数组和链表组成的,而在JDK1.8中,HashMap的数据结构则是由数组和链表/红黑树组成的。当链表长度超过8时,链表会自动转化为红黑树,以提高查找效率。当红黑树节点个数小于6时,红黑树会自动转化为链表,以避免频繁的树形结构操作。

    总结

    本文从基础概念、数据结构、源码实现等多个方面,深入分析了HashMap的实现原理,并区分了JDK1.7和JDK1.8的不同点。HashMap作为Java中最常用的数据结构之一,在实际开发中应用广泛,深入理解其实现原理对于优化代码性能和解决问题具有重要意义。同时,ConcurrentHashMap作为HashMap的线程安全版本,也是Java并发编程中经常使用的数据结构,需要掌握其实现原理和使用方法。

    相关文章

      网友评论

        本文标题:「深度剖析」一文搞定!从初识HashMap到剖析实现原理

        本文链接:https://www.haomeiwen.com/subject/tlzxedtx.html