currenthashmap扩容原理_HashMap 深入解析(二)

本文详细解析HashMap的扩容原理,指出装填因子默认为0.75,当元素数量达到容量的75%时触发扩容,长度翻倍。讨论了为何HashMap长度必须为2的倍数以提高效率。同时,提到了并发环境下HashMap可能导致的死循环问题,以及Java8中引入的红黑树优化。HashMap与HashSet的关系也得以阐述,HashSet基于HashMap的Key实现。最后介绍了CurrentHashMap的线程安全机制,使用锁分段技术提高并发性能。
摘要由CSDN通过智能技术生成

共两篇,本文是第二篇,包含后六节。ps:你看到的是我写的第二遍!~ 坑 die 的有道云,写完之后居然给我清空了,无力吐槽

目录

引言

基本存储结构

Put 方法原理

Get 方法原理

装填因子默认值及作用

HashMap 默认长度及原因

HashMap 的线程安全问题

Java8 中 HashMap 的优化

HashMap 和 HashSet 的关系

线程安全的 HashMap:CurrentHashMap 简介

装填因子默认值及作用

/**

* The load factor used when none specified in constructor.

*/

static final float DEFAULT_LOAD_FACTOR = 0.75f;

装填因子默认是 0.75,当 put 值时

// The next size value at which to resize (capacity * load factor).

threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);

// 如果大于下一个重置大小的值 则把数组扩大一倍

if ((size >= threshold) && (null != table[bucketIndex])) {

resize(2 * table.length);

hash = (null != key) ? hash(key) : 0;

bucketIndex = indexFor(hash, table.length);

}

意思是说,当数组中的元素的个数 >=总长度*装填因子时,数组长度扩容一倍

HashMap 默认长度及原因

/**

* The default initial capacity - MUST be a power of two.

*/

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

hashmap 的默认长度是 16,而且长度必须是 2 的倍数

private void inflateTable(int toSize) {

// Find a power of 2 >= toSize

int capacity = roundUpToPowerOf2(toSize);

threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);

table = new Entry[capacity];

initHashSeedAsNeeded(capacity);

}

// 把不是2的倍数的数转换成 > 原始值的2的倍数

private static int roundUpToPowerOf2(int number) {

// assert number >= 0 : "number must be non-negative";

return number >= MAXIMUM_CAPACITY

? MAXIMUM_CAPACITY

: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;

}

之所以必须是 2 的倍数,是因为 HashMap 进行 Entry 数组定位的时候,不是使用的取模操作,而是进行的位操作默认取 hash 值的后四位。

例如:hash 值为 12345 Entry 数组长度是 16 该 hash 在数组中的位置计算不是 12345 mod 16 = 9 而是 12345 & 15 = 9,虽然结果是一样的,但是计算效率会更高,速度更快。

HashMap 的线程安全问题

HashMap 在并发编程中可能导致程序死循环

在多线程环境下,使用 HashMap 进行 put 操作时,如果多个线程同时进行扩容操作,有可能会使链表形成闭环,造成获取 Entry 时死循环,导致 CPU 利用率接近 100%。主要是因为 HashMap 在插入 Entry 的时候是插在链表头的而不是链表尾,具体原因参考:https://www.cnblogs.com/andy-zhou/p/5402984.html

Java8 中 HashMap 的优化

JDK1.8 中 HashMap 是数组 + 链表 + 红黑树实现的,链表长度是大于 8 的话把链表转换为红黑树

JDK1.8 种优化了扩容机制

JDK1.8 中优化了高位运算的算法

等等 虽然有点贱,但是的确还有很多 ~

HashMap 和 HashSet 的关系

// 构造方法中就是初始化一个默认的HashMap

public HashSet() {

map = new HashMap<>();

}

private static final Object PRESENT = new Object();

// 增加元素 就是在HashMap中增加一个Key为增加的元素,值为Object实例的键值对

public boolean add(E e) {

return map.put(e, PRESENT)==null;

}

// 判断是否存在也是判断元素是否在HashMap中是否存在

public boolean contains(Object o) {

return map.containsKey(o);

}

综上,HashSet 是通过 HashMap 的 Key 实现的

线程安全的 HashMap:CurrentHashMap 简介

HashMap 存在线程安全问题,HashTable 虽然是线程安全的,但是效率太低。所以在多线程的环境下我们一般使用 CurrentHashMap。

CurrentHashMap 使用了锁分段技术,首先把数据分成一段一段地存储,然后给每一段都加上一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

有机会的话我会写一篇文章仔细分析,埋个坑,敬请期待 :)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值