1.在上篇HashMap博客中,已经提到jdk7使用的是数组+链表来实现,而jdk8使用数组+链表+红黑树实现,那为什么要引入红黑树呢,原因就是哪怕将hash的碰撞降到最低,也不能避免链表会越来越长。

[1].链表属于插入快,遍历慢的数据结构

[2].完全二叉树插入慢,遍历快

[3].红黑树插入和查询都较快

[4].jdk7中的put方法

public V put(K key, V value) {// HashMap允许存放null键和null值。// 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。if (key == null)return putForNullKey(value);// 根据key的keyCode重新计算hash值。int hash = hash(key.hashCode());// 搜索指定hash值在对应table中的索引。int i = indexFor(hash, table.length);// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}// 如果i索引处的Entry为null,表明此处还没有Entry。modCount++;// 将key、value添加到i索引处。addEntry(hash, key, value, i);return null;}

[5].jdk8中的put方法

     public V put(K key, V value) {return putVal(hash(key), key, value, false,  true);}/*** Implements Map.put and related methods** @param hash*            hash for key* @param key*            the key* @param value*            the value to put* @param onlyIfAbsent*            if true, don't change existing value* @param evict*            if false, the table is in creation  mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean  onlyIfAbsent, boolean evict) {Node<K, V>[] tab;Node<K, V> p;int n, i;// 如果当前map中无数据,执行resize方法。并且返回nif ((tab = table) == null || (n = tab.length) ==  0)n = (tab = resize()).length;// 如果要插入的键值对要存放的这个位置刚好没有元素,那么把他封装成Node对象,放在这个位置上就完事了if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);// 否则的话,说明这上面有元素else {Node<K, V> e;K k;// 如果这个元素的key与要插入的一样,那么就替换一下,也完事。if (p.hash == hash && ((k = p.key) == key  || (key != null && key.equals(k))))e = p;// 1.如果当前节点是TreeNode类型的数据,执行putTreeVal方法else if (p instanceof TreeNode)e = ((TreeNode<K, V>)  p).putTreeVal(this, tab, hash, key, value);else {// 还是遍历这条链子上的数据,跟jdk7没什么区别for (int binCount = 0;; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key,  value, null);// 2.完成了操作后多做了一件事情,判断,并且可能执行treeifyBin方法if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash && ((k =  e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for  keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)  // true || --e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;// 判断阈值,决定是否扩容if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

2.hash算法简化

[1].jdk7的hash算法

    final int hash(Object k) {int h = hashSeed;if (0 != h && k instanceof String) {return sun.misc.Hashing.stringHash32((String)  k);}h ^= k.hashCode();// This function ensures that hashCodes that  differ only by// constant multiples at each bit position have a  bounded// number of collisions (approximately 8 at  default load factor).h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);}

[2].jdk8中的hash算法

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

[3].为什么在jdk8中要简化hash算法:jdk8之前之所以hash方法写的比较复杂,主要是为了提高散列行,进而提高遍历速度,但是jdk8以后引入红黑树后大大提高了遍历速度,继续采用复杂的hash算法也就没太大意义,反而还要消耗性能,因为不管是put()还是get()都需要调用hash()

3.新节点插入顺序:jdk7在头部插入,jdk8在尾部插入

[1].这里需要提一下两个jdk8新增的属性TREEIFY_THRESHOLD是树化阀值,TREEIFY_THRESHOLD是链表化阀值,当链表上的数据大于等于树化阀值,链表转化为红黑树,反之小于等于链表化阀值时转为链表

    static final int TREEIFY_THRESHOLD = 8;/*** The bin count threshold for untreeifying  a (split) bin during a* resize operation. Should be less than  TREEIFY_THRESHOLD, and at* most 6 to mesh with shrinkage detection  under removal.*/static final int UNTREEIFY_THRESHOLD= 6;

[2].因为并没有对每个链表(红黑树)进行记录元素个数,所以每次都是通过遍历得来的元素个数,所以在遍历完后就顺带插入到尾部

4.扩容机制

[1].jdk7中的扩容

    void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}Entry[] newTable = new Entry[newCapacity];transfer(newTable,  initHashSeedAsNeeded(newCapacity));table = newTable;threshold = (int)Math.min(newCapacity *  loadFactor, MAXIMUM_CAPACITY + 1);}/*** Transfers all entries from current table to  newTable.*/void transfer(Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;for (Entry<K,V> e : table) {while(null != e) {Entry<K,V> next = e.next;if (rehash) {e.hash = null == e.key ? 0 :  hash(e.key);}int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;}}}

[2].核心就是transfer()方法,2个循环

①.对索引数组中的元素遍历

②.对链表上的每一个节点遍历:用 next 取得要转移那个元素的下一个,将 e 转移到新 Hash 表的头部,使用头插法插入节点(所以元素的位置在链表中的物理体现是跟之前反着的)。

③.循环2,直到链表节点全部转移

④.循环1,直到所有索引数组全部转移

[3].问题:在多线程下当2个线程操作统一条链表就有可能出现链表闭合

①.当两个线程调用put()方法,且都满足扩容的条件(new了两个新数组),其中一条链表是3→5

②.线程一进到transfer(),把 Entry<K,V> next = e.next;执行完就挂起了,当前e记录的是3,next是5

③.线程二进到这个方法将扩容完成,此时由于jdk7的扩容后链表会反转,所以是5→3

④.线程一被唤醒,将e(3)插到线程一的某个链表的表头,把next(5)值赋给e

⑤.由于5的下个节点是3,故把3赋给next,将e(5)插到线程一对应链表的表头,现在的指向是3→5,把next(3)赋给e

⑥.e的next为null(不重要),把e(3)再指放到链表的表头,把3指向5,此时,闭环就形成了,3的next是5,5的next也是3

[4].由于jdk8是顺序拷贝,所以就不会产生jdk7的死锁

[5].jdk7扩容后hash值会发生变化,因为hashSeed值随着容量增长,,而jdk8根据该扩容长度调整存元素放位置

    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) // initial capacity  was placed in thresholdnewCap = oldThr;else {               // zero initial  threshold signifies using defaultsnewCap = 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)newTab[e.hash & (newCap -  1)] = e;else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j,  oldCap);else { // preserve orderNode<K,V> loHead = null,  loTail = null;Node<K,V> hiHead = null,  hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) ==  0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) !=  null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] =  hiHead;}}}}}return newTab;}

jdk7和jdk8HashMap主要的区别相关推荐

  1. Java之HashMap系列--JDK7与JDK8的HashMap的区别

    原文网址:Java之HashMap系列--JDK7与JDK8的HashMap的区别_IT利刃出鞘的博客-CSDN博客 简介 本文介绍JDK7与JDK8的HashMap的区别. JDK7与JDK8的Ha ...

  2. HashMap的31连环炮,我倒在第5个上

    写在前面 在面试中,HashMap基本必问,只是问法各有不同而已.曾经我也和很多面试官聊过关于HashMap的话题,使用HashMap就能考察面试者的很多知识点.不幸的是,很大部分人都拜倒在HashM ...

  3. 苦修月余,斩获bigo、腾讯offer,面经奉上!

    2020年已经接近尾声了,跳槽的季节又来了,刚好,最近有好几个读者拿到了腾讯.阿里大厂的offer,在我厚颜无耻的追问之下,他们终于给我透露出了面试题的细节,这份热乎乎.滚滚烫的面经分享给大家,希望对 ...

  4. java 中向文本写入和读取hashmap_就靠这一篇HashMap的讲解,我与头条面试官聊了一个小时。...

    预备知识 位运算知识(更多资料私信"学习"免费获取) 位运算操作是由处理器支持的底层操作,底层硬件只支持01这样的数字,因此位运算运行速度很快.尽管现代计算机处理器拥有了更长的指令 ...

  5. Java集合篇:HashMap 与 ConcurrentHashMap 原理总结

    一.HashMap原理总结: 1.什么是HashMap: (1)HashMap 是基于 Map 接口的非同步实现,线程不安全,是为了快速存取而设计的:它采用 key-value 键值对的形式存放元素( ...

  6. 阿里技术官最新总结一份105道Java面试题小册,看完我惊呆了

    话不多说,直接上题: 一.Java基础 1.什么是面向对象? 2.JDK JRE JVM 3.==和equals比较 4.hashCode与equals 5.final 6.String.String ...

  7. 关于多线程的几个问题

    本文来说下关于多线程的几个问题 文章目录 HashMap ConcurrentHashMap JDK7 JDK8 并发基础知识 进程跟线程 并行跟并发 线程几个状态 阻塞与等待的区别 yield 跟 ...

  8. Java基础 (适合新手入门保姆级)

    基础1 1.进制的转换 1. 十进制数据转成二进制数据:使用除以2获取余数的方式 2. 二进制(0B/b开头)转换为十进制:1001 = 1*2^0+0*2^1+0*2^2 +1*2^3 = 9 3. ...

  9. 靠一个HashMap的讲解打动了头条面试官,我的秘诀是

    最近收集了一份github标星81.6k的Java面试突击手册,文末查看 关注 转发+转发+转发 私信回复关键词 [学习]即可获取~ 预备知识 位运算知识 位运算操作是由处理器支持的底层操作,底层硬件 ...

最新文章

  1. c++ 将输入存储到数组,然后反转数组,最后输出
  2. Linux三剑客之grep 与 egrep
  3. C++ - 类模板(class template) 详解 及 代码
  4. 递归——外星密码(洛谷 P1928)
  5. C++引用和指针区别
  6. Google 包庇 Android 之父还给了 9000 万美元,女工程师们怒了!
  7. [PBRT-V3]代码中的#define(持续更新)
  8. 修改mysql连接回收时间_Druid无效链接回收策略(源码分析)(mysql 8小时连接失效问题)...
  9. CUMT矿大----电路与数字系统实验一 一位全减器
  10. javascript语法
  11. 转账功能怎么测试?以支付宝转账到银行卡为例
  12. simulink反差表
  13. 这个好用的办公网优化工具,官宣免费了
  14. android 4.4 蓝牙开发总结(电视盒子)
  15. 想去阿里大厂去面试测试工程师?想月薪15k?这篇文章一定对你有所帮助
  16. 【JavaMap接口】HashMap源码解读实例
  17. AutoCAD Civil 3D-纵断面-纵断面与纵断面图
  18. 用Excel,只需30秒就可爬取网站数据
  19. 人工智能基础 | K近邻(三)
  20. [office软件教程] Excel怎么排序数据?Excel数据排序的方法

热门文章

  1. MindMap软件介绍
  2. 实体商家也能玩转月活10亿的微信小程序生态
  3. USART1_IRQHandler 函数
  4. 执行kubectl get csr显示NoT found.
  5. cefsharp实现双屏显示网页(横屏|竖屏)可拖动分隔条
  6. TCP协议与SCTP协议的区别
  7. 智慧风电:数字孪生 3D 风机智能设备运维
  8. Jupyter Lab 密码登录、远程访问
  9. python读取.csv 大文件的解决办法(iterator=true)
  10. 出租车计价C语言程序