简介

HashMap采用key/value存储结构,每个key对应唯一的value,查询和修改的速度都很快,能达到O(1)的平均时间复杂度。它是非线程安全的,且不保证元素存储的顺序;

继承体系

HashMap实现了Cloneable,可以被克隆。

HashMap实现了Serializable,可以被序列化。

HashMap继承自AbstractMap,实现了Map接口,具有Map的所有功能。

存储结构

在Java中,HashMap的实现采用了(数组 + 链表 + 红黑树)的复杂结构,数组的一个元素又称作桶。

在添加元素时,会根据hash值算出元素在数组中的位置,如果该位置没有元素,则直接把元素放置在此处,如果该位置有元素了,则把元素以链表的形式放置在链表的尾部。

当一个链表的元素个数达到一定的数量(且数组的长度达到一定的长度)后,则把链表转化为红黑树,从而提高效率。

数组的查询效率为O(1),链表的查询效率是O(k),红黑树的查询效率是O(log k),k为桶中的元素个数,所以当元素数量非常多的时候,转化为红黑树能极大地提高效率。

源码解析

属性

/** * 默认的初始容量为16 */static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;/** * 最大的容量为2的30次方 */static final int MAXIMUM_CAPACITY = 1 << 30;/** * 默认的装载因子 */static final float DEFAULT_LOAD_FACTOR = 0.75f;/** * 当一个桶中的元素个数大于等于8时进行树化 */static final int TREEIFY_THRESHOLD = 8;/** * 当一个桶中的元素个数小于等于6时把树转化为链表 */static final int UNTREEIFY_THRESHOLD = 6;/** * 当桶的个数达到64的时候才进行树化 */static final int MIN_TREEIFY_CAPACITY = 64;/** * 数组,又叫作桶(bucket) */transient Node[] table;/** * 作为entrySet()的缓存 */transient Set> entrySet;/** * 元素的数量 */transient int size;/** * 修改次数,用于在迭代的时候执行快速失败策略 */transient int modCount;/** * 当桶的使用数量达到多少时进行扩容,threshold = capacity * loadFactor */int threshold;/** * 装载因子 */final float loadFactor;

(1)容量

容量为数组的长度,亦即桶的个数,默认为16,最大为2的30次方,当容量达到64时才可以树化。

(2)装载因子

装载因子用来计算容量达到多少时才进行扩容,默认装载因子为0.75。

(3)树化

树化,当容量达到64且链表的长度达到8时进行树化,当链表的长度小于6时反树化。

Node内部类

Node是一个典型的单链表节点,其中,hash用来存储key计算得来的hash值。

static class Node implements Map.Entry { final int hash; final K key; V value; Node next;}

TreeNode内部类

这是一个神奇的类,它继承自LinkedHashMap中的Entry类,关于LInkedHashMap.Entry这个类我们后面再讲。

TreeNode是一个典型的树型节点,其中,prev是链表中的节点,用于在删除元素的时候可以快速找到它的前置节点。

// 位于HashMap中static final class TreeNode extends LinkedHashMap.Entry { TreeNode parent; // red-black tree links TreeNode left; TreeNode right; TreeNode prev; // needed to unlink next upon deletion boolean red;}// 位于LinkedHashMap中,典型的双向链表节点static class Entry extends HashMap.Node { Entry before, after; Entry(int hash, K key, V value, Node next) { super(hash, key, value, next); }}

HashMap()构造方法

空参构造方法,全部使用默认值。

public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}

HashMap(int initialCapacity)构造方法

调用HashMap(int initialCapacity, float loadFactor)构造方法,传入默认装载因子。

public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);}

HashMap(int initialCapacity)构造方法

判断传入的初始容量和装载因子是否合法,并计算扩容门槛,扩容门槛为传入的初始容量往上取最近的2的n次方。

public HashMap(int initialCapacity, float loadFactor) { // 检查传入的初始容量是否合法 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // 检查装载因子是否合法 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; // 计算扩容门槛 this.threshold = tableSizeFor(initialCapacity);}static final int tableSizeFor(int cap) { // 扩容门槛为传入的初始容量往上取最近的2的n次方 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;}

put(K key, V value)方法

添加元素的入口。

public V put(K key, V value) { // 调用hash(key)计算出key的hash值 return putVal(hash(key), key, value, false, true);}static final int hash(Object key) { int h; // 如果key为null,则hash值为0,否则调用key的hashCode()方法 // 并让高16位与整个hash异或,这样做是为了使计算出的hash更分散 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; // 如果桶的数量为0,则初始化 if ((tab = table) == null || (n = tab.length) == 0) // 调用resize()初始化 n = (tab = resize()).length; // (n - 1) & hash 计算元素在哪个桶中 // 如果这个桶中还没有元素,则把这个元素放在桶中的第一个位置 if ((p = tab[i = (n - 1) & hash]) == null) // 新建一个节点放在桶中 tab[i] = newNode(hash, key, value, null); else { // 如果桶中已经有元素存在了 Node e; K k; // 如果桶中第一个元素的key与待插入元素的key相同,保存到e中用于后续修改value值 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) // 如果第一个元素是树节点,则调用树节点的putTreeVal插入元素 e = ((TreeNode) p).putTreeVal(this, tab, hash, key, value); else { // 遍历这个桶对应的链表,binCount用于存储链表中元素的个数 for (int binCount = 0; ; ++binCount) { // 如果链表遍历完了都没有找到相同key的元素,说明该key对应的元素不存在,则在链表最后插入一个新节点 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 如果插入新节点后链表长度大于8,则判断是否需要树化,因为第一个元素没有加到binCount中,所以这里-1 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 如果待插入的key在链表中找到了,则退出循环 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 如果找到了对应key的元素 if (e != null) { // existing mapping for key // 记录下旧值 V oldValue = e.value; // 判断是否需要替换旧值 if (!onlyIfAbsent || oldValue == null) // 替换旧值为新值 e.value = value; // 在节点被访问后做点什么事,在LinkedHashMap中用到 afterNodeAccess(e); // 返回旧值 return oldValue; } } // 到这里了说明没有找到元素 // 修改次数加1 ++modCount; // 元素数量加1,判断是否需要扩容 if (++size > threshold) // 扩容 resize(); // 在节点插入后做点什么事,在LinkedHashMap中用到 afterNodeInsertion(evict); // 没找到元素返回null return null;}

(1)计算key的hash值;

(2)如果桶(数组)数量为0,则初始化桶;

(3)如果key所在的桶没有元素,则直接插入;

(4)如果key所在的桶中的第一个元素的key与待插入的key相同,说明找到了元素,转后续流程(9)处理;

(5)如果第一个元素是树节点,则调用树节点的putTreeVal()寻找元素或插入树节点;

(6)如果不是以上三种情况,则遍历桶对应的链表查找key是否存在于链表中;

(7)如果找到了对应key的元素,则转后续流程(9)处理;

(8)如果没找到对应key的元素,则在链表最后插入一个新节点并判断是否需要树化;

(9)如果找到了对应key的元素,则判断是否需要替换旧值,并直接返回旧值;

(10)如果插入了元素,则数量加1并判断是否需要扩容;

resize()方法

扩容方法。

final Node[] resize() { // 旧数组 Node[] 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) // 如果旧容量的两倍小于最大容量并且旧容量大于默认初始容量(16),则容量扩大为两部,扩容门槛也扩大为两倍 newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold // 使用非默认构造方法创建的map,第一次插入元素会走到这里 // 如果旧容量为0且旧扩容门槛大于0,则把新容量赋值为旧门槛 newCap = oldThr; else { // zero initial threshold signifies using defaults // 调用默认构造方法创建的map,第一次插入元素会走到这里 // 如果旧容量旧扩容门槛都是0,说明还未初始化过,则初始化容量为默认容量,扩容门槛为默认容量*默认装载因子 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { // 如果新扩容门槛为0,则计算为容量*装载因子,但不能超过最大容量 float ft = (float) newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE); } // 赋值扩容门槛为新门槛 threshold = newThr; // 新建一个新容量的数组 @SuppressWarnings({"rawtypes

hashmap修改对应key的值_死磕 java集合之HashMap源码分析相关推荐

  1. java ee是什么_死磕 java集合之HashSet源码分析

    问题 (1)集合(Collection)和集合(Set)有什么区别? (2)HashSet怎么保证添加元素不重复? (3)HashSet是否允许null元素? (4)HashSet是有序的吗? (5) ...

  2. 【死磕 Java 集合】— LinkedTransferQueue源码分析

    [死磕 Java 集合]- LinkedTransferQueue源码分析 问题 (1)LinkedTransferQueue是什么东东? (2)LinkedTransferQueue是怎么实现阻塞队 ...

  3. 死磕Java集合之BitSet源码分析(JDK18)

    死磕Java集合之BitSet源码分析(JDK18) 文章目录 死磕Java集合之BitSet源码分析(JDK18) 简介 继承体系 存储结构 源码解析 属性 构造方法 set(int bitInde ...

  4. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  5. java arraydeque_死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  6. 死磕 java集合之ConcurrentSkipListMap源码分析——发现个bug

    前情提要 点击链接查看"跳表"详细介绍. 拜托,面试别再问我跳表了! 简介 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表. 跳表在原有的有序链表上面增加了多级 ...

  7. 死磕 java集合之TreeMap源码分析(二)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 插入元素 插入元素, ...

  8. 死磕 java集合之TreeMap源码分析(一)——红黑树全解析

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 简介 TreeMap使用红黑树存储元素,可以保证元素按key值的大小进行遍历. 继承体系 Tr ...

  9. 死磕 java集合之TreeMap源码分析(三)- 内含红黑树分析全过程

    2019独角兽企业重金招聘Python工程师标准>>> 欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. 删除元素 删除元素本 ...

最新文章

  1. sl中几个简单变量的获取
  2. 优先队列如何按照pair 的第二关键字排序(对比vector按照pair第二关键字排序)
  3. *2 echo、printf、mkdir命令的应用
  4. 中的挂起是什么意思_客厅适合挂什么字画 要想有品位就挂这样的
  5. 通过 pxe(网络安装)完成centos 系统的网络安装
  6. jenkins+k8s实现持续集成
  7. php按照二维数组某个字段排序,PHP 二维数组根据某个字段排序
  8. 软件开发中Alpha、Beta、RC、GA版本的含义
  9. Python实现香农编码和费诺编码
  10. python计算方差的置信区间_python计算置信区间
  11. 百度、阿里、腾讯、华为和移动等常用网盘免费空间与性价比
  12. SDN 交换机迁移1
  13. C+++实现推箱子(附加回撤功能)
  14. Linux 远程工具
  15. dos2unix和unix2dos命令
  16. ES6中setTimeout函数的this
  17. 菜鸟学Java——简单验证码生成(Java版)
  18. python3 高效实现 最大质因数/质因数集合 方法
  19. *每日一题(三零)var a = 10 var foo={ a:20, bar:function(){ var a=30 return this.a
  20. Cura15.04.6 安装闪退及汉化解决

热门文章

  1. 13.python中的字典
  2. Winform主窗体的设置
  3. hdu_1285_确定比赛名次_201312081335
  4. firefox下光标处插入文本
  5. Quartz.net官方开发指南 第七课 : TriggerListeners和JobListeners
  6. windows查看端口占用 windows端口占用 查找端口占用程序 强制结束端口占用 查看某个端口被占用的解决方法 如何查看Windows下端口占用情况
  7. 关于 错误 137 (net::ERR_NAME_RESOLUTION_FAILED) 的解决方案
  8. 【重磅推出】推荐系统系列教程之九:解密“看了又看”和“买了又买”(Item-Based)...
  9. python编译:setup.py添加.h头文件或者库的搜索路径
  10. 浙大PAT甲级1019. General Palindromic Number (20)