Hash Map 存储原理  (图解比较详细)

  调用hashcode方法算出哈希码,然后再算出在主数组中对应的存储位置,将key value 、哈希码 、下一个元素的地址  这四个数据存储在一个Entry类的对象中,这样以来,主数组就成为了一个Entry的数组。

  每一个数组元素都是由链表组成的,链表头部的头节点只存储下一个链表元素的地址,

  哈希冲突(哈希碰撞):两个键值对根据key计算出的哈希值是相同的,也就意味着他们要存入同一个位置,分为两种情况:

     1.key 完全一样,比如(12,"丽丽")    (12,"明明")    先插入丽丽 ,后插入明明,这时候  明明 就会把 丽丽  替换掉,最后得到的结果是(12,"明明"),但是  12  是第一个插入的键值对的12。

             2.key不一样,但是计算出的值 是相等的,最后也要存储到同一个位置,这是将后插入的元素方法头节点的后面,而头节点存储的地址也要换成后插入节点的地址,后插入节点的地址            

       下一个元素的地址也就只想原来的节点(元素和节点理解为同一概念,知识为了便于分辨过程中不同元素)

        其实jdk 1.7 和jdk 1.8 插入方式是不一样的,遵循 “7  上  8  下”  的原则,1.7是在前面插入(头插法),而1.8 是在后面插入(尾插法)

  最底层的原理 是由哈希表组成 ,而 哈希表  =  数组  +   链表

  HashMap的父类AbstractMap已经实现了Map接口,但是在HashMap中又再一次地实现了类Map接口,这个操作就是一个多余地操作,包括list也是,本来是作者想对做一些事,但是最后没做

                 

    重要属性 : 

     //HashMap的父类AbstractMap已经实现类Map接口,但是源码中又单独实现了Map接口//这个操作就是一个多余的操作-->集合的创作者承认了
     public class HashMap<K,V>
     extends AbstractMap<K,V>
     implements Map<K,V>,Cloneable, serializable{//重要属性:
     static final int DEFAUIT_INITIAL_CAPACITY = 16;//定义了一个16,一会要赋给数组的长度static final int MAXIMUM _CAPACITY= 1 <<30;//定义了一个很大很大的数
     static final float DEFAULT_LOAD_FACTOR = 0.75f;//定义了一个值:0.75负载因子,加载因子transient Entry<K,V>[]table;//底层主数组
     transient int size;//添加的元素的数量
     int threshold;//定义个变量,没赋值默认为0 ,-->这个变量是用来表示数组扩容的边界值,门槛值final float loadFactor;//这个变量用来接收:装填因子,负载因子,加载因子

          

                               

                            

 

    put方法

    在put()方法添加元素地时候,计算出哈希码,然后在进行二次散列,才是最后的位置,在数组中的位置,底层计算位置的公式都是&运算,相当于对长度length取余数,但是用&位比较的方式效率高于直接取余,但是length 必须书  2的n次幂,所以底层才要求length 是2的n次幂。

 

JDK 1.8 存储原理

    jdk1.8与1.7 的hashmap实现还是有一些差别的,但是表层的逻辑理解上差别并不是很大。8在7 的基础上,二次散列由四次变为一次,将头插法改为尾插法,为了更好的遍历链表,而且在链表长度大于8的时候会调用treeifbin()方法,来判断数组长度是否大于64,如果大于64,就会将链表树化,变为红黑树,而当删减红黑树中的元素的时候,删除到书的元素小于6的时候,就会将红黑树链化,变为链表。

    在元素方面,使用了Node节点并且实现了Map.Entry<K , V >的接口,所以元素不再是Entry,而是Node 

 

 

 

 

 

 

 

 

 

 

    

 

posted on 2022-04-16 11:03  迎新  阅读(24)  评论(0编辑  收藏  举报