前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >HashMap源码剖析

HashMap源码剖析

作者头像
java达人
发布于 2020-02-25 01:32:14
发布于 2020-02-25 01:32:14
82700
代码可运行
举报
文章被收录于专栏:java达人java达人
运行总次数:0
代码可运行

HashMap是大家常用的基于哈希表的Map接口实现,这里解说的是JDK1.8的代码,在介绍它之前,我们先来看看编写HashMap代码的是哪几位大牛。

Doug Lea

就是这位鼻梁挂着眼镜,留着德王威廉二世的胡子的人。

他是JCP (Java社区项目)中的一员 由JDK 1.1到JDK 1.2的很重要的一项新创举就是Collections,其Collection的概念可以说承袭自Doug Lea于1995年发布的第一个被广泛应用的collections;2004年所推出的Tiger广纳了15项JSRs(Java Specification Requests)的语法及标准,其中一项JSR-166是来自于Doug编写的util.concurrent包。

Josh Bloch

别的不提,《Effective Java》作者这一身份就可以让你记得他。

Arthur van Hoff

公司首席技术官、创始人、临时CEO。

Neal Gafter

与Josh Bloch联合编著《Java解惑》一书。

注释中HashMap的简明介绍

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

在此,它与HashTable作了比较,主要有两个区别

1、HashMap是不同步的 如果多个线程并发访问一个哈希map,并且至少有一个线程对Map进行结构性修改,则必须在外部对其进行同步(结构性修改是指增加或删除一个或多个映射的操作;仅仅更改已经包含的键关联的值并不是结构性修改),即可以使用Collections.synchronizedMap包装。这最好在创建时完成,以防止意外的非同步访问:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 Map m = Collections.synchronizedMap(new HashMap(...));

2、HashMap允许null值和null键

还介绍了其他需要注意的特性,即HashMap不保证Map的顺序(为基本操作get、put提供了稳定的时间性能,它假定散列函数将元素适当地分散到各个bucket中)、基本的数据结构等。

接着我们开始浏览下内部代码:

数据结构

为了方便后续理解,我们不得不先跳到HashMap数据结构相关的那段代码,了解下HashMap基本的数据结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 transient Node<K,V>[] table;
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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 final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(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;
        }
    }

真的是数组链表结构,在首次使用时,数组即初始化,table中的每个元素即是一个链表(Node)。JDK8以后,为了更高效的检索,在链表节点数达到一定数量后,会转换为红黑树结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }

        /**
         * Returns root of tree containing this node.
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
  //代码量较多,此处省略
  //......
}

TreeNode 继承了LinkedHashMap.Entry,这里代码较多,省略,有兴趣可以自己翻阅源码详细阅读。

图示:

(https://images.morethink.cn/b3e3671b2edf38a675b5a28587f37ae4.png)

类变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

这是HashMap的默认初始容量,即创建时桶的个数,注释中说容量值必须是2的整数次幂

The default initial capacity - MUST be a power of two.

为什么是2的整数次幂?

1、这样可以通过hash&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多。

2、 其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了hash&(length-1)的最后一位可能为0,也可能为1(这取决于hash的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样hash&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数标位置上,这便浪费了近一半的空间。另外,2的n次方能保证length-1的(n - 1)低位都是1,使hash低位的特征得以更好地保留,当hash低位相同时两个元素才能产生hash碰撞。因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。

在设置map的初始容量时,应该考虑map中条目期望数量及其负载因子,从而最小化rehash操作的数量。如果初始容量大于最大条目数除以负载因子的结果,则不会发生rehash操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static final int MAXIMUM_CAPACITY = 1 << 30;

最大容量,可以使用带有参数的构造函数自己重新指定。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static final float DEFAULT_LOAD_FACTOR = 0.75f;

默认的负载因子。负载因子是度量哈希表数量达到多满时容量将自动扩容。当哈希表中的条目数超过负载因子和当前容量的乘积时,将对哈希表进行rehash(即重新构建内部数据结构),使哈希表的桶数大约提高到原来的两倍。那为什么默认是0.75呢?一般来说,默认的负载因子(0.75)在时间和空间成本之间提供了很好的权衡。值取得高一点减少了空间开销,但增加了查找成本。想深入研究可以学习下泊松分布,0.75的碰撞最小。http://www.ruanyifeng.com/blog/2015/06/poisson-distribution.html#comment-356111

初始容量和负载因子是HashMap实例中两个影响其性能的重要参数。

集合视图迭代的时间与HashMap实例的“容量”(桶的数量)及其大小(键值对的数量)成比例.因此,如果迭代性能很重要,那么不要将初始容量设置得太高(或负载因子太低)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 static final int TREEIFY_THRESHOLD = 8;

树化阈值,若链表上节点超过TREEIFY_THRESHOLD - 1,即链表长度为8,将链表转变为红黑树

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 static final int UNTREEIFY_THRESHOLD = 6;

链表化阈值,当链表的节点个数小于等于解除树形化阀值UNTREEIFY_THRESHOLD时,将红黑树转为普通链表, 为什么上面两个阀值不一样呢?这里要解释下复杂度震荡的概念,假如TREEIFY_THRESHOLD为8,一个桶中链表长度为8时,此时继续向该桶中put会进行树化,然后remove又会链表化。如果反复put和remove,每次都会进行极其耗时的数据结构转换。

因此,推迟缩容的操作,将减少这种极端情况发生的概率

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 static final int MIN_TREEIFY_CAPACITY = 64;

对桶进行树化的最小容量。(如果小于这个容量,且桶中的节点超过树化阀值,就会进行扩容操作。)

实例变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 transient Node<K,V>[] table;

正是上面提到的数组链表数据结构中的数组。使用transient修饰,表示不能序列化,因为读写Map是根据Object.hashcode()来确定从哪个bucket读/写, 而Object.hashcode()是native方法, 不同的JVM里可能是不一样的。HashMap使用writeObject和readObject来实现自定义序列化,而不仅仅是让其字段正常序列化。它将桶的数量,总的容量和每个条目写入流,并在反序列化时从这些字段重建Map。table本身不需要序列化,节省空间。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 transient Set<Map.Entry<K,V>> entrySet;

此map中包含的映射的Set视图,通过entrySet()获得。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 transient int size;

此map中包含的键-值映射数量。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 transient int modCount;

对该HashMap进行结构性修改的次数。此字段用于实现HashMap的集合视图迭代器的快速失败。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 int threshold;

需要resize的下一个阀值(容量*负载因子)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 final float loadFactor;

哈希表的负载因子,默认是DEFAULT_LOAD_FACTOR,可通过构造函数指定。由于loadFactor不能改变,用final修饰。

构造函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

部分构造函数可以指定初始容量和负载因子。如果要将大量映射存储在HashMap实例中,那么用足够大的容量创建map比根据需要执行自动rehash扩展表能更有效地存储映射。这里请看第一个构造函数中的tableSizeFor方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

如上所述,容量值必须是2的整数次幂,该方法将返回大于输入参数的最小的2的整数次幂(如不考虑最大容量限制的情况),如initialCapacity为7,则返回8,作为table数组的大小.

核心操作

接下来看下HashMap的两个核心操作:put和get。

put
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
       //case1:若table为null或者长度为0,则进行resize扩容操作,risize方法后续将介绍
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
        //case2:没有哈希冲突,则将值放入空桶中
            tab[i] = newNode(hash, key, value, null);
        else {
        //case3:存在哈希冲突
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
               // case3.1:如果key已经存在,覆盖旧值
                e = p;
            else if (p instanceof TreeNode)
              // case3.2:链表已转化为红黑树,加入红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
              // case3.3:节点加入链表尾部,如果超过阀值,则转化为红黑树
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果key已经存在,覆盖旧值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                 }
             }
            //当key已经存在,执行覆盖旧值逻辑。
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
          //容量超过阀值,则扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }
关键方法进一步解析:

1、hash(key):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里为什么要将hashcode右位移16位。因为table使用2的整数次幂掩码,所以仅在当前掩码上方的位中变化的哈希集, 将发生冲突。因此,我们应用了一个转换,将高半区和低半区做异或,混合原始哈希码的高位和低位,以此来加大低位的随机性。

如果我们不做移位异或运算,那么在计算槽位时将丢失高区特征,当两个哈希码很接近时,就可能导致一次哈希碰撞。

2、

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 if ((p = tab[i = (n - 1) & hash]) == null)
        //case2:没有哈希冲突,则将值放入空桶中
        tab[i] = newNode(hash, key, value, null);

上面说了,使用了hash&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多。

3、resize()

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            //大于最大容量,不扩容
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //扩大为原来两倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0)
        // 初始容量设置为阈值
            newCap = oldThr;
        else {
        // 初始阈值为零表示使用默认值
            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);
        }
        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;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                    //只有一个元素,直接移到新的桶中,e.hash & (newCap - 1)计算桶新下标.
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                    //是树的情况
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                       //当前桶为链表,rehash
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                             //(e.hash & oldCap) == 0,说明该元素不用移动
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                            //否则,元素需要移动
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            //元素依然在j位置,即oldIndex
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            //元素移动到 oldIndex + oldCap 位置
                            newTab[ + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

如果table为null,则根据字段threshold中保持的初始容量值创建。如果不为null,因为我们根据2的整数次幂扩展,所以每个bin中的元素必须保持相同的索引,或者在新表中以2的整数次幂偏移,即原来桶中的元素将在新表的oldIndex或oldCap+oldIndex中。这样也尽量减少了rehash时元素位置的移动。

4、treeifyBin(tab, hash)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

如前述说明,假如桶数组长度小于MIN_TREEIFY_CAPACITY,则不会进行树化,而是扩容,树化本身就是耗时操作,这里是空间换时间的体现。

get:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //判断计算下标(n - 1) & hash
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash &&
                ((k = first.key) == key || (key != null && key.equals(k))))
                // 检查第一个元素,如果是所求则返回
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                  //是红黑树的情况
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                   //是链表的情况,循环检查获取所求
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

迭代器遍历

HashMap所有“集合视图方法”返回的迭代器是快速失败的:在创建迭代器之后的任何时候,以任何方式(除了通过迭代器自己的remove方法)对map进行结构修改,迭代器将抛出ConcurrentModificationException异常。因此,在面对并发修改时,迭代器会快速失败,从而避免在将来某个不确定的时间发生任意的、不确定的行为风险。

注意迭代器的快速失败行为不能得到保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何严格保证。快速迭代器在最大努力的基础上抛出ConcurrentModificationException。因此,期望依赖于这个异常编写正确的程序是不恰当的:迭代器的快速失败行为应该只用于检测bug。

后续内容计划

后面将继续剖析其他Java容器类。当然, HashMap也有很多特性值得思考,如JDK7中并发环境循环引用问题, 链表转红黑树的性能提升, hash算法过程的图解等等,后面也会继续拓展补充。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
学会扒源码-HashMap
好久不见,最近我要复习了,随时准备面试,顺便整理笔记,所以这又是一篇没有感情的纯物理输出!!!
王炸
2020/06/09
3240
面试官:来,问你几个关于HashMap的问题?
最简单形式的 hash,是一种在对任何变量/对象的属性应用任何公式/算法后, 为其分配唯一代码的方法。
用户5546570
2019/06/06
9630
面试官:来,问你几个关于HashMap的问题?
HashMap的底层实现-JDK1.8之后
在JDK 1.8及之后的版本中,HashMap的底层实现进行了优化,主要改进了处理哈希冲突的方式。具体来说,当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以提高查找效率。这种优化使得HashMap在处理大量哈希冲突时性能更好。
代码小李
2025/01/30
1200
面试必备:HashMap源码解析(JDK8)
在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也可以使用红黑树进行存储,总之,目标只有一个,那就是在安全和功能性完备的情况下让其速度更快,提升性能。好~下面就开始分析源码。
搜云库技术团队
2019/10/17
4700
Java HashMap源码浅析
  之前虽然很频繁使用java的hashmap,但一直都是纯用,至于里面怎么实现的,一直都是糊里糊涂的。今年4月份跳槽找工作,大概看了一下HashMap的源码,在面试过程中也被多位面试官问到HashMap的相关问题,有些问题也没回答出来。本来几个月前就想着写一篇相关源码解析的博客(主要是加深自己的理解),一直拖到现在,接下来就跟我一起看下HashMap的实现(基于jdk8)。
xindoo
2021/01/22
4120
Java HashMap源码浅析
HashMap详解
**Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。**这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
程序员阿杜
2023/08/25
2940
Java 集合深入理解(16):HashMap 主要特点和关键方法源码解读
前面我们介绍了 哈希相关概念:哈希 哈希函数 冲突解决 哈希表,这篇文章我们来根据 JDK 1.8 源码,深入了解下使用频率很高的 HashMap 。 读完本文你将了解到: 点击查看 Java 集
张拭心 shixinzhang
2018/01/05
1.6K0
Java 集合深入理解(16):HashMap 主要特点和关键方法源码解读
高级架构进阶之HashMap源码就该这么学
“当然用过,HashMap是一种<key,value>的存储结构,能够快速将key的数据put方式存储起来,然后很快的通过get取出来”,然后说“HashMap不是线程安全的, 答:
技术zhai
2019/02/15
1.2K0
面试中最长常问到的 HashMap,你都知道多少?
推荐阅读:https://zhuanlan.zhihu.com/p/31610616
村雨遥
2020/08/13
3660
一文解读JDK8中HashMap的源码
HashMap是平时开发中非常常用的一个集合框架类,了解其内部构造及运行原理可以说是每一个Java程序员必不可少的,接下来就从源码一探究竟HashMap到底是什么样的类。
beifengtz
2019/08/01
9180
一文解读JDK8中HashMap的源码
HashMap源码解析
带 容量 和 负载因子的参数,分别对两个参数做 范围判断,然后赋值给loadFactor和threshold。 通过tableSizeFor(int cap)对传入的 容量 取 大于等于 该值 最小的2的指数幂。
103style
2022/12/19
3260
Java集合源码分析(四)HashMap
一、HashMap简介 1.1、HashMap概述   HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射。此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能。   在API中给出了相应的定义: //1、哈希表基于map接口的实现,这个实现提供了map所有的操作,并且提供了key和value可以为null,(HashMap和HashTable大致上是一样的除了hashmap是异步的和允许key和value为n
用户1195962
2018/01/18
9320
Java集合源码分析(四)HashMap
HashMap 源码分析
HashMap继承AbstractMap<K,V>,实现了Map,Cloneable, Serializable。如下:
Jacob丶
2020/08/05
5990
集合系列 Map(十二):HashMap
HashMap 是 Map 基于哈希散列算法的实现,其在 JDK1.7 中采用了数组+链表的数据结构。在 JDK1.8 中为了提高查询效率,采用了数组+链表+红黑树的数据结构。本文所有讲解均基于 JDK1.8 进行讲解。
陈树义
2019/08/29
4690
集合系列 Map(十二):HashMap
HashMap实现原理和源码详细分析
HashMap 基于哈希表的 Map 接口实现,是以 key-value 存储形式存在 ,HashMap 的实现不是同步的,这意味着它不是线程安全的。它的 key、value 都可以为 null,此外,HashMap 中的映射不是有序的。
SmileNicky
2021/09/08
4580
面试 | Java8 HashMap原理
基于Map接口实现、允许null键/值、非同步、不保证有序(比如插入的顺序)、也不保证顺序不随时间变化。
Spark学习技巧
2018/12/24
6220
Java的Hashmap
HashMap是什么,估计学Java的人都懂。那我就不啰嗦了,本文主要是基于Java8,下面主要以下几个方面学习一下:1)HashMap的数据结构、负载因子 2)HashMap的put和get方法 3)HashMap的碰撞问题 4)HashMap的扩容、Rehash
用户3467126
2019/07/19
4700
帮你面试——HashMap
这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没有指定JDK版本,很多文章都是旧版本JDK1.6.JDK1.7的。现在我来分析一哈最新的JDK1.8的HashMap及性能优化。
小忽悠
2018/12/19
3960
JDK1.8源码分析之HashMap
HashMap是一种Map,HashMap仅是一种Map的实现版本,下面的图片展示了java中Map的一些实现版本:
九州暮云
2019/08/21
3690
Java源码阅读之HashMap - JDK1.8
HashMap的实际负责K,V存储的是transient Node<K,V>[] table,而这里的Node<K,V>则是HashMap的一个静态内部类,如下
格子Lin
2018/09/30
5020
相关推荐
学会扒源码-HashMap
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验