Java集合07

14.HashMap底层机制

  1. (k,v)是一个Node,实现了Map.Entry<K,V>,查看HashMap的源码可以看到
  2. jdk7.0 的HashMap底层实现[数组+链表],jdk8.0底层[数组+链表+红黑树]

14.1HashMap扩容机制(和HashSet完全相同)

详见10.2HashSet的底层扩容机制

  1. HashMap底层维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因子(loadfactor)初始化为0.75
  3. 当添加key-value时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素则直接添加;如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等。若相等,则直接替换value;若不相等,需要判断是树结构还是链表结构,作出相应处理。如果添加是发现容量还不够,则需要扩容。
  4. 第一次添加,则需要扩容table容量为16,临界值(threshold)为(0.75*16=)12
  5. 以后再扩容,则需要扩容为table的容量为之前的两倍,临界值也为原来的两倍,即24.以此类推
  6. 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认为8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。

例子:

package li.map.hashmap;

import java.util.HashMap;

@SuppressWarnings("all")
public class HashMapSource {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java",10);//ok
map.put("php",10);//ok
map.put("java",20);//替换value System.out.println(map);//{java=20, php=10}
}
}

执行过程如下:

  1. 执行构造器 newHashMap();

    初始化加载因子 loadfactor = 0.75

    HashMap$Node[ ] = null

  2. 执行put(),调用hash()方法计算key的值

    可以看到,如果传入的参数key为空的话,就返回0;如果不为空,则求出 key 的 hashCode 值,然后将hashCode 值右移16位并且与原来的 hashCode 值进行 ^(按位异或) 操作,并返回这个哈希值

public V put(K key, V value) {//K="java" value= 10
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

3.调用putVal方法:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {//
Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
//这里定义的tablejiushi HashMap的一个数组,类型是Node[]数组
if ((tab = table) == null || (n = tab.length) == 0)//if 语句表示,如果当前table是null,或者大小=0,则进行第一次扩容,扩容到16个空间
n = (tab = resize()).length;//如果为第一次扩容,此时初始的table已经变成容量为16的数组 /*
1.根据key,得到hash 去计算key应该放到table表的哪个索引位置,并且把这个未知的对象赋给赋值变量p 2.再判断p是否为空
2.1如果p为空,表示该位置还没存放元素,就创建一个Node (key="java", value=PRESENT)并把数 据放在该位置--table[i]=newNode(hash, key, value, null);
*/
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); else {
//2.2如果不为空,就会进入else语句
Node<K,V> e; K k;//定义辅助变量
/*这里的p指向当前索引所在的对象(由上面的p = tab[i = (n - 1) & hash])计算出索引位置),如 果当前索引位置对应链表的第一个元素的哈希值 和 准备添加的key的哈希值 一样,
并且 满足下面两个条件之一:
1.准备加入的key 和 p指向的Node节点 的key 是同一个对象:(k = p.key) == key
2.p指向的Node节点的key 的equals()和准备加入的key比较后相同 并且key不等于null:(key != null && key.equals(k))
就不加入 只是换原来的元素(不插入新结点只是替换值)
*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p; //再判断p是否是一颗红黑树
//如果是红黑树,就调用putTreeVal()方法来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //如果table对应索引位置已经是一个链表了,就使用for循环依次比较
//(1)依次和该链表的每个元素都比较后 都不相同,就则将数据加入到该链表的最后
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//先赋值再判断
p.next = newNode(hash, key, value, null);
//注意:把元素添加到链表之后立即 判断该链表是否已经达到8个节点,如果已经达到则 //调用 treeifyBin()对当前链表进行树化(转成红黑树)
//在转成红黑树时 还要进行一个判断:
//如果该table数组的为空或者大小小于64,则对table数组进行扩容
//如果上面条件不成立,即数组大小大于等于64且链表数量达到8个,就转成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
} //(2)如果在依次和该链表的每个元素比较的过程中发现如果有相同情况,就直接break
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;//在上面for循环条件已经把p.next赋值给e了,这里e又赋值给p 其实就是将p指针指 //向p.next,然后再进行新一轮的判断,如此循环,直到有满足上面if语句的条件为止
}
} if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;//替换,key对应value
afterNodeAccess(e);
return oldValue;
}
} ++modCount;//每增加一个Node,就size++
if (++size > threshold)//当使用的容量 > 临界值时,就扩容
resize();
afterNodeInsertion(evict);
return null;
}

PS:关于树化

        for (int binCount = 0; ; ++binCount) {
//(1)依次和该链表的每个元素都比较后 都不相同,就则将数据加入到该链表的最后
if ((e = p.next) == null) {//先赋值再判断
p.next = newNode(hash, key, value, null); //注意:把元素添加到链表之后立即 判断该链表是否已经达到8个节点,如果已经达到则 //调用 treeifyBin()对当前链表进行树化(转成红黑树)
//在转成红黑树时 还要进行一个判断:
//如果该table数组的为空或者大小小于64,则对table数组进行扩容
//如果上面条件不成立,即数组大小大于等于64且链表数量达到8个,就转成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
} //(2)如果在依次和该链表的每个元素比较的过程中发现如果有相同情况,就直接break
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;//在上面for循环条件已经把p.next赋值给e了,这里e又赋值给p 其实就是将p指针指 //向p.next,然后再进行新一轮的判断,如此循环,直到有满足上面if语句的条件为止
}

遍历过程中p从第一个节点遍历到最后一个节点,但由于binCount是从0开始计数,所以在做树化判断时binCount

的值等于 链表长度 - 1(注意此时的链表长度没有算新插入的节点)

判断条件为 binCount >= TREEIFY_THRESHOLD - 1 ==> binCount+1(链表长度) >= TREEIFY_THRESHOLD

但此时链表新插入了一个节点

 p.next = newNode(hash, key, value, null);

所以链表树化的那一刻,它的真实长度应该时binCount+1+1 => 链表长度>TREEIFY_THRESHOLD(8)

即:

链表长度大于8时,treeifyBin()方法被调用

(在做树化判断时,链表长度 = binCount+1(从零计数)+1(新插入节点) = bincount +2)

(判断条件: (bincount >= 8-1) => (bincount>=7) => (bincount+2>=9) => (链表长度>=9) 长度是整数 大于等于9也就是大于8)

ps:剪枝--->如果有链表树化之后,树中的节点经过删除之后越来越少,当元素个数减少到一定程度,树会转变为了链表

day24--Java集合07的更多相关文章

  1. Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和性能分析)

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  2. Java 集合系列07之 Stack详细介绍(源码解析)和使用示例

    概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...

  3. 【转】 Java 集合系列07之 Stack详细介绍(源码解析)和使用示例

    概要 学完Vector了之后,接下来我们开始学习Stack.Stack很简单,它继承于Vector.学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码:最后再通过实例来学会使用它. ...

  4. Java 集合系列目录(Category)

    下面是最近总结的Java集合(JDK1.6.0_45)相关文章的目录. 01. Java 集合系列01之 总体框架 02. Java 集合系列02之 Collection架构 03. Java 集合系 ...

  5. Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

    概要  前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...

  6. Java集合源码学习(三)LinkedList分析

    前面学习了ArrayList的源码,数组是顺序存储结构,存储区间是连续的,占用内存严重,故空间复杂度很大.但数组的二分查找时间复杂度小,为O(1),数组的特点是寻址容易,插入和删除困难.今天学习另外的 ...

  7. Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

    概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 Ja ...

  8. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  9. Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. DS18B20数字温度计 (三) 1-WIRE总线 ROM搜索算法和实际测试

    目录 DS18B20数字温度计 (一) 电气特性, 寄生供电模式和远距离接线 DS18B20数字温度计 (二) 测温, ROM和CRC算法 DS18B20数字温度计 (三) 1-WIRE总线 ROM搜 ...

  2. Camunda如何配置和使用mysql数据库

    Camunda默认使用已预先配置好的H2数据库,数据库模式和所有必需的表将在引擎第一次启动时自动创建.如果你想使用自定义独立数据库,比如mysql,请遵循以下步骤: 一.新建mysql数据库 为Cam ...

  3. 物联网微消息队列MQTT介绍-EMQX集群搭建以及与SpringBoot整合

    项目全部代码地址:https://github.com/Tom-shushu/work-study.git (mqtt-emqt 项目) 先看我们最后实现的一个效果 1.手机端向主题 topic111 ...

  4. BUUCTF-BJDCTF2020]just_a_rar

    BJDCTF2020]just_a_rar 压缩包提示是四位数密码 爆破得知压缩包密码 16进制查看解压的图片后发现flag flag{Wadf_123}

  5. Javaweb-Servlet学习

    1.Servlet简介 Servlet就是sun公司开发动态web的一门技术 Sun在这些API中提供一个借口叫做:Servlet,如果你想开发一个Servlet程序,只需要完成两个小步骤: 编写一个 ...

  6. jfinal中如何使用过滤器监控Druid监听SQL执行?

    摘要:最开始我想做的是通过拦截器拦截SQL执行,但是经过测试发现,过滤器至少可以监听每一个SQL的执行与返回结果.因此,将这一次探索过程记录下来. 本文分享自华为云社区<jfinal中使用过滤器 ...

  7. 不花钱~Python制作视频解析免费追剧神器

    同学们在闲暇之余是否喜欢看电影或者电视剧呢? 今天带领大家使用python制作能免费追剧的桌面软件.还在等什么?发车了! 效果我就不再这里演示了https://jq.qq.com/?_wv=1027& ...

  8. 7.Spark SQL

    1.分析SparkSQL出现的原因,并简述SparkSQL的起源与发展. SparkSQL出现是因为关系数据库已经不能满足各种在大数据时代新增的用户需求.首先,用户需要在不同的结构化和非结构化数据中执 ...

  9. [APIO2008]DNA 题解

    题目链接 首先呢,看到 A C G T 对应不同的权值,第一步就是把字母转换成数字. 我们分别对 A->1 C->2 G->3 T->4 进行标号,之后方便 \(\text{d ...

  10. 2m高分辨率土地利用分类数据

    数据下载链接:百度云下载链接 土地利用数据是在根据影像光谱特征,结合野外实测资料,同时参照有关地理图件,对地物的几何形状,颜色特征.纹理特征和空间分布情况进行分析,建立统一解译标志的基础之上,依据多源 ...