Jdk1.7下的hashmap详解

 

目录

数据结构:

原理说明:

加链表的好处:

加链表的坏处:

数组+链表的结构图

链表的原理

jdk1.7 链表头插法.

方法解析

重要变量说明

size    

loadFactor

threshold

modCount(了解)

put方法

代码解析

get方法

代码解析

resize方法

方法流程:

存在的问题. (jdk1.7的resize问题) 

总结:


数据结构:

原理说明:

jdk1.7的hashmap的底层结构是数组加单向链表实现的.将key的hash值进行取模获取index既即将存放的元素的数组的位置.然后到对应的链表中进行put和get操作.

加链表的好处:

因为对数组进行取模的时候可能会遇到获取index的位置是一样的,所以可能会遇到hash碰撞冲突.加上链表可以减少冲突.

加链表的坏处:

(这样不好,效率不高,每次都得遍历.时间复杂度为o(n),所以jdk1.8做了优化改成了链表加红黑树,时间复杂度从o(n)变为了logn,40亿数据检索只需要四五十次查询.就可以具体到某数据)   

 

数组+链表的结构图

下面是HashMapJDK1.6JDK1.7的底层结构组+单向链表的结构图

image.png

数组(绿色):hash数组(桶),数组元素是每个链表的头节点

链表(浅蓝色):解决hash冲突,不同的key映射到了数组的同一索引处,则形成链表。

 

 

链表的原理

说明:

对一个链表entry对象来说,包含了节点所对应的key和value对儿,还包含了节点的下个节点的指针next.

链表尾插法和头插法:

加入一个节点.想将新的节点加在链表的头部,此时需要将新节点的next指针指向原先链表的头结点.这样始终就保持头结点就是最新插入的数据.这是头插法

加入一个节点,想将旧的节点加载链表的尾部,此时需要将链表尾节点的next指针指向新节点.这样就可以保证尾节点始终是最新插入的数据.这就是尾插法.

下面是单向链表的对象代码.

   static 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;
        }
    }

 

jdk1.7 链表头插法.

Put的方法里面的这行代码table[bucketIndex] = new Entry<>(hash, key, value, e);   就是上面头插法的一个方式

既在new一个entry节点的时候将老节点作为新节点的下一个节点.然后赋值给新节点.始终保证头结点的数据是新插入的数据.

 

 void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
}

 

方法解析

重要变量说明

size    

集合map的元素数量

loadFactor

负载因子,默认是0.75,可以通过构造函数进行修改,不建议修改.该值是经过数学运算得出的最适合扩容的一个值.

为什么负载因子的大小默认为0.75而不是1呢.

这涉及到一个数学运算.

根据HashMap的扩容机制,他会保证capacity的值永远都是2的幂。

那么,为了保证负载因子(loadFactor) * 容量(capacity)的结果是一个整数,这个值是0.75(3/4)比较合理,因为这个数和任何2的幂乘积结果都是整数。

如果我们把负载因子设置成1,容量使用默认初始值16,那么表示一个HashMap需要在"满了"之后才会进行扩容,显然会发生更多的hash碰撞.

总结:负载因子默认为0.75是最适合减少hash碰撞又能保证 loadFactor*capacity的结果是一个整数的一个值

threshold

集合的容量,初始构造方法可传入,如果不传,默认会构造成16.

 

在第一次的put的时候会重新赋值为下次扩容的容量临界值. 比如初始数组大小为16 ,负载因子为0.75,那么扩容临界值就为12.

此时该值变为16

 

modCount(了解)

简而言之就是为了记录hashmap的结构修改的次数(rehash的次数).具体用处在hashmap中用不到,但是在其他地方用的到.跟一个异常有关系ConcurentModificationException.该异常一般在迭代器遍历的时候有用处.任何一个拥有迭代器的集合都会在用迭代器遍历时出现并发情况下的线程并发修改集合的时候抛出该异常.

迭代器在初始化时会将迭代器中的modCount指向集合中的modCount,那么在遍历迭代器的时候每次都会去进行比较迭代器中的modCount和集合中的modCount是否一致,不一致就抛出异常(代表其他线程对集合进行修改了.此时集合的modCount跟迭代器中的是不一样的.).

 

put方法

 

观察hashmap源码的put方法 

  public V put(K key, V value) {
        //1
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值