Java八股复习指南-集合

Java集合

Map

HashMap

实现原理/底层

Java1.8之前:数组加链表

Java1.8之后:当一个链表的长度超过8,且数组大小超过64时,会将链表转换成红黑树存储,查找效率更高,时间复杂度O(log n)。如果长度超过8,但是数组容量不足64,则会选择扩容数组。

定位算法

计算key的哈希值,并进行与运算

int index = hash & (table.length - 1);

327b6ce496363ad358efda4838aff0d1

扩容机制

HashMap扩容因子为0.75,当元素个数超过总容量的75%时,则会触发扩容。

主要分为两个步骤:

  1. 对哈希表长度扩展成2倍

  2. 将旧哈希表中的数据放入新的表中

为什么是2倍?如何放入新表?

1713514753772-9467a399-6b18-4a47-89d4-957adcc53cc0

通过将长度进行2次幂扩容,在重新计算hash值时,只需要看新增位是1还是0,是0则索引不变,是1则变成“原索引+oldCap”。这样既节省了重新计算hash的时间,同时也将冲突的节点均匀分散了。

put方法

1720684054342-1e3cb2a9-532e-40b8-b5cf-0043811391dc

  • 根据要添加的键的哈希码计算在数组中的索引
  • 判断对应位置是否有键值对

如果为空,则根据键值对直接创建Entry对象,把该对象存入相应位置。

  • 如果该位置存在其他键值对,检查第一个键值对的哈希码和键是否与要添加的键值对相同(equals()和hashcode())

如果相同,说明添加的是同一个键,使用新值替换旧值,完成更新

  • 如果不相同,则继续遍历链表或红黑树,查找是否有相同的键

如果有相同的值,则使用新值替换旧值,完成更新。

如果没有相同的值,则将新的键值对添加到链表或红黑树。

  • 检查链表长度是否超过阈值(8)

如果链表长度超过8,且数组容量超过64,则会将链表转换成红黑树。

  • 检查负载因子是否超过阈值(0.75)

如果键值对的数量与数组的长度大于阈值,则触发扩容。(链表长度>8,数组容量<64也会触发扩容)

  • 扩容操作

  • 完成添加操作

ConcurrentHashMap

底层实现:

Java1.8之前:底层实现是数组+链表的形式(Segment[]和HashEntry[])

使用分段锁,将整个哈希表分成多个段,每个段都维护自己的哈希表和锁。

Java1.8之后:底层实现是数组+链表/红黑树的形式

主要通过 volatile + CAS 或者 synchronized 来实现的线程安全的。

加锁机制:

分段锁:

将整个数据结构分为多个Segment后,每个Segment都有自己的锁,对不同Segment的操作互不影响,从而提升并发性能。

乐观锁/悲观锁

添加元素时,判断计算的hash是否有元素,如果没元素,则会使用乐观锁CAS,如果发生了hash碰撞则使用悲观锁synchronized。这是因为发生hash碰撞时,大概率是线程竞争比较激烈的时候,使用悲观锁保证线程安全。

posted @ 2024-09-11 09:56  forest-pan  阅读(3)  评论(0编辑  收藏  举报