|
HashMap 1.8 源码解析服务器域名116.206.102.2香港服务器低延迟
需要服务器咨询以下联系方式
联系人:銳輝(香港)科技 敏敏
扣扣:3007425289/2853898501
电话:18316411879
一个网站的服务器性能比较差,负载能力有限,优势面临突发流量,招架不住,直接导致服务器奔溃,网站打不开,尤其是电商网站在节日期间,因为这种情况网站打不开,导致销售额白白流失。
使用CDN后有什么有好处?
1.不用担心自己网站访客在任何时间,任何地点,任何网络运营商,都能快速打开。
2.各种服务器器虚拟主机带宽等采购成本,包括后期运维成本都会大大减少。
3.给网站直接带来的好处就是:流量,咨询量,客户量,成单量,都会得到大幅度提升。
香港免备案机器那款才是最好,香港机房现全新配置上线,机器硬件可升级,G口cn2线路独独享带宽,国内延迟低,可提升防御,死扛流量攻击,机房策略可针对各种攻击方式,公司售后7*24小时技术在线处理问题。为您的机器保驾护航。
虚拟主机就是集体宿舍 一人脚臭全屋熏天
虚拟服务器就是小户型 麻雀虽小五脏俱全
独立服务器那就是单元房了 我的地盘我做主
我们机房运营商就是开发商物业管理 欢迎您入住
1 / 数据结构
在JDK1.7中HashMap的数据结构是数组 + 链表 , 而在JDK1.8中则演化成了数组 + 链表 + 红黑树的结构 , 这也是1.8中最大的更新 , 下面我们来探究一下为何要演化为数组 + 链表 + 红黑树这样的数据结构
我们知道在1.7中当产生了hash碰撞时便会将当前Entry变成链表 , 单向链表查找除了head节点外的时间复杂度都是O(n) , 如果频繁的发生了hash碰撞每次查找元素都是非常耗费时间的 , 所以为了避免这一现象1.8中引入了红黑树
红黑树的插入、查找的时间复杂度都是O(log n) , 假如你的红黑树里面有256个数据 , 此时只需要8次就能找到目标数据 , 即使是65536个数据也只需要16次即可 , 效率相比链表而言提升的非常大
我们来看下HashMap转为红黑树后存储的数据结构图
现在我们知道了为何要引入红黑树 , 但是这就又衍生了一个问题 , 为什么不在一开始就使用红黑树来直接替代链表呢? 这个问题的答案在源码解析的核心参数中会有详细的解释
这篇文章的重点在于HashMap,有关于红黑树的介绍就暂时到此为止了 , 有兴趣的朋友可以网上找找红黑树的资料 , 我的笔记还在整理中…
/ 2 / 源码解析
2 . 1 核心参数
//默认初始化table数组容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//table最大容量1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子, 即当现有数组长度达到容量的75%时会进行扩容操作
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//1.8新增 当链表的长度 >=8 - 1 时会转换为红黑树, 关于为什么要定义为8的详细解读在下面↓
static final int TREEIFY_THRESHOLD = 8;
//1.8新增 当红黑树的长度 <=6 时会转换为链表, 关于为什么红黑树 → 链表的阈值是6的详细解读在下面↓
static final int UNTREEIFY_THRESHOLD = 6;
//1.8新增 红黑树的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
//定义一个类型为Node<K,V>的table数组
transient Node<K,V>[] table;
//table数组的长度
transient int size;
//实际的扩容的阈值 threshold = 容量 * 加载因子
//在构造器中会被初始化为DEFAULT_INITIAL_CAPACITY的值16
//在第一次存储数据时会在inflateTable()方法中再次赋值threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
int threshold;
//实际的加载因子, 在构造器中进行初始化
//如果创建HashMap时没有指定loadFactor的大小则会初始化为DEFAULT_INITIAL_CAPACITY的值
final float loadFactor;
//HashMap更改的次数
//用来作为并发下判断是否有其它线程修改了该HashMap,抛出ConcurrentModificationException
transient int modCount;
//在初始化时指定初始长度及加载因子的构造器
public HashMap(int initialCapacity, float loadFactor) {
...
}
//在初始化时指定初始长度的构造器
public HashMap(int initialCapacity) {
//这里调用的其实还是上面的构造器
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//什么也不指定的构造器 , 这里不像1.7中还是去调用了有参构造器 , 具体原因下面会有分析
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
我们来重点介绍几个核心参数
TREEIFY_THRESHOLD
这个参数是链表转换成红黑树的阈值 , 但是为什么是8呢?以及我们来看一下上面提到的关于为何不在一开始就用红黑树来替代链表
问 : 为什么不在一开始就使用红黑树来替代链表?
答 : 我们来看一段HashMao源码中的注解
* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins.
在该注解中详细的说明了相同数据量下红黑树(TreeNode)占用的空间是链表(Node)的俩倍 , 考虑到时间和空间的权衡 , 只有当链表的长度达到阈值时才会将其转成红黑树
问 : 为什么链表 → 红黑树的阈值是8呢?
答 : 我们继续来看一段注解
* In usages with well-distributed user hashCodes, tree bins are
* rarely used. Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)). The first values are:
*
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
* more: less than 1 in ten million
HashMap的作者认为在理想的情况下随机hashCode算法下所有节点的分布频率会遵循泊松分布(Poisson distribution) , 上面也列举了链表长度达到8的概率是0.00000006,也就是说我们几乎不可能会使用到红黑树 , 所以作者使用8作为一个分水岭
UNTREEIFY_THRESHOLD
上面已经解释了为何链表 → 红黑树的阈值是8,这里我们来解释一下为何链表 → 红黑树的阈值却是6
问 : 为何链表 → 红黑树的阈值是6
答 : 假设UNTREEIFY_THRESHOLD的 = 7 , 当我们有频繁的添加和删除操作时 ,
hash碰撞产生的节点数量 一旦在7附件徘徊就会造成红黑树和链表的频繁转
换 , 此时我们大多数的性能就都耗费在了链表 → 红黑树和红黑树 → 链表` ,
这样反而就得不偿失了 , 所以作者将长度为7作为一个缓存地段从而选取了6
作为红黑树 → 链表的阈值
loadFactor
我们通常使用的构造器都是最后一个构造器 , 什么都不会传 , 如果我们需要更改加载因子的话需要注意几个点
加载因子并不是越大越好的 , 虽然加载因子越大就意味着HashMap的实际容量越大 , 扩容的次数越少 , 但是因为实际存储的数据大了 , 俩个相同容量的HashMap加载因子越大的那个读取的速度更慢 , 所以我们需要根据自己的实际使用情况来进行判断 , 是要存储更多的数据呢 , 还是要更快的读取速度
加载因子是会影响到扩容的次数的 , 如果加载因子太小的话HashMap会频繁的进行扩容 , 导致在存储的时候性能下降
如果我们在创建HashMap时就已经知道了要存储的数据量 , 那么我们完全可以通过实际存储数量 ÷ 0.75来计算出我们初始化的HashMap容量 , 这样可以避免HashMap再进行扩容操作 , 提升代码效率
modCount
关于modCount这里做一下解释 , 这个元素是用来干什么的呢?
我们知道HashMap不是线程安全的 , 也就是说你在操作的同时可能会有其它的线程也在操作该map,那样会造成脏数据 , 所以为了避免这种情况发生HashMap、ArrayList等使用了fail-fast策略 , 用modCount来记录修改集合修改次数
我们在边迭代边删除集合元素时会碰到一个异常ConcurrentModificationException , 原因是不管你使用entrySet()方法也好 , keySet()方法也好 , 其实在for循环的时候还是会使用该集合内置的Iterator迭代器中的nextEntry()方法 , 如果你没有使用Iterator内置的remove()方法 , 那么迭代器内部的记录更改次数的值便不会被同步 , 当你下一次循环时调用nextEntry()方法便会抛出异常
|
|