在[深入浅出集合Map]中,已讲述了HashMap在jdk7中实现,在此就不再细说了

JDK7中的HashMap

基于链表+数组实现,底层维护一个Entry数组

Entry<K,V>[] table;

根据计算的hashCode将对应的KV键值对存储到该table中,一旦发生hashCode冲突,那么就会将该KV键值对放到对应的已有元素的后面, 此时,形成了一个链表式的存储结构,如下图

JDK8中的HashMap

基于位桶+链表/红黑树的方式实现,底层维护一个Node数组

Node<K,V>[] table;

在JDK7中HashMap,当成百上千个节点在hash时发生碰撞,存储一个链表中,那么如果要查找其中一个节点,那就不可避免的花费O(N)的查找时间,这将是多么大的性能损失,这个问题终于在JDK8中得到了解决。

JDK8中,HashMap采用的是位桶+链表/红黑树的方式,当链表的存储的数据个数大于等于8的时候,不再采用链表存储,而采用了红黑树存储结构。这是JDK7与JDK8中HashMap实现的最大区别。

如下图所示:

这么做主要是再查询的时间复杂度上进行优化,链表为O(n),而红黑树一直是O(logn),冲突(即为相同的hash值存储的元素个数) 超过8个,可以大大的提高查找性能。

其他异同

共同点

1.容量(capacity):容量为底层数组的长度,JDK7中为Entry<k,v style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box !important; font-size: inherit; color: inherit; line-height: inherit;”>数组,JDK8中为Node<k,v style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box !important; font-size: inherit; color: inherit; line-height: inherit;”>数组

a. 容量一定为2的次幂</k,v></k,v>

static int indexFor(int h, int length) {      return h & (length-1);  }  

这段代码是用来计算出键值对存放在一个数组的索引,h是int hash = hash(key.hashCode())计算出来的,SUN大师们发现, “当容量一定是2^n时,h & (length - 1) == h % length” ,按位运算特别快 。

源码中大量使用运算,对于计算机,位运算计算效率特别快,毕竟二进制才是亲儿子呀

b. 默认初始容量16(容量为低层数组的长度,JDK7中为Entry<k,v style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box !important; font-size: inherit; color: inherit; line-height: inherit;”>数组,JDK8中为Node<k,v style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box !important; font-size: inherit; color: inherit; line-height: inherit;”>数组)</k,v></k,v>

c.最大容量1<<30,即2的30次方

1 << 30 = 10737418241 << 31 = -21474836481 << 32 = 11 << 33 = 21 << -1 = -2147483648

hashmap的“最大容量“其实是Integer.MAX_VALUE

2.加载因子(Load factor):HashMap在其容量自动增加前可达到多满的一种尺度

a.  默认加载因子 = 0.75

static final float DEFAULT_LOAD_FACTOR = 0.75f 
  • 加载因子越大、填满的元素越多 = 空间利用率高、但冲突的机会加大、查找效率变低(因为链表变长了)

  • 加载因子越小、填满的元素越少 = 空间利用率小、冲突的机会减小、查找效率高(链表不长)

    0.75是一个"冲突的机会"与"空间利用率"之间寻找一种平衡与折衷的选择

3.扩容机制:扩容时resize(2 * table.length),扩容到原数组长度的2倍。

4.key为null:若key == null,则hash(key) = 0,则将该键-值 存放到数组table 中的第1个位置,即table [0]

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

1.发生hash冲突时 **JDK7:**发生hash冲突时,新元素插入到链表头中,即新元素总是添加到数组中,就元素移动到链表中。

**JDK8:**发生hash冲突后,会优先判断该节点的数据结构式是红黑树还是链表,如果是红黑树,则在红黑树中插入数据;如果是链表,则将数据插入到链表的尾部并判断链表长度是否大于8,如果大于8要转成红黑树。

2.扩容时 **JDK7:**在扩容resize()过程中,采用单链表的头插入方式,在将旧数组上的数据 转移到 新数组上时,转移操作 = 按旧链表的正序遍历链表、在新链表的头部依次插入,即在转移数据、扩容后,容易出现链表逆序的情况 。

多线程下resize()容易出现死循环。此时若(多线程)并发执行 put()操作,一旦出现扩容情况,则 容易出现 环形链表,从而在获取数据、遍历链表时 形成死循环(Infinite Loop),即 死锁的状态 。

**JDK8:**由于 JDK 1.8 转移数据操作 = 按旧链表的正序遍历链表、在新链表的尾部依次插入,所以不会出现链表 逆序、倒置的情况,故不容易出现环形链表的情况 ,但jdk1.8仍是线程不安全的,因为没有加同步锁保护。

建议: 1.使用时设置初始值,避免多次扩容的性能消耗

2.使用自定义对象作为key时,需要重写hashCode和equals方法

3.多线程下,使用CurrentHashMap代替HashMap

推荐阅读:

「深入浅出」集合Map

javaWeb传收参数方式总结

周末了,笑久一点~

如果觉得不错,请给个「好看」

分享给你的朋友!

THANDKS

  • End -

一个立志成大腿而每天努力奋斗的年轻人

伴学习伴成长,成长之路你并不孤单!

HashMap在JDK7和JDK8中的区别的更多相关文章

  1. JDK7与JDK8中HashMap的实现

    JDK7中的HashMap HashMap底层维护一个数组,数组中的每一项都是一个Entry transient Entry<K,V>[] table; 我们向 HashMap 中所放置的 ...

  2. HashMap:JDK7 与 JDK8 的实现

    JDK7中的HashMap HashMap底层维护一个数组,数组中的每一项都是一个Entry: transient Entry<K,V>[] table; 我们向在HashMap 中存放的 ...

  3. jdk7中hashmap实现原理和jdk8中hashmap的改进方法总结

    1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二分查找时间复杂度小,为O(1 ...

  4. HashMap 在 Java1.7 与 1.8 中的区别

    hashMap 数据结构 如上图所示,JDK7之前hashmap又叫散列链表:基于一个数组以及多个链表的实现,hash值冲突的时候,就将对应节点以链表的形式存储. JDK8中,当同一个hash值(Ta ...

  5. jdk7和8中关于HashMap和concurrentHashMap的扩容过程总结,以及HashMap死循环

    题外话:为什么要hashcode进行spread? 充分使用key.hashCode()的高16位信息,保证hash分布更分散, 扩容操作是新建2倍于原表大小的新表,并将原表结点拷贝一份放在新表中,对 ...

  6. 深入分析 JDK8 中 HashMap 的原理、实现和优化

    HashMap 可以说是使用频率最高的处理键值映射的数据结构,它不保证插入顺序,允许插入 null 的键和值.本文采用 JDK8 中的源码,深入分析 HashMap 的原理.实现和优化.首发于微信公众 ...

  7. 2、JDK8中的HashMap实现原理及源码分析

    本篇提纲.png 本篇所述源码基于JDK1.8.0_121 在写上一篇线性表的文章的时候,笔者看的是Android源码中support24中的Java代码,当时发现这个ArrayList和Linked ...

  8. JDK7中匿名内部类中使用局部变量要加final,JDK8中不需要,但jdk会默认加上final

    今天看书的时候看到了局部内部类,书上说局部内部类可以访问局部变量,但是必须是final的.因为局部变量在方法调用之后就消失了,使用final声明的话该局部变量会存入堆中,和内部类有一样的声明周期.但是 ...

  9. JDK8中的HashMap实现原理及源码分析

    大纲 一.什么是Hash?什么是HashMap? 二.HashMap的内部实现机制 1.HashMap基本元素 ①DEFAULT_INITIAL_CAPACITY&MAXIMUM_CAPACI ...

随机推荐

  1. linux内核指针和错误值

    很多内部内核函数返回一个指针值给调用者. 许多这些函数也可能失败. 大部分情况, 失 败由返回一个 NULL 指针值来指示. 这个技术是能用的, 但是它不能通知问题的确切特性. 一些接口确实需要返回一 ...

  2. Common Logging包装设计

    类设计 LogFactory根据当前环境加载具体的Log实现: 1.从缓存中加载LogFactory 2.从系统属性org.apache.commons.logging.LogFactory 中加载L ...

  3. git常用常用操作指令

    GIT操作 1:git init 初始化空的仓库,会在当前文件夹生成一个隐藏.git的文件夹,相当于一个仓库. 2:提交代码的流程:工作代码区-->暂存区 -->主仓库 -->服务器 ...

  4. [板子]SPFA算法+链式前向星实现最短路及负权最短路

    参考:https://blog.csdn.net/xunalove/article/details/70045815 有关SPFA的介绍就掠过了吧,不是很赞同一些博主说是国内某人最先提出来,Bellm ...

  5. 学习Java第四周

    复习数组,数组和方法在内存中是怎样存储这个问题,有些一知半解. 复习了面向对象思想,是面向对象思想,类的定义,对象实例化,构造函数,还有用javabean的格式定义类.面向过程是自己解决问题,面向对象 ...

  6. HDU 2102 A计划 DFS与BFS两种写法 [搜索]

    1.题意:一位公主被困在迷宫里,一位勇士前去营救,迷宫为两层,规模为N*M,迷宫入口为(0,0,0),公主的位置用'P'标记:迷宫内,'.'表示空地,'*'表示墙,特殊的,'#'表示时空传输机,走到这 ...

  7. IPv4数据报格式及其语义

    一.IP数据报的格式如下图所示 版本 首部长度 服务类型 数据报长度 16比特标识 标志 13比特片偏移 寿命 上层协议 首部检验和 32比特源IP地址 32比特目的IP地址 选项(如果有的话) 数据 ...

  8. pytorch代码调试工具

    https://github.com/zasdfgbnm/TorchSnooper pip install torchsnooper 在函数前加装饰器@torchsnooper.snoop()

  9. TestStand 基本设置

    1. 过程模型设置 菜单->Configure->Station Options->Model TestStand 默认提供了三种过程模型 Sequential.Batch.Para ...

  10. DRF框架中的异常处理程序

    目录 DRF框架中自定义异常处理 一.自定义异常的原因 二.如何设置处理异常的程序 DRF框架中自定义异常处理 一.自定义异常的原因 在Django和DRF框架中都封装了很多的处理异常的程序,可以处理 ...