HashMap 的原理

简介: HashMap 的原理

一直使用HashMap,但是 你知道他的实现原理吗?


HashMap的存储结构:

首先 看一张图:


20190212181743594.jpg


hashmap 的存储结构其实就是 数组加链表的形式来存放数据,这个数组的类型就是Entry。map里面的所有内容都保存在Entry里面。每个数组的元素其实就是一个链表,当哈希值重复的时候,就会将数据存到链表中。形象一点说就是 有好多个桶(数组的元素),当你需要存放一个键值对的时候,就会通过键去拿到对应的哈希值,然后将数据放在和哈希值对应的桶(数组的下标)里面,这个桶里面的存放形式是链表。


HashMap的put方法:
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }


static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }


可以看到在hashmap保存值的时候,首先会调用hash方法,判断key是否为空,如果为空返回0,否在继续调用hashCode方法获取这个key的哈希值,最后返回。获取到哈希值后,会调用putVal方法,将要保存的值按照哈希值存放在对应的数组中,(注意,保存的不仅仅是value,HashMap存储着Entry(hash, key, value, next)对象。)。hashmap可以允许key和value为null,当key为null的时候,他的哈希值就为0,保存在数组的第一个位置上。


如果put的时候计算出来key的哈希值有重复怎么办:

上面说存储结构的时候说过,有一个数组,数组的每一个元素都是一个链表。当通过key计算出来的哈希值有重复,还是会找到对应的桶(数组的下标),然后和桶中的元素进行判断,如果两个key相同,则覆盖原来的值,否则 将值插入到链表中。每次插入的元素位于链表的最前面。


hashmap的get方法:
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    /**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((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;
    }


get方法会根据对应的key 然后计算出来哈希值,然后找到对应的数组元素,然后遍历链表,取出和key 相同的值,找不到则返回null。


hashmap 的 resize


当hashmap中的元素越来越多的时候,key的哈希值相同的就会越来越多(因为桶是固定的),所以为了查询的效率,就要对hashmap进行扩容,扩容也是最耗性能的,在扩容的时候,会创建一个是原来数组二倍的数组,然后将原来数组中的元素必须重新计算在新数组中的位置,然后存放在新数组对应的下标中。那么hashmap什么时候扩容呢,当hashmap中的元素个数超过数组大小为loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,16*0.75= 12,当hashmap中的元素超过12的时候,就会对数组进行扩容。因为这样是一个非常耗性能的操作,所以在使用hashmap的时候,需要对存入的元素有一个 估计,然后通过hashmap的构造方法指定数组的长度,比如new HashMap(1024);


总结:


利用key的hashcode计算出哈希值,也就是当前元素位于数组的什么地方

存储时,当key的哈希值相同,有两种情况 1,key相同,覆盖掉原来的数据,2,key不相同(出现碰撞),将当前的数据存放在链表中。

读取时,还是获取key的哈希值,找到数组中对应的下标,然后遍历链表,返回key对应的值。

理解了以上就不难明白hashmap 是怎么解决哈希值冲突的问题。原理就是运用了数组和链表。当哈希值冲突时,就会在冲突的那个下标中 比较key是否重复(数组中每个元素就是一个链表),重复侧覆盖值,不重复则为冲突,添加进链表。


相关文章
|
7月前
|
存储 缓存 安全
面试题-HashMap底层原理与HashTable的区别
字节跳动面试题-HashMap底层原理与HashTable的区别
73 0
|
7月前
|
存储 算法 Java
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
在阅读了上篇文章《【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)》之后,相信您对HashMap的基本原理和基础结构已经有了初步的认识。接下来,我们将进一步深入探索HashMap的源码,揭示其深层次的技术细节。通过这次解析,您将更深入地理解HashMap的工作原理,掌握其核心实现。
65 0
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
|
存储 算法 安全
HashMap底层实现原理
HashMap底层实现原理
168 0
|
9天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
48 13
|
1月前
HashMap原理
1.HashMap在Jdk1.8以后是基于数组+链表+红黑树来实现的,特点是,key不能重复,可以为null,线程不安全 2.HashMap的扩容机制: HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树
32 2
|
1月前
|
存储 Java 索引
HashMap原理详解,包括底层原理
【11月更文挑战第14天】本文介绍了数据结构基础,重点讲解了哈希表的概念及其实现方式,包括数组与链表的特点及其在HashMap中的应用。详细分析了Java 7及Java 8版本中HashMap的底层存储结构,特别是Java 8中引入的红黑树优化。此外,还探讨了哈希函数的设计、哈希冲突的解决策略以及HashMap的重要方法实现原理,如put、get和remove方法,最后讨论了HashMap的容量与扩容机制。
|
2月前
|
机器学习/深度学习 算法
让星星⭐月亮告诉你,HashMap之tableSizeFor(int cap)方法原理详解(分2的n次幂和非2的n次幂两种情况讨论)
`HashMap` 的 `tableSizeFor(int cap)` 方法用于计算一个大于或等于给定容量 `cap` 的最小的 2 的幂次方值。该方法通过一系列的无符号右移和按位或运算,逐步将二进制数的高位全部置为 1,最后加 1 得到所需的 2 的幂次方值。具体步骤包括: 1. 将 `cap` 减 1,确保已经是 2 的幂次方的值直接返回。 2. 通过多次无符号右移和按位或运算,将最高位 1 后面的所有位都置为 1。 3. 最终加 1,确保返回值为 2 的幂次方。 该方法保证了 `HashMap` 的数组容量始终是 2 的幂次方,从而优化了哈希表的性能。
34 1
|
3月前
|
设计模式 安全 Java
HashMap底层原理:数据结构+put()流程+2的n次方+死循环+数据覆盖问题
假如有T1、T2两个线程同时对某链表扩容,他们都标记头结点和第二个结点,此时T2阻塞,T1执行完扩容后链表结点顺序反过来,此时T2恢复运行再进行翻转就会产生环形链表,即B.next=A;采用2的指数进行扩容,是为了利用位运算,提高扩容运算的效率。JDK8中,HashMap采用尾插法,扩容时链表节点位置不会翻转,解决了扩容死循环问题,但是性能差了一点,因为要遍历链表再查到尾部。例如15(即2^4-1)的二进制为1111,31的二进制为11111,63的二进制为111111,127的二进制为1111111。
HashMap底层原理:数据结构+put()流程+2的n次方+死循环+数据覆盖问题
|
7月前
|
Java
HashMap原理解析
HashMap原理解析
170 47