1. java集合笔记一
  2. java集合笔记二
  3. java集合笔记三

jdk 8 之前,其内部是由数组+链表来实现的,而 jdk 8 对于链表长度超过 8 的链表将转储为红黑树

1.属性

    //节点数组,第一次使用时初始化,后面根据需要调整,
transient Node<K,V>[] table;
//实际存储的键值对个数
transient int size;
//用于迭代防止结构性破坏的标量
transient int modCount;
//临界值,等于数组容量*负载因子,添加元素时数组中的元素到达临界值,数组扩容后再添加元素
int threshold;
//HashMap 中默认负载因子为 0.75
final float loadFactor;
//数组中存放的节点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;//hash值
final K key;//键
V value;//值
Node<K,V> next;//指向的下一个节点
....
}
//链表超过数量时红黑树节点
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
...
}

2.构造函数

初始化负载因子及容量大小(放置在临界值变量中)

    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);
} //通过该方法,我们将获得一个 2 的整数次幂的容量的值,此处存放至 threshold,为后面put方法中初始数组时使用
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;
}

该方法例如 传入自定义初始容量大小为19
例如自定义初始化容量为19;最后结果为32
0000 0000 0001 0001 减一
0000 0000 0000 1000 >>>1

0000 0000 0001 1001 或结果25
0000 0000 0000 0110 >>>2

0000 0000 0001 1111 或结果31
0000 0000 0000 0001 >>>4

0000 0000 0001 1111 或结果31
0000 0000 0000 0000 >>>8

0000 0000 0001 1111 或结果31
0000 0000 0000 0000 >>>16

0000 0000 0001 1111 或结果31

3.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 值找到对应的索引位置,如果该位置为 null,说明还没有头结点,于是 newNode 并存储在该位置上。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//若该索引位置有值
Node<K,V> e; K k;
//若该位置的第一个节点hash值及Key相同,则修改该第一个节点的值
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);
//TREEIFY_THRESHOLD=8;若该位置节点数大于等于8,将链表裂变成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//在遍历时发现该节点的hash值及key相同,则跳出遍历,在下面将值修改
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//修改,将节点e的值替换为新值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//需要扩容,调用resize();
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

初始化数组或数组扩容

        final Node<K,V>[] resize() {
...
int oldCap = (oldTab == null) ? 0 : oldTab.length;//旧数组长度
...
//若旧数组长度大于0则已经初始化过
if (oldCap > 0) {
//若旧数组长度大于了最大长度限制
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//若旧数组长度翻倍赋值给新数组长度小于最大长度限制且旧数组长度大于默认长度(16)则临界值翻倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//若旧临界值大于0,则新数组长度为旧临界值(及构造方法中的tableSizeFor())
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//构造函数无参,初始化数组长度为16,临界值为0.75*16
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
threshold = newThr;
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 order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
//根据hash与运算旧数组长度,分为两个链表
//关于原链表分成2个不同的链表后如何查找节点
//查询节点在putVal和getNode中为:tab[i = (n - 1) & hash],n为数组长度
//假如n为16,二进制为1 0000 hash值为0101 则查找数组中的下标为3的节点
//现扩容为32,二进制为10 0000 hash依然为0101 则扩容后依然查询新数组中下标为3的节点,链表也即为下面lo
//假如n为16,hash值为1 1101 则查找数组中下标为13的节点
//现扩容为32,hash值为1 1101 则需查询扩容后的数组下标为(原位置+原数组长度)29的节点,链表即为下面的hi
if ((e.hash & oldCap) == 0) {
//尾部节点为空 为第一个将原节点e赋值给头节点
if (loTail == null)
loHead = e;
//此时头节点已存在,将原节点e赋值给新链表尾部节点的下一个
else
loTail.next = e;
//尾部节点赋值为源节点e
loTail = e;
}
//同上
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//lo尾部节点不为空,新数组(原位置)存放该链表的头节点
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//hi尾部节点不为空,新数组(原位置+原数组长度)存放该链表的头节点
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
//返回初始化或扩容后的数组
return newTab;
}

裂变为红黑树

        final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//原数组为空或数组长度小于MIN_TREEIFY_CAPACITY=64则选择数组扩容,不转为红黑树
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//链表的头节点不为空
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
原单向链表转化为双向链表,node转换为treeNode
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
//头节点不为空,将链表转换为红黑树结构
hd.treeify(tab);
}
}

Map集合类(一.hashMap源码解析jdk1.8)的更多相关文章

  1. 最全的HashMap源码解析!

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

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

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

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

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

  4. HashMap源码解析 非原创

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

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

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

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

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

  7. 死磕Java之聊聊HashMap源码(基于JDK1.8)

    死磕Java之聊聊HashMap源码(基于JDK1.8) http://cmsblogs.com/?p=4731 为什么面试要问hashmap 的原理

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

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

  9. hashmap源码解析,JDK1.8和1.7的区别

    背景:hashmap面试基础必考内容,需要深入了解,并学习其中的相关原理.此处还要明白1.7和1.8不通版本的优化点. Java 8系列之重新认识HashMap Java 8系列之重新认识HashMa ...

随机推荐

  1. MYSQL如何优化?

    MYSQL如何优化?结合你的经验 1.数据库的设计尽量把数据库设计的更小的占磁盘空间.1).尽可能使用更小的整数类型.(mediumint就比int更合适).2).尽可能的定义字段为not null, ...

  2. java23种设计模式(一)-- 工厂模式、抽象工厂模式和单例模式

    一.工厂模式 1.定义统一的接口,并在接口中定义要实现的抽象方法. 2.创建接口的具体实现类,并实现抽象方法. 3.创建一个工厂类,根据传递的参数,生成具体的实现类对象,执行具体的方法. 优点: 1. ...

  3. [转]DesignWare是什么

    一.DesignWare是什么 摘自https://zhidao.baidu.com/question/473669077.html DesignWare是SoC/ASIC设计者最钟爱的设计IP库和验 ...

  4. ContextLoaderListener vs DispatcherServlet

    In XML based Spring MVC configuration, you must have seen two declarations in web.xml file i.e. Cont ...

  5. 深入理解volatile关键字

    Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作内存 ...

  6. shell 根据路径获取文件名和目录

    path=/dir1/dir2/dir3/test.txt echo ${path##*/} 获取文件名 test.txtecho ${path##*.} 获取后缀 txt #不带后缀的文件名temp ...

  7. SCP-bzoj-1069

    项目编号:bzoj-1069 项目等级:Safe 项目描述: 戳这里 特殊收容措施: 求凸包后在凸包上旋转卡壳.然而复杂度要求较低,故可直接枚举四边形的一条对角线,另两个顶点在凸包上随这条对角线的移动 ...

  8. c#如何写服务,打包和卸载服务

    Service.cs  每隔一分钟进行一次数据操作 public Service1()        {            InitializeComponent();            Sy ...

  9. CTO 技能图谱skill-map

    # CTO 技能图谱 ### 岗位职责* 建立技术团队文化* 规划技术发展路线* 落地产品研发成果* 宣传公司技术品牌* 吸引优秀技术人才 ### 基本素质* 正直诚实的道德修养* 谦虚谨慎的工作态度 ...

  10. STM32 系统架构

    这里所讲的 STM32 系统架构主要针对的 STM32F103 这些非互联型芯片 STM32 主系统主要由四个驱动单元和四个被动单元构成. 四个驱动单元是: 内核 DCode 总线; 系统总线;通用  ...