JDK版本 1.8

结构:

HashMap实现了Map Cloneable Serializable接口;

基础了AbstractMap类,AbstractMap提供一些通用方法,如put remove等,但子类一般会重写put等方法;

public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable{ }

HashMap中定义了一些常量

DEFAULT_INITIAL_CAPACITY  1 << 4 默认初始化容量值
MAXIMUM_CAPACITY 1 << 30 最大容量值
DEFAULT_LOAD_FACTOR 0.75f 默认负载因子
TREEIFY_THRESHOLD 8 树化门槛值
UNTREEIFY_THRESHOLD 6 反树化门槛值
MIN_TREEIFY_CAPACITY 64 最小树化容量

数据结构节点

HashMap中定义了2个重要的数据结构,Node和TreeNode

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // hashCode值是key和value的hashcode取异或值
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
}

存储节点Node的容器

// 实际上就是一个Node数组
transient Node<K,V>[] table;

hash方法

相当于是HashMap自己的取Hash值的方法

    // 将key的hashcode值和hashcode值的低16值进行异或  记过就是在HashMap中的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap的put方法实现

put函数大致的思路为:

  1. 对key的hashCode()做hash,然后再计算index;
  2. 如果没碰撞直接放到bucket里;(实际上是看index所在的位置是否有节点)
  3. 如果碰撞了,以链表的形式存在buckets后;
  4. 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
  5. 如果节点已经存在就替换old value(保证key的唯一性)
  6. 如果bucket满了(超过load factor*current capacity),就要resize。

put()方法是public的,会调用putVal()方法。

计算index,通过i = (n - 1) & hash来确定桶的位置 再根据桶是否为空(以及桶元素的长度 延长链表或者变树)

代码



    // 先对key进行hash计算  再调用putVal去存
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; // table的临时引用
Node<K,V> p; // p是桶位置上存放的节点
int n, i; // n存放table数组的长度 i是元素数组下标 即桶位置
// 判断table是否为null 或者容量为空 是则初始化tablesize
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; // 桶位置上的节点为null 则创建一个节点(使用本次存放的keyvalue)
// ***计算位置索引 i = (n - 1) & hash
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 如果桶位置有节点 则验证是否是同一个hash值 是则替换value
Node<K,V> e; K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
// 如果桶节点元素已经是树节点 则调用putTreeVal()放入树中
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 1st
treeifyBin(tab, hash);
break;
}
// 如果找到key的对应节点 则break
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果映射存在 则根据onlyIfAbsent进行存储
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 更新计数器+1
++modCount;
// 如果size大于阈值 则扩容
if (++size > threshold)
resize();
// LinkedHashMap的回调 对节点插入完成后的操作
afterNodeInsertion(evict);
return null;
}

扩容的源码

    /**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
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)
// 将老容量值右移1位 即乘以2 并将阈值也翻倍
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
} // 如果新阈值为0 则按重新计算阈值
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) {
// 老table不为null 则遍历
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
// 通过 e.hash & (newCap - 1)来重新计算桶位置
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<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;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.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;
}

2550--HashMap源码解析的更多相关文章

  1. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  2. HashMap源码解析 非原创

    Stack过时的类,使用Deque重新实现. HashCode和equals的关系 HashCode为hash码,用于散列数组中的存储时HashMap进行散列映射. equals方法适用于比较两个对象 ...

  3. Java中的容器(集合)之HashMap源码解析

    1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...

  4. 最全的HashMap源码解析!

    HashMap源码解析 HashMap采用键值对形式的存储结构,每个key对应唯一的value,查询和修改的速度很快,能到到O(1)的平均复杂度.他是非线程安全的,且不能保证元素的存储顺序. 他的关系 ...

  5. HashMap源码解析和设计解读

    HashMap源码解析 ​ 想要理解HashMap底层数据的存储形式,底层原理,最好的形式就是读它的源码,但是说实话,源码的注释说明全是英文,英文不是非常好的朋友读起来真的非常吃力,我基本上看了差不多 ...

  6. 详解HashMap源码解析(下)

    上文详解HashMap源码解析(上)介绍了HashMap整体介绍了一下数据结构,主要属性字段,获取数组的索引下标,以及几个构造方法.本文重点讲解元素的添加.查找.扩容等主要方法. 添加元素 put(K ...

  7. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  8. 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析

    前面了解了jdk容器中的两种List,回忆一下怎么从list中取值(也就是做查询),是通过index索引位置对不对,由于存入list的元素时安装插入顺序存储的,所以index索引也就是插入的次序. M ...

  9. 【Java深入研究】9、HashMap源码解析(jdk 1.8)

    一.HashMap概述 HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现.与HashTable主要区别为不支持同步和允许null作为key和value.由于HashMap不是线程 ...

  10. HashMap 源码解析(一)之使用、构造以及计算容量

    目录 简介 集合和映射 HashMap 特点 使用 构造 相关属性 构造方法 tableSizeFor 函数 一般的算法(效率低, 不值得借鉴) tableSizeFor 函数算法 效率比较 tabl ...

随机推荐

  1. 【Java面试】如何理解Spring Boot中的Starter?

    一个工作了3年的Java程序员,遇到一个Spring Boot的问题. 他对这个问题有一些了解,但是回答得不是很好,希望参考我的高手回答. 这个问题是:"如何理解Spring Boot中的S ...

  2. 没错,就是Access-Control-Allow-Origin,跨域

    服务端添加: <add name="Access-Control-Allow-Origin" value="*" /><add name=&q ...

  3. 分布式任务调度ScheduleMaster

    1.什么是ScheduleMaster ScheduleMaster是分布式任务调度系统.简称:集中任务调度系统,最简单的理解ScheduleMaster,就是对不同的系统里面的调度任务做统一管理的框 ...

  4. 1.设计模式第一步-《设计模式从头到脚舔一遍-使用C#实现》

    更新记录: 完成第一次编辑:2022年4月23日20:29:33. 加入小黄人歌曲:2022年4月23日21:45:36. 1.1 设计模式(Design Pattern)是什么 设计模式是理论.是前 ...

  5. C#项目中常见的目录和文件

    本文迁移自Panda666原博客,原发布时间:2021年4月17日. Bin 目录 bin是英文binary的缩写, 字面意思是二进制,意指用来存放编译后的结果.C#/VB编译器编译后的程序二进制文件 ...

  6. 写了个基于 MacOS + iTerm2 自动打开窗口执行命令的工具

    大家好,我是秋风,今天要给大家带来的这个工具是我最近写的 一个 npm 工具.mmt 是基于 MacOS + iTerm2 ,目的主要是为了提高日常生活中的效率,接下来我带大家看看一些常用的一些场景. ...

  7. 基于Vue2.x的前端架构,我们是这么做的

    通过Vue CLI可以方便的创建一个Vue项目,但是对于实际项目来说还是不够的,所以一般都会根据业务的情况来在其基础上添加一些共性能力,减少创建新项目时的一些重复操作,本着学习和分享的目的,本文会介绍 ...

  8. Java判断字符串是否为金额

    public static void main(String[] args) { String aa = "5632.2"; //小数点前后是数字即可,无小数点后数据也ok Sys ...

  9. docker 部署 minio

    1.下载镜像 docker pull minio/minio 2.启动 docker run -p 9000:9000 --name minio \ -d --restart=always \ -e ...

  10. map集合中对应key的value为null处理办法

    问题: Map集合中对应key的value为null,但是现在需要将这个value转为Integer类型,这个value如果不是null,那么get到的是long类型或者是Bigdecimal类型 处 ...