一、HashMap数据结构

HashMap由 数组+链表+红黑树实现,桶中元素可能为链表,也可能为红黑树。为了提高综合(查询、添加、修改)效率,当桶中元素数量超过TREEIFY_THRESHOLD(默认为8)时,链表存储改为红黑树存储,当桶中元素数量小于UNTREEIFY_THRESHOLD(默认为6)时,红黑树存储改为链表存储。

table即Node<k,v>[] table,Node有两种,分别为链表节点Node和其子类TreeNode(红黑树节点)

每一个table槽称为桶,用于装hash%table.length的元素

table.length为2的整数幂,主要是为了提高取模运算效率,即对于2的整数幂n,hash%n可以转化为hash&(n-1)

二、HashMap类属性及构造函数

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {// 序列号private static final long serialVersionUID = 362498820763181265L;    // 默认的初始容量是16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   // 最大容量static final int MAXIMUM_CAPACITY = 1 << 30; // 默认的填充因子static final float DEFAULT_LOAD_FACTOR = 0.75f;// 当桶(bucket)上的结点数大于这个值时会转成红黑树static final int TREEIFY_THRESHOLD = 8; // 当桶(bucket)上的结点数小于这个值时树转链表static final int UNTREEIFY_THRESHOLD = 6;// 桶中结构转化为红黑树对应的table的最小大小static final int MIN_TREEIFY_CAPACITY = 64;// 存储元素的数组,总是2的幂次倍transient Node<k,v>[] table; // 存放具体元素的集transient Set<map.entry<k,v>> entrySet;// 存放元素的个数,注意这个不等于数组的长度。transient int size;// 每次扩容和更改map结构的计数器transient int modCount;   // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容int threshold;// 填充因子final float loadFactor;
}

构造函数HashMap(int,flaot)

public HashMap(int initialCapacity, float loadFactor) {// 初始容量不能小于0,否则报错if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);// 初始容量不能大于最大值,否则为最大值if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;// 填充因子不能小于或等于0,不能为非数字if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);// 初始化填充因子                                        this.loadFactor = loadFactor;// 初始化threshold大小this.threshold = tableSizeFor(initialCapacity);
}

tableSizeFor为取不小于capacity的最小2的整数幂,该算法的详解参考本文的上一篇博客

static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

三、HashMap类的主要方法

putVal函数(put操作的基础函数)

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//检测table是否为空,如果为空,则使用扩容函数进行初始化if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//如果通过hash值取模得到的桶为空,则直接把新生成的节点放入该桶if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {//以下为该桶不为空的逻辑Node<K,V> e; K k;//判断桶的第一个元素的key值是否相同(hash值相同,且能equals)//如果相同,则返回当前元素(函数末尾进行统一处理)if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)//桶元素采用的是红黑树结构e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//桶元素采用的是链表结构for (int binCount = 0; ; ++binCount) {//如果遍历到了链表末端,则直接在链表末端插入新元素if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//插入之后,检查是否达到了转成红黑树结构的标准if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}//如果在遍历过程中,发现了key值相同,则返回当前元素(函数末尾进行统一处理)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;//如果onlyIfAbsent为ture,则在oldValue为空时才替换//否则直接替换if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;//修改次数+1//map的size加1,然后判断是否达到了threshold,否则进行扩容//threshold由Node[] table的长度及loadFactor控制if (++size > threshold)resize();//执行回调函数afterNodeInsertion(evict);return null;}

put函数

 public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

hash函数的作用是使元素hash值更加发散,经过hash()处理之后,hash值的高16位没变,低16为原来的低16与高16异或的结果,即用16位来综合了高16位和低16位的影响,这样能提高key的发散性的主要原因是table的长度通常小于2^16,通过hash%table.length就能更发散

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

getNode函数(get操作的基础函数)

 final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;//如果table不为空,则再进行查询操作if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {//先检查第一个元素是否key相同if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;if ((e = first.next) != null) {//如果为红黑树结构,则走红黑树的查询逻辑if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);do {//否则遍历链表if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;}

resize()函数

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) {//如果扩容之前的容量已经达到了最大值//则只把threshold变成Integer.MAX_VALUE,即不限制map的最大size,之后不管插入多少元素也不触发resizeif (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}//把新容量变成原来的2倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // 初始capacity(构造函数输入的)被设置成了thresholdnewCap = oldThr;else { // oldThr=0的情况,此时表明采用默认的参数进行初始化newCap = 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);}//处理好newThr和newCap之后,开始resize()函数的真正逻辑threshold = newThr;//设置threshold@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 { // 链式情况,此时会把原来的链分成两个链loHead和hiHead//loHead存储(e.hash & oldCap) == 0的元素//hiHead存储(e.hash & oldCap) != 0的元素//扩容之后,由于新的capacity为oldCap的2倍,且它们都为2的整数幂//对于该链上的元素,如果(e.hash & oldCap) == 0,则新的槽位(hash%capacity)==旧的槽位(hash%oldCap)//否则,它们新的槽位都一样,且都为原来的槽位后移oldCapNode<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;}

链式情况的resize()图解:

四、对扩容的理解

扩容触发的条件是map中的元素个数达到threshold,threshold的一般值为table.length*loadFactor

扩容的作用是提高数据访问效率,通过扩容,元素会更分散,可以减少单个桶中元素的个数,进而减少链表的长度或红黑树的深度,进而可以提高数据的访问效率

扩容是一个很重的操作,需要遍历所有的元素,因此当扩容带来的性能提升优势不明显时,尽量避免扩容

JDK1.8 HashMap源码解析(不分析红黑树部分)相关推荐

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

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

  2. 红黑树分析与JDK8中HashMap源码解析

    红黑树分析与JDK8中HashMap源码解析 BST O(1), O(n), O(logn), O(nlogn) 的区别 红黑树-RBTree 插入数据 HashMap中红黑树的插入操作 HashMa ...

  3. hashmap删除指定key_Java集合之HashMap源码解析(JDK8)

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景非常丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...

  4. 面试官系统精讲Java源码及大厂真题 - 08 HashMap 源码解析

    08 HashMap 源码解析 自信和希望是青年的特权. --大仲马 引导语 HashMap 源码很长,面试的问题也非常多,但这些面试问题,基本都是从源码中衍生出来的,所以我们只需要弄清楚其底层实现原 ...

  5. Java集合框架之三:HashMap源码解析

    Java集合框架之三:HashMap源码解析 版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! HashMap在我们的工作中应用的非常广泛,在工作面试中也经常会被问到,对于这样一个重要的集 ...

  6. 通俗易懂Hashmap源码解析

    Hashmap源码解析 一.Hashmap数据结构 哈希表是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的值即 key,就可以找到其对应的值即 Value.哈希的思路很简单 ...

  7. JDK8 HashMap源码解析

    1.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值, ...

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

    HashMap是由数组加链表的结合体.如下图: 图中可以看出HashMap底层就是一个数组结构,每个数组中又存储着链表(链表的引用) JDK1.6实现hashmap的方式是采用位桶(数组)+链表的方式 ...

  9. 史上最详细的 JDK 1.8 HashMap 源码解析

    微信搜索[程序员囧辉],关注这个坚持分享技术干货的程序员. 我的最新文章:BAT 老兵的经验之谈,成长路上这个道理越早知道越好 目录 前言 几个点: 基本属性 定位哈希桶数组索引位置 get 方法 代 ...

最新文章

  1. 关于char(M)和varchar(N)的区别
  2. java下拉列表选日期_iPhone应用程序:日期选择器查看下拉列表
  3. 2020-11-29(准备考试)
  4. Tomcat配置虚拟内存
  5. easyui数据表格重置_数据库三种删除方式
  6. python 函数进度条怎么_刷新你对进度条的认识,用python写出不一样的进度条
  7. Latex 修改公式的的大小
  8. python输出100到200的素数_python输出2到100之间的素数
  9. flash 音乐 html代码,用html为flash页面添加音乐
  10. nmap命令教程详解
  11. VLAN中tagged与untagged的处理(转)
  12. php源码 gd,php 源码安装没有gd库
  13. 苹果ipad服务器无响应怎么办,苹果iPad死机怎么办?几种处理iPad使用过程中死机的方法...
  14. 算法归总—短除法求最大公约数
  15. 阶乘约数-蓝桥杯国赛java
  16. 网络-电脑网络突然变成球形, 网络不可用
  17. UDP服务器开发与nb-iot模组通信(1)----协议篇
  18. PE中Ghost 使用详解
  19. 刘汝佳 例题7-1 除法(暴力解法)
  20. 聊一聊关于微前端架构的几种技术选型

热门文章

  1. appserv怎么安装mysql_AppServ怎么安装?AppServ 8.6.0 64位图文超详细安装教程(附下载)...
  2. ResNet 小白学习笔记
  3. 联想乐Phone中关村惨遭“机卡分离”销售
  4. WWW(环球信息网)
  5. 服务网格和Cookpad
  6. WAP WEB
  7. java中数组转List
  8. Linux网络系统任务教程,Linux网络操作系统项目式教程(CentOS 7.6)(微课版)
  9. Android MediaScanner MediaProvider流程以及性能优化,音视频扫描
  10. 【转】宽带连接错误的处理办法691、623、678、645、720、721、718、734、769、619、676、815