0 概述

HashMap是Java程序员使用频率最高的容器之一,主要原因它的查询效率比较高,本文基于JDK1.8,深入探讨HashMap的结构实现和功能原理。

1 HashMap底层数据结构

首先看下HashMap底层数据结构,本质上就是一个数组+链表+红黑树(JDK1.8),数组存放的是一个个链表节点,是采用拉链法解决Hash冲突,如果链表长度过长(大于8),将会转化为红黑树。

 //node 数组transient Node<K,V>[] table;// Node 节点static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey()        { return key; }public final V getValue()      { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}

1 HashMap实现原理

下图给出向HashMap容器中,put一个key-value的过程,首先根据key 计算出其对应的hash值,然后再根据hash值,计算机这个key存放位置(数组下标)。

实现细节:

  • hash(key),通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销,进而减少冲突。
  • index=h & (length-1)获取node 数组具体位置,这个方法比较巧妙,因为HashMap的Node数组大小都是2的n次方,当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。值得说明是可以指定初始化容量,当然如果你知道容量不是2的n次幂,它会用tableSizeFor方法将其处理成2的n次幂。
    //static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}// 寻址index=h & (length-1);/**这个方法就是将容量处理成 2 的n次方,先将cap处理成全是111111(二进制的),然后再加1,非常的巧妙* Returns a power of two size for the given target capacity.*/static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;//n和n右移一位或,高位至少有两个1 (处理后n=00011****)n |= n >>> 2; n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

几个重要的属性

    transient int size;int threshold;final float loadFactor;
  • size 表示hashMap 实际存储了多少对key-valve。
  • threshold,是HashMap所能容纳的最大数据量的Node(键值对)个数,超过就会扩容(数组扩容)。由于threshold = length * Load factor,所以在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。
  • loadFactor 负载因子,默认的负载因子0.75是对空间和时间效率的一个平衡选择。当loadFactor过大(大于1),threshold值也就大于长度,出现碰撞的数据也就越多,查询等效率也就变低,但是空间利用率高;当loadFactor过小(假设为0.1),threshold远小于长度,出现碰撞的数据也就比较低,查询等效率也就变高,但是空间利用率不高。

3 HashMap 扩容

扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,当size大于等于前面提到的这个threshold时候,就需要扩大数组的长度。简单的说,也就是重新分配一个大的数组,将老的数组中数据拷贝到新的数组中去。因此从这里我们可以看到如果我们知道我们需要往HashMap中放多少数据时候,可以直接初始化容量,这样可以有效避免内存拷贝。
由于其扩容(2倍)&以及寻址特点,对于扩容后数据拷贝过程,对应的数组节点数据,要么还在改位置要么会索引到(不会跑到其的位置去):原索引+oldCap(老的容量)。举个例子:初始容量16 一个hash值为10010,其位置010010&01111=00010,可以看到其位置是2,那么扩容后
容量是32,其位置其位置010010&11111=10010(18)可以看到其位置是18=16+2。由此也可以看出扩容数据分配更加均匀。

    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;}//没有超过最大值,那就扩容是原来的2倍(左移1位)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);}//扩容后重新计算threshold值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;// 等于0 表示还在原位置if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}// 原索引+oldCap的位置else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);// 原索引位置if (loTail != null) {loTail.next = null;newTab[j] = loHead;}// 原索引+oldCap的位置if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}

4 总结

JDK 1.8 的HashMap底层设计的非常棒,非常巧妙,不愧为大师之作。

参考文献:
https://tech.meituan.com/java-hashmap.html

HashMap 源码分析(JDK1.8)相关推荐

  1. HashMap源码分析-jdk1.6和jdk1.8的区别

    2019独角兽企业重金招聘Python工程师标准>>> 在java集合中,HashMap是用来存放一组键值对的数,也就是key-value形式的数据,而在jdk1.6和jdk1.8的 ...

  2. 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  3. 源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  4. Java类集框架 —— HashMap源码分析

    HashMap是基于Map的键值对映射表,底层是通过数组.链表.红黑树(JDK1.8加入)来实现的. HashMap结构 HashMap中存储元素,是将key和value封装成了一个Node,先以一个 ...

  5. HashMap源码解析(JDK1.8)

    HashMap源码解析(JDK1.8) 目录 定义 构造函数 数据结构 存储实现源码分析 删除操作源码分析 hashMap遍历和异常解析 1. 定义 HashMap实现了Map接口,继承Abstrac ...

  6. 在参考了众多博客之后,我写出了多达三万字的HashMap源码分析,比我本科毕业论文都要精彩

    HashMap源码分析 以下代码都是基于java8的版本 HashMap简介 源码: public class HashMap<K,V> extends AbstractMap<K, ...

  7. 【阅读源码系列】ConcurrentHashMap源码分析(JDK1.7和1.8)

    个人学习源码的思路: 使用ctrl+单机进入源码,并阅读源码的官方文档–>大致的了解一下此类的特点和功能 使用ALIT+7查看类中所有方法–>大致的看一下此类的属性和方法 找到重要方法并阅 ...

  8. hashmap源码分析及常用方法测试_一点课堂(多岸学院)

    HashMap 简介 底层数据结构分析 JDK1.8之前 JDK1.8之后 HashMap源码分析 构造方法 put方法 get方法 resize方法 HashMap常用方法测试 感谢 changfu ...

  9. HashMap 源码分析与常见面试题

    文章目录 HashMap 源码分析 jdk 1.7 内部常量 静态内部类 Holder 类 构造方法 put 过程 put 整体流程图 jdk 1.8 增加的常量 Node 类 Hash 值计算的变化 ...

最新文章

  1. css海浪动画代码,不行一行代码,纯css实现海浪动态效果!
  2. python opencv 拼接 连接 显示 图片
  3. VTK:隐式布尔值用法实战
  4. android 绘制5格电量,Android 如何把状态栏信号格改为5格
  5. 你的密码已泄露!使用C#阻止弱密码
  6. 预处理命令中的#和##
  7. 计算机技术在酒店管理专业的应用与探索,计算机信息化在高职酒店管理专业教学中的应用.doc...
  8. Python变量的下划线
  9. 没有第三个变量的前提下交换两个变量_很多人连Python变量都没搞懂,说自己会python
  10. Windows Media Center SDK 在 GitHub 上发布
  11. WPS使用VB批量将多个sheet单独存储为xls文件
  12. 让你的PPT更出彩的时间轴这样画!
  13. postman“在Tests中通过data.token获取token失败”的解决方法
  14. 加快黑群晖套件中心的套件下载速度
  15. 法语学习笔记——语音
  16. CorelDRAW 2023版本更新内容及安装详细教程
  17. android自定义曲线控件,Android自定义折线图(可拖动显示)
  18. 英特尔至强融核助力国家海洋局探索超算应用
  19. TokenGazer《一问到底》| 第45期 :研究员 VS Reserve
  20. Mac OS X Mavericks 迅雷下载地址 ( mac os x 10.9 )

热门文章

  1. 清空数组的三种方法,简单直观
  2. thinkphp批量更新php,thinkphp6数据批量更新
  3. 数据结构系列——Java后缀树实现代码
  4. Ansible最佳实践
  5. 基于结构光的立体视觉
  6. 小样本学习记录————用于深度网络快速适应的模型不可知元学习(MAML)
  7. idea中的编码设置
  8. SPC软控件提供商NWA的产品在各行业的应用(化工行业)
  9. sqlite表更新添加字段——sqlite3
  10. appium安装及入门