mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-08-05 20:31:37 +08:00
[docs update]java集合部分内容完善
This commit is contained in:
parent
f28c32fb95
commit
8d77986e7c
@ -1 +1 @@
|
||||
<mxfile host="Electron" modified="2022-08-11T10:29:38.995Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="EaH9fKq6YaRBX68HPPyn" version="13.4.5" type="device"><diagram id="mPrK13Eo7WhzfbSKrwnZ" name="Page-1">7Vtbd6M2EP41PGYPiIvh0XbsbNpkkx6nl32UQVw2MnKFHNv99RUgbED4kqxt3JSThzAjIcF8M6NvJKPow9nqjsJ5+Eg8hBWgeitFv1UA0Bxd5/9SzTrXOE4vVwQ08kSnrWIS/YOEUhXaReShpNKREYJZNK8qXRLHyGUVHaSULKvdfIKrs85hgCTFxIVY1v4ZeSzMtTbobfVfURSExcya5eQtM1h0Fm+ShNAjy5JKHyn6kBLC8qvZaohwarzCLvl94x2tmwejKGbH3ODfjqI7FuNZ3NcfXp71iT7q3xji2di6eGHk8fcXIqEsJAGJIR5ttQNKFrGH0lFVLm37PBAy50qNK38gxtYCTLhghKtCNsOilT8wXf+V3v/FLMTvYrhMuF1VpLWQfBIzMahmcXlj0bQxeUXMDcUMsm2EuRKyoC7aY5DCxyANENvTD+T9UmuVJhCWv0NkhviT8w4UYciit6o3QeGUwaafuLVPKVyXOsxJFLOkNPJzquAdRHzplnAuEV163Qdq/e293flF/gCFVHqTrSpzq3e4mNmGi9Vd5aMu16aL6f9JF7O0FnxMvPQbxAthhpeQ53445d5U976tb6UwLsOIockcZogt+Rq234/8COMhwYRmY+keRLbvpq7BKHlFpRbLtdHU/5gHvSHK0Gov5gU4atXYwBDycrtWacUCFJbWqQLUJjcpQfV+JAC4hnA/R9iCY1cG9dRx+3OA6K0s8auIldItl76XWrbJNhUuubwfDaJ2XSBK+W20ctGcRSQ+b34zke0ZTfnNBlPd+iBKx+c3UFtLrLbTm/VZs9t5yMZBdmCqNaTyyBR31cA6AVHQ5UDiRSI9axD5tovcRpIwtU3D/CDNPD6ITPPaoqgnofCpATCcHU5fAmDD3MoAAPNcCDgSAk8L9uQ/ohmh6+aQ4C/LqmavmjMmMarZXqggjoKYiy43IeL6QWq6yIW4LxpmkedlqbIJ52r6PEEufEfkaGYVOL0hcpwm4M6FW5E+S8D9EVG2gPgRumEUow67XdhZZtvYyVXqhEH39Ym/1BiTZQfdDuishnx5YegMCbr7pw6vHXj1tNbxMg8zDBR7/fSAYAuAB5NwQzlKAKb6Z8g4KHGmAaq+gbU4FACnMfpB/l0yqdlg0UJ3Ypreq9P0vJ6QaPrBgaw6pzwz39dsyRH6SZLGFYm7+N2Vb1unOUYrhwbXfC5VmPrwzpV5ZCa50M6VTFiHIXJfOU5Avcwmlu/7oLn886ypZZ49vG60ev3XsD5uDrwvUoAb3bnvjg3fw/FlXVd8yUXF77F78QhD1o4I6zlT9ewbLJpxbQEGjiCgnyrHAbXGIRo2GZv3uIyzYWBdGoOWowBcHwTyTi+vm3fmpP8h9+YVSq1Cctom30CumYYYJsk3wsapoTr89uHXa714AvLm/uS3hw61vaj12kZNlwulcYRRF3THwGc3HWleFj75ZFlCqttxLM1Q2yi8savkxa4ffR674ygNdOlfGMhHBX0asZCPG7ldFO+p2tpnPrpctWXMZwgT1kG3B7r2SY8uF3v3GKMA4peQIuhNGGSow7CMoXl1FEgmrvfcOqvspymD1FJJh+AeBDdrZnvnNqBjQT/DgoBhfDkRD2oY6nxM6GX44n4N1F9NL/wG2MQfsTVt+ChDGZkKp2f9oTIylMFQcXqZZqwM+MU40/R/gW/wLv3oTwEWTiN6Siv+Y/29SL+Yy0C/STLU+7yDZs1XGcBFO78Ksv+jnjK4VRwtvegPFMfZTPWDT5V9X/jFvdZ8guEU4QF0X4NMX5+8viHJ5dK24yD7y8bkq1+aNBtCohIsOzJQQ8js/sl27bDLAA1JqdcQQtr7kxIXt99P5m67/QpVH/0L</diagram></mxfile>
|
||||
<mxfile host="Electron" modified="2022-08-11T12:50:32.532Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="EW2-PGcUJKY6Z6AQ4-jf" version="13.4.5" type="device"><diagram id="mPrK13Eo7WhzfbSKrwnZ" name="Page-1">7Vtde6o4EP41XPY8Er4v1WpPd9vT7mP341xGCMJpJG6IVffXb4CgQPCjPSJul6cXZSYhgXlnJu8komjD+fqOwkXwSDyEFdDz1op2qwCgOprG/yWaTaZxHCtTzGjoiU47xST8BwllT2iXoYfiUkdGCGbhoqx0SRQhl5V0kFKyKnfzCS7PuoAzJCkmLsSy9s/QY0GmtYG1039F4SzIZ1ZNJ2uZw7yzeJM4gB5ZFVTaSNGGlBCWXc3XQ4QT4+V2ye4b72ndPhhFETvlBv92FN6xCM+jvvbw8qxNtFH/RhfPxjb5CyOPv78QCWUBmZEI4tFOO6BkGXkoGbXHpV2fB0IWXKly5Q/E2EaACZeMcFXA5li08gemm7+S+78YufhdDJcKt+uStBGSTyImBlVNLm8tmjTGr4i5gZhBto0wV0yW1EUHDJL7GKQzxA70A1m/xFqFCYTl7xCZI/7kvANFGLLwrexNUDjlbNtP3NqnFG4KHRYkjFhcGPk5UfAOIr40UziXiC6t6gOV/vbB7vwie4BcKrzJTpW61TtczGjDxaqu8lGXa9PFtP+ki5lqCz4mXvoN4qUww0vAcz+ccm+qet/OtxIYV0HI0GQBU8RWfA077Ed+iPGQYELTsTQPItt3E9dglLyiQovp2mjqf8yD3hBlaH0Q8xycXtnYQBfyardWqfkCFBTWqRzUOjcpQPV+JAC4hnBvImzBqStD79xx+3OAaK0s8euQFdItl74XWnbJNhEuubyfDKJ6XSBK+W20dtGChSRqNr8ZyPb0uvxmg6lmfhCl0/MbqKwlZtvpzfys2a0ZsnGUHRi9ClJZZIq7KmCdgShociDxIpE2GkS+7SK3liRMbUM3PkgzTw8iw7i2KLIkFD41ALqzx+kLAGyZWxEAYDSFgCMh8LRkT/4jmhO6qQ8J/rKsbPayOSMSoYrthQricBZx0eUmRFw/SEwXuhD3RcM89Lw0VdbhXE6fZ8iF74gc1SgDp9VEjlMHXFO45emzANwfIWVLiB+hG4QR6rDbh51ptI2dXKVOGHRfn/hLjTFZddDtgc6syZcXhk6XoLt/6vDag5elto6XcZxhoMjrJwcEOwA8GAdbylEAMNE/Q8ZBiVIN6GlbWPNDAXAeox/l3wWTGjUWzXVnpulWlaZn9YRE048OZFY5ZcN8X7UlR+jHcRJXJOrid1++bZ3m6K0cGlzzuVRu6uM7V8aJmeRCO1cyYR0GyH3lOIHeZTaxfN8H9eWfZ05No/HwulGr9V/N+rg98L5IAa535757NnyPx5d5XfElFxW/R+7FIwyZeyLMcqa9xjdYVP3aAgycQEA/VY4DvQqHqNlkrN/j0hvDwLw0Bi1HAbg+COSdXl43781J/0PuzSuUSoXktE2+gVwzDTGM42+EjRNDdfgdws9qvXgC8ub+5LeHDrWDqFlto6bJhdI4xKgLulPgs+uONC8Ln3yyLCHV7TgWZqhsFN7YZfJiV48+T91xlAa69C8M5KOCPg1ZwMcN3S6KD1Rt7TMfTa7aUuYzhDHroDsAXfukR5OLvXuM0Qzil4Ai6E0YZKjDsIihcXUUSCau99w66/SnKYPEUnGH4AEEt2tme+c2oGNBP8OCgK5/ORMPqhmqOSb0Mnxxv856vxpe8A2wiT9iG1rzUYYyMhROz/pDZaQrg6HiWKlmrAz4xTjV9H+Bb/Au+ehPASZOInpKS/5j/r1MvphLQb+JU9T7vINqLtYpwHk7v5ql/0eWMrhVHDW56A8Ux9lO9YNPlX5f+MW91nyC4RThAXRfZ6m+Onl1Q5LLhW3HQfqXjslXvyRp1oREKVjOkJRA5bBLBzVJyaoJIfX9SYmLu+8nM7fdfYWqjf4F</diagram></mxfile>
|
@ -16,7 +16,7 @@ head:
|
||||
|
||||
**Java 异常类层次结构图概览** :
|
||||
|
||||

|
||||

|
||||
|
||||
### Exception 和 Error 有什么区别?
|
||||
|
||||
|
@ -14,15 +14,13 @@ tag:
|
||||
|
||||
### 1. 存储结构
|
||||
|
||||
> 下图存在两个笔误 : Segmeng -> Segment ; HashEntity -> HashEntry
|
||||

|
||||
|
||||

|
||||
|
||||
Java 7 中 `ConcurrentHashMap` 的存储结构如上图,`ConcurrnetHashMap` 由很多个 `Segment` 组合,而每一个 `Segment` 是一个类似于 HashMap 的结构,所以每一个 `HashMap` 的内部可以进行扩容。但是 `Segment` 的个数一旦**初始化就不能改变**,默认 `Segment` 的个数是 16 个,你也可以认为 `ConcurrentHashMap` 默认支持最多 16 个线程并发。
|
||||
Java 7 中 `ConcurrentHashMap` 的存储结构如上图,`ConcurrnetHashMap` 由很多个 `Segment` 组合,而每一个 `Segment` 是一个类似于 `HashMap` 的结构,所以每一个 `HashMap` 的内部可以进行扩容。但是 `Segment` 的个数一旦**初始化就不能改变**,默认 `Segment` 的个数是 16 个,你也可以认为 `ConcurrentHashMap` 默认支持最多 16 个线程并发。
|
||||
|
||||
### 2. 初始化
|
||||
|
||||
通过 ConcurrentHashMap 的无参构造探寻 ConcurrentHashMap 的初始化流程。
|
||||
通过 `ConcurrentHashMap` 的无参构造探寻 `ConcurrentHashMap` 的初始化流程。
|
||||
|
||||
```java
|
||||
/**
|
||||
@ -101,11 +99,11 @@ public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLe
|
||||
总结一下在 Java 7 中 ConcurrnetHashMap 的初始化逻辑。
|
||||
|
||||
1. 必要参数校验。
|
||||
2. 校验并发级别 concurrencyLevel 大小,如果大于最大值,重置为最大值。无参构造**默认值是 16.**
|
||||
3. 寻找并发级别 concurrencyLevel 之上最近的 **2 的幂次方**值,作为初始化容量大小,**默认是 16**。
|
||||
4. 记录 segmentShift 偏移量,这个值为【容量 = 2 的N次方】中的 N,在后面 Put 时计算位置时会用到。**默认是 32 - sshift = 28**.
|
||||
5. 记录 segmentMask,默认是 ssize - 1 = 16 -1 = 15.
|
||||
6. **初始化 segments[0]**,**默认大小为 2**,**负载因子 0.75**,**扩容阀值是 2*0.75=1.5**,插入第二个值时才会进行扩容。
|
||||
2. 校验并发级别 `concurrencyLevel` 大小,如果大于最大值,重置为最大值。无参构造**默认值是 16.**
|
||||
3. 寻找并发级别 `concurrencyLevel` 之上最近的 **2 的幂次方**值,作为初始化容量大小,**默认是 16**。
|
||||
4. 记录 `segmentShift` 偏移量,这个值为【容量 = 2 的N次方】中的 N,在后面 Put 时计算位置时会用到。**默认是 32 - sshift = 28**.
|
||||
5. 记录 `segmentMask`,默认是 ssize - 1 = 16 -1 = 15.
|
||||
6. **初始化 `segments[0]`**,**默认大小为 2**,**负载因子 0.75**,**扩容阀值是 2*0.75=1.5**,插入第二个值时才会进行扩容。
|
||||
|
||||
### 3. put
|
||||
|
||||
@ -179,23 +177,23 @@ private Segment<K,V> ensureSegment(int k) {
|
||||
}
|
||||
```
|
||||
|
||||
上面的源码分析了 ConcurrentHashMap 在 put 一个数据时的处理流程,下面梳理下具体流程。
|
||||
上面的源码分析了 `ConcurrentHashMap` 在 put 一个数据时的处理流程,下面梳理下具体流程。
|
||||
|
||||
1. 计算要 put 的 key 的位置,获取指定位置的 Segment。
|
||||
1. 计算要 put 的 key 的位置,获取指定位置的 `Segment`。
|
||||
|
||||
2. 如果指定位置的 Segment 为空,则初始化这个 Segment.
|
||||
2. 如果指定位置的 `Segment` 为空,则初始化这个 `Segment`.
|
||||
|
||||
**初始化 Segment 流程:**
|
||||
|
||||
1. 检查计算得到的位置的 Segment 是否为null.
|
||||
2. 为 null 继续初始化,使用 Segment[0] 的容量和负载因子创建一个 HashEntry 数组。
|
||||
3. 再次检查计算得到的指定位置的 Segment 是否为null.
|
||||
4. 使用创建的 HashEntry 数组初始化这个 Segment.
|
||||
5. 自旋判断计算得到的指定位置的 Segment 是否为null,使用 CAS 在这个位置赋值为 Segment.
|
||||
1. 检查计算得到的位置的 `Segment` 是否为null.
|
||||
2. 为 null 继续初始化,使用 `Segment[0]` 的容量和负载因子创建一个 `HashEntry` 数组。
|
||||
3. 再次检查计算得到的指定位置的 `Segment` 是否为null.
|
||||
4. 使用创建的 `HashEntry` 数组初始化这个 Segment.
|
||||
5. 自旋判断计算得到的指定位置的 `Segment` 是否为null,使用 CAS 在这个位置赋值为 `Segment`.
|
||||
|
||||
3. Segment.put 插入 key,value 值。
|
||||
3. `Segment.put` 插入 key,value 值。
|
||||
|
||||
上面探究了获取 Segment 段和初始化 Segment 段的操作。最后一行的 Segment 的 put 方法还没有查看,继续分析。
|
||||
上面探究了获取 `Segment` 段和初始化 `Segment` 段的操作。最后一行的 `Segment` 的 put 方法还没有查看,继续分析。
|
||||
|
||||
```java
|
||||
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
|
||||
@ -249,29 +247,29 @@ final V put(K key, int hash, V value, boolean onlyIfAbsent) {
|
||||
}
|
||||
```
|
||||
|
||||
由于 Segment 继承了 ReentrantLock,所以 Segment 内部可以很方便的获取锁,put 流程就用到了这个功能。
|
||||
由于 `Segment` 继承了 `ReentrantLock`,所以 `Segment` 内部可以很方便的获取锁,put 流程就用到了这个功能。
|
||||
|
||||
1. tryLock() 获取锁,获取不到使用 **`scanAndLockForPut`** 方法继续获取。
|
||||
1. `tryLock()` 获取锁,获取不到使用 **`scanAndLockForPut`** 方法继续获取。
|
||||
|
||||
2. 计算 put 的数据要放入的 index 位置,然后获取这个位置上的 HashEntry 。
|
||||
2. 计算 put 的数据要放入的 index 位置,然后获取这个位置上的 `HashEntry` 。
|
||||
|
||||
3. 遍历 put 新元素,为什么要遍历?因为这里获取的 HashEntry 可能是一个空元素,也可能是链表已存在,所以要区别对待。
|
||||
3. 遍历 put 新元素,为什么要遍历?因为这里获取的 `HashEntry` 可能是一个空元素,也可能是链表已存在,所以要区别对待。
|
||||
|
||||
如果这个位置上的 **HashEntry 不存在**:
|
||||
如果这个位置上的 **`HashEntry` 不存在**:
|
||||
|
||||
1. 如果当前容量大于扩容阀值,小于最大容量,**进行扩容**。
|
||||
2. 直接头插法插入。
|
||||
|
||||
如果这个位置上的 **HashEntry 存在**:
|
||||
如果这个位置上的 **`HashEntry` 存在**:
|
||||
|
||||
1. 判断链表当前元素 Key 和 hash 值是否和要 put 的 key 和 hash 值一致。一致则替换值
|
||||
1. 判断链表当前元素 key 和 hash 值是否和要 put 的 key 和 hash 值一致。一致则替换值
|
||||
2. 不一致,获取链表下一个节点,直到发现相同进行值替换,或者链表表里完毕没有相同的。
|
||||
1. 如果当前容量大于扩容阀值,小于最大容量,**进行扩容**。
|
||||
2. 直接链表头插法插入。
|
||||
|
||||
4. 如果要插入的位置之前已经存在,替换后返回旧值,否则返回 null.
|
||||
|
||||
这里面的第一步中的 scanAndLockForPut 操作这里没有介绍,这个方法做的操作就是不断的自旋 `tryLock()` 获取锁。当自旋次数大于指定次数时,使用 `lock()` 阻塞获取锁。在自旋时顺表获取下 hash 位置的 HashEntry。
|
||||
这里面的第一步中的 `scanAndLockForPut` 操作这里没有介绍,这个方法做的操作就是不断的自旋 `tryLock()` 获取锁。当自旋次数大于指定次数时,使用 `lock()` 阻塞获取锁。在自旋时顺表获取下 hash 位置的 `HashEntry`。
|
||||
|
||||
```java
|
||||
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
|
||||
@ -311,7 +309,7 @@ private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
|
||||
|
||||
### 4. 扩容 rehash
|
||||
|
||||
ConcurrentHashMap 的扩容只会扩容到原来的两倍。老数组里的数据移动到新的数组时,位置要么不变,要么变为 index+ oldSize,参数里的 node 会在扩容之后使用链表**头插法**插入到指定位置。
|
||||
`ConcurrentHashMap` 的扩容只会扩容到原来的两倍。老数组里的数据移动到新的数组时,位置要么不变,要么变为 `index+ oldSize`,参数里的 node 会在扩容之后使用链表**头插法**插入到指定位置。
|
||||
|
||||
```java
|
||||
private void rehash(HashEntry<K,V> node) {
|
||||
@ -406,7 +404,7 @@ public V get(Object key) {
|
||||
|
||||
### 1. 存储结构
|
||||
|
||||

|
||||

|
||||
|
||||
可以发现 Java8 的 ConcurrentHashMap 相对于 Java7 来说变化比较大,不再是之前的 **Segment 数组 + HashEntry 数组 + 链表**,而是 **Node 数组 + 链表 / 红黑树**。当冲突链表达到一定长度时,链表会转换成红黑树。
|
||||
|
||||
@ -442,7 +440,7 @@ private final Node<K,V>[] initTable() {
|
||||
}
|
||||
```
|
||||
|
||||
从源码中可以发现 ConcurrentHashMap 的初始化是通过**自旋和 CAS** 操作完成的。里面需要注意的是变量 `sizeCtl` ,它的值决定着当前的初始化状态。
|
||||
从源码中可以发现 `ConcurrentHashMap` 的初始化是通过**自旋和 CAS** 操作完成的。里面需要注意的是变量 `sizeCtl` ,它的值决定着当前的初始化状态。
|
||||
|
||||
1. -1 说明正在初始化
|
||||
2. -N 说明有N-1个线程正在进行扩容
|
||||
@ -541,7 +539,7 @@ final V putVal(K key, V value, boolean onlyIfAbsent) {
|
||||
|
||||
5. 如果都不满足,则利用 synchronized 锁写入数据。
|
||||
|
||||
6. 如果数量大于 `TREEIFY_THRESHOLD` 则要执行树化方法,在treeifyBin中会首先判断当前数组长度≥64时才会将链表转换为红黑树。
|
||||
6. 如果数量大于 `TREEIFY_THRESHOLD` 则要执行树化方法,在 `treeifyBin` 中会首先判断当前数组长度≥64时才会将链表转换为红黑树。
|
||||
|
||||
### 4. get
|
||||
|
||||
@ -583,12 +581,12 @@ public V get(Object key) {
|
||||
|
||||
总结:
|
||||
|
||||
总的来说 ConcurrentHashMap 在 Java8 中相对于 Java7 来说变化还是挺大的,
|
||||
总的来说 `ConcurrentHashMap` 在 Java8 中相对于 Java7 来说变化还是挺大的,
|
||||
|
||||
## 3. 总结
|
||||
|
||||
Java7 中 ConcurrentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。
|
||||
Java7 中 `ConcurrentHashMap` 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 `Segment` 都是一个类似 `HashMap` 数组的结构,它可以扩容,它的冲突会转化为链表。但是 `Segment` 的个数一但初始化就不能改变。
|
||||
|
||||
Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 **Segment 数组 + HashEntry 数组 + 链表** 进化成了 **Node 数组 + 链表 / 红黑树**,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
|
||||
Java8 中的 `ConcurrentHashMap` 使用的 `Synchronized` 锁加 CAS 的机制。结构也由 Java7 中的 **`Segment` 数组 + `HashEntry` 数组 + 链表** 进化成了 **Node 数组 + 链表 / 红黑树**,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
|
||||
|
||||
有些同学可能对 Synchronized 的性能存在疑问,其实 Synchronized 锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 Synchronized 的**锁升级**。
|
||||
有些同学可能对 `Synchronized` 的性能存在疑问,其实 `Synchronized` 锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 `Synchronized` 的**锁升级**。
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2022-08-11T23:13:39.447Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="1SSSpNWCcrVkbHT7kLUn" version="13.4.5" type="device"><diagram id="oZhQm8CpKlBOiSaDXld-" name="Page-1">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</diagram></mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB |
@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2022-08-12T00:27:09.339Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="O27uhhJHVBRjI3i60NQI" version="13.4.5" type="device"><diagram id="Y7qozskM6-LQTmM2nR-R" name="Page-1">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</diagram></mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
1
docs/java/collection/images/jdk1.7_hashmap.drawio
Normal file
1
docs/java/collection/images/jdk1.7_hashmap.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2022-08-11T13:21:25.948Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="uplkEXfcB5SYh6XxKwOW" version="13.4.5" type="device"><diagram id="AhvkeES_NzU0VgdTypUe" name="Page-1">7Vtdj5s4FP01fmzFd+AxEKbtql1VO5V22zcHHKDjYNZxJkl//V6ICSGQmWSVAJqiiTT29Re+5x5jDgbp3nL7geMs/sJCQpGmhFukz5Cmqapiwb/csttbHGeyN0Q8CWWlyvCY/CLSqEjrOgnJqlZRMEZFktWNAUtTEoiaDXPONvVqC0bro2Y4Ig3DY4Bp0/p3Eoq4nJflVAUfSRLF5dCWaexLlrisLaeyinHINkcm3Ue6xxkT+9Ry6xGae690zL7dw5nSw5VxkopLGljB14/6F5r8Sqw/f3z6oSvfvn5/J3t5xnQtZywvVuxKF8B1Z3lS4HlucmOxpJBVIbmJE0EeMxzk5RuIALCtBOZCAqlAHpAROEkJl20CRinOVknR2b5GnNDwM96xtSiHKXPu6omIIJZNFwmlHqOMFxemh5jYi6AYkrMnclRiBTaZL6CEs3UaklC2l3MlXJDtWSeqB2ggqAlbEsF3UEU20ByJpoxnfSLzmyo6LGmKj+JCU6QRy4CMDl1XkEFConYFgtrlCMI0RYLpX0AUnEaXgNkEK+Qs+4Z5RIQ0ZCxJBeH+M3hwJW3HQKUszQcSLJOFlCzKtnMmBFvKDJfeOnRaeMp04Qe+85T3JjJhNh7k1SoPv7w6Fx5LIQ4g0vI+CF6JDVk1IuiiCDhPlGZYvAK7cS/U9fuiXiymuEKdgcsWtFi94iQMSXoLkG+EjDYsZIyRjz3ycVdHt68gMEd61vgwHHpaIz37p6fdMz0nIz1rfBgOPe2Rnv3TU9V65qcz8rNGiOHws9RlRoL2SlCrZ4KqV+hGb52hqtKOVW/YtClCFs2Dc8FgJscgWf+uWVnwblWIdlOooNrZtiqEVFT89y3kmAjo4U+Q6yLbKPuF69x3LSuehgL4VNRhr0t2Er4WRDFNIgB7FpCc9WDIEUoCTKeyYAnhUMRUWyBV8t//w/tlLVAvNb1SC9SbrDRakNfuhvwVqlAvam5djm3ItuHEmSunCzrYFwtiBcEdEDS0EwRb1NzDZqgb8o7yUTf3V/3VRbtj4EfJ6IQEA7qj3lk0egvYbOu49PZeRG0gQ8KIPMosLCwxi1iKqV9ZT7YJVZ3PrPBw7rCfRIidvA/itWB1WMFlfPePbF9kvucZWNJkdrY9LpztzmOSX+3LdzOYHFvzgLzgBRmvolzYz4LptIPJCcUiea5fyO3flIxYVe9vX8fqDPG6wUq9QpIZt5YtW0vDbK6MrU8Hd1satVG66WZreWZR7Q/4Uaw5IcFwtpYlFCM257EZyNZSG8/cXApV7y+QtDvLKG8Jq75fxmtXKB/j1rJla2m26M4HVLuBcDxV08nWck+Vl1XLboEfT9KckGBAW8s7n6V5C9gMZWs5Hni5FKret5ZGE5oOVMt7qY/leZ5X5cdzt56OpOK2Z6/bnGlwkKMj10e+jaYqmtq/95mGxhvxvs80GC1Pcr6JbBNNPeQbyPWQMyksD8iFxENhmf6Bn/GH/NvBEs45vyhILAiSIjoacTJB7gw5ap6YushxDkP9hKGKzxTfB+lAo4TiOaEuDp6iwn46OPhALjyqJfNHzzFu8Vf0KYDqLG0LvcPHjcpt4tA0TuLQNpvPOJOWQFSvD0TIVl9hFmVHH7Pq/n8=</diagram></mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
1
docs/java/collection/images/jdk1.8_hashmap.drawio
Normal file
1
docs/java/collection/images/jdk1.8_hashmap.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2022-08-11T13:34:59.439Z" agent="5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="UrMpCepcFkqqf0A1lrSf" version="13.4.5" type="device"><diagram id="AhvkeES_NzU0VgdTypUe" name="Page-1">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</diagram></mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.2 KiB |
@ -20,7 +20,7 @@ Java 集合, 也叫作容器,主要是由两大接口派生而来:一个
|
||||
|
||||
Java 集合框架如下图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
注:图中只列举了主要的继承派生关系,并没有列举所有关系。比方省略了`AbstractList`, `NavigableSet`等抽象类以及其他的一些辅助类,如想深入了解,可自行查看源码。
|
||||
@ -85,13 +85,13 @@ Java 集合框架如下图所示:
|
||||
|
||||
### ArrayList 与 LinkedList 区别?
|
||||
|
||||
1. **是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全;
|
||||
2. **底层数据结构:** `ArrayList` 底层使用的是 **`Object` 数组**;`LinkedList` 底层使用的是 **双向链表** 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
|
||||
3. **插入和删除是否受元素位置的影响:**
|
||||
- **是否保证线程安全:** `ArrayList` 和 `LinkedList` 都是不同步的,也就是不保证线程安全;
|
||||
- **底层数据结构:** `ArrayList` 底层使用的是 **`Object` 数组**;`LinkedList` 底层使用的是 **双向链表** 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
|
||||
- **插入和删除是否受元素位置的影响:**
|
||||
- `ArrayList` 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行`add(E e)`方法的时候, `ArrayList` 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element)`)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
|
||||
- `LinkedList` 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响(`add(E e)`、`addFirst(E e)`、`addLast(E e)`、`removeFirst()` 、 `removeLast()`),时间复杂度为 O(1),如果是要在指定位置 `i` 插入和删除元素的话(`add(int index, E element)`,`remove(Object o)`), 时间复杂度为 O(n) ,因为需要先移动到指定位置再插入。
|
||||
4. **是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index)`方法)。
|
||||
5. **内存空间占用:** `ArrayList` 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
|
||||
- **是否支持快速随机访问:** `LinkedList` 不支持高效的随机元素访问,而 `ArrayList` 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index)`方法)。
|
||||
- **内存空间占用:** `ArrayList` 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
|
||||
|
||||
我们在项目中一般是不会使用到 `LinkedList` 的,需要用到 `LinkedList` 的场景几乎都可以使用 `ArrayList` 来代替,并且,性能通常会更好!就连 `LinkedList` 的作者约书亚 · 布洛克(Josh Bloch)自己都说从来不会使用 `LinkedList` 。
|
||||
|
||||
@ -103,8 +103,6 @@ Java 集合框架如下图所示:
|
||||
|
||||
**双向链表:** 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。
|
||||
|
||||
> 另外推荐一篇把双向链表讲清楚的文章:[https://juejin.cn/post/6844903648154271757](https://juejin.cn/post/6844903648154271757)
|
||||
|
||||

|
||||
|
||||
**双向循环链表:** 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。
|
||||
@ -271,9 +269,8 @@ Output:
|
||||
|
||||
### 无序性和不可重复性的含义是什么
|
||||
|
||||
1、什么是无序性?无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。
|
||||
|
||||
2、什么是不可重复性?不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()方法和 HashCode()方法。
|
||||
- 无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。
|
||||
- 不可重复性是指添加的元素按照 `equals()` 判断时 ,返回 false,需要同时重写 `equals()` 方法和 `hashCode()` 方法。
|
||||
|
||||
### 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同
|
||||
|
||||
|
@ -16,11 +16,11 @@ head:
|
||||
|
||||
### HashMap 和 Hashtable 的区别
|
||||
|
||||
1. **线程是否安全:** `HashMap` 是非线程安全的,`Hashtable` 是线程安全的,因为 `Hashtable` 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 `ConcurrentHashMap` 吧!);
|
||||
2. **效率:** 因为线程安全的问题,`HashMap` 要比 `Hashtable` 效率高一点。另外,`Hashtable` 基本被淘汰,不要在代码中使用它;
|
||||
3. **对 Null key 和 Null value 的支持:** `HashMap` 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 `NullPointerException`。
|
||||
4. **初始容量大小和每次扩充容量大小的不同 :** ① 创建时如果不指定容量初始值,`Hashtable` 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。`HashMap` 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 `Hashtable` 会直接使用你给定的大小,而 `HashMap` 会将其扩充为 2 的幂次方大小(`HashMap` 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 `HashMap` 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
|
||||
5. **底层数据结构:** JDK1.8 以后的 `HashMap` 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间(后文中我会结合源码对这一过程进行分析)。`Hashtable` 没有这样的机制。
|
||||
- **线程是否安全:** `HashMap` 是非线程安全的,`Hashtable` 是线程安全的,因为 `Hashtable` 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 `ConcurrentHashMap` 吧!);
|
||||
- **效率:** 因为线程安全的问题,`HashMap` 要比 `Hashtable` 效率高一点。另外,`Hashtable` 基本被淘汰,不要在代码中使用它;
|
||||
- **对 Null key 和 Null value 的支持:** `HashMap` 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 `NullPointerException`。
|
||||
- **初始容量大小和每次扩充容量大小的不同 :** ① 创建时如果不指定容量初始值,`Hashtable` 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。`HashMap` 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 `Hashtable` 会直接使用你给定的大小,而 `HashMap` 会将其扩充为 2 的幂次方大小(`HashMap` 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 `HashMap` 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
|
||||
- **底层数据结构:** JDK1.8 以后的 `HashMap` 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间(后文中我会结合源码对这一过程进行分析)。`Hashtable` 没有这样的机制。
|
||||
|
||||
**`HashMap` 中带有初始容量的构造函数:**
|
||||
|
||||
@ -74,7 +74,7 @@ head:
|
||||
|
||||
`TreeMap` 和`HashMap` 都继承自`AbstractMap` ,但是需要注意的是`TreeMap`它还实现了`NavigableMap`接口和`SortedMap` 接口。
|
||||
|
||||

|
||||

|
||||
|
||||
实现 `NavigableMap` 接口让 `TreeMap` 有了对集合内元素的搜索的能力。
|
||||
|
||||
@ -138,22 +138,24 @@ TreeMap<Person, String> treeMap = new TreeMap<>((person1, person2) -> {
|
||||
|
||||
**综上,相比于`HashMap`来说 `TreeMap` 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。**
|
||||
|
||||
### HashSet 如何检查重复
|
||||
### HashSet 如何检查重复?
|
||||
|
||||
以下内容摘自我的 Java 启蒙书《Head first java》第二版:
|
||||
|
||||
当你把对象加入`HashSet`时,`HashSet` 会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的 `hashcode` 值作比较,如果没有相符的 `hashcode`,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 `hashcode` 值的对象,这时会调用`equals()`方法来检查 `hashcode` 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让加入操作成功。
|
||||
> 当你把对象加入`HashSet`时,`HashSet` 会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的 `hashcode` 值作比较,如果没有相符的 `hashcode`,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 `hashcode` 值的对象,这时会调用`equals()`方法来检查 `hashcode` 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让加入操作成功。
|
||||
|
||||
在 JDK1.8 中,`HashSet`的`add()`方法只是简单的调用了`HashMap`的`put()`方法,并且判断了一下返回值以确保是否有重复元素。直接看一下`HashSet`中的源码:
|
||||
|
||||
在openjdk8中,`HashSet`的`add()`方法只是简单的调用了`HashMap`的`put()`方法,并且判断了一下返回值以确保是否有重复元素。直接看一下`HashSet`中的源码:
|
||||
```java
|
||||
// Returns: true if this set did not already contain the specified element
|
||||
// 返回值:当set中没有包含add的元素时返回真
|
||||
// 返回值:当 set 中没有包含 add 的元素时返回真
|
||||
public boolean add(E e) {
|
||||
return map.put(e, PRESENT)==null;
|
||||
}
|
||||
```
|
||||
|
||||
而在`HashMap`的`putVal()`方法中也能看到如下说明:
|
||||
而在`HashMap`的`putVal()`方法中也能看到如下说明:
|
||||
|
||||
```java
|
||||
// Returns : previous value, or null if none
|
||||
// 返回值:如果插入位置没有元素返回null,否则返回上一个元素
|
||||
@ -163,29 +165,13 @@ final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
|
||||
}
|
||||
```
|
||||
|
||||
也就是说,在openjdk8中,实际上无论`HashSet`中是否已经存在了某元素,`HashSet`都会直接插入,只是会在`add()`方法的返回值处告诉我们插入前是否存在相同元素。
|
||||
|
||||
**`hashCode()`与 `equals()` 的相关规定:**
|
||||
|
||||
1. 如果两个对象相等,则 `hashcode` 一定也是相同的
|
||||
2. 两个对象相等,对两个 `equals()` 方法返回 true
|
||||
3. 两个对象有相同的 `hashcode` 值,它们也不一定是相等的
|
||||
4. 综上,`equals()` 方法被覆盖过,则 `hashCode()` 方法也必须被覆盖
|
||||
5. `hashCode()`的默认行为是对堆上的对象产生独特值。如果没有重写 `hashCode()`,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
|
||||
|
||||
**==与 equals 的区别**
|
||||
|
||||
对于基本类型来说,== 比较的是值是否相等;
|
||||
|
||||
对于引用类型来说,== 比较的是两个引用是否指向同一个对象地址(两者在内存中存放的地址(堆内存地址)是否指向同一个地方);
|
||||
|
||||
对于引用类型(包括包装类型)来说,equals 如果没有被重写,对比它们的地址是否相等;如果 equals()方法被重写(例如 String),则比较的是地址里的内容。
|
||||
也就是说,在 JDK1.8 中,实际上无论`HashSet`中是否已经存在了某元素,`HashSet`都会直接插入,只是会在`add()`方法的返回值处告诉我们插入前是否存在相同元素。
|
||||
|
||||
### HashMap 的底层实现
|
||||
|
||||
#### JDK1.8 之前
|
||||
|
||||
JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。HashMap 通过 key 的 `hashCode` 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
|
||||
JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。HashMap 通过 key 的 `hashcode` 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
|
||||
|
||||
所谓扰动函数指的就是 HashMap 的 `hash` 方法。使用 `hash` 方法也就是扰动函数是为了防止一些实现比较差的 `hashCode()` 方法 换句话说使用扰动函数之后可以减少碰撞。
|
||||
|
||||
@ -220,13 +206,13 @@ static int hash(int h) {
|
||||
|
||||
所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
|
||||
|
||||

|
||||

|
||||
|
||||
#### JDK1.8 之后
|
||||
|
||||
相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
|
||||
|
||||

|
||||

|
||||
|
||||
> TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
|
||||
|
||||
@ -309,56 +295,93 @@ final void treeifyBin(Node<K,V>[] tab, int hash) {
|
||||
`ConcurrentHashMap` 和 `Hashtable` 的区别主要体现在实现线程安全的方式上不同。
|
||||
|
||||
- **底层数据结构:** JDK1.7 的 `ConcurrentHashMap` 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟 `HashMap1.8` 的结构一样,数组+链表/红黑二叉树。`Hashtable` 和 JDK1.8 之前的 `HashMap` 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
|
||||
- **实现线程安全的方式(重要):** ① **在 JDK1.7 的时候,`ConcurrentHashMap`(分段锁)** 对整个桶数组进行了分割分段(`Segment`),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了 `Segment` 的概念,而是直接用 `Node` 数组+链表+红黑树的数据结构来实现,并发控制使用 `synchronized` 和 CAS 来操作。(JDK1.6 以后 对 `synchronized` 锁做了很多优化)** 整个看起来就像是优化过且线程安全的 `HashMap`,虽然在 JDK1.8 中还能看到 `Segment` 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **`Hashtable`(同一把锁)** :使用 `synchronized` 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
|
||||
- **实现线程安全的方式(重要):**
|
||||
- 在 JDK1.7 的时候,`ConcurrentHashMap` 对整个桶数组进行了分割分段(`Segment`,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
|
||||
- 到了 JDK1.8 的时候,`ConcurrentHashMap` 已经摒弃了 `Segment` 的概念,而是直接用 `Node` 数组+链表+红黑树的数据结构来实现,并发控制使用 `synchronized` 和 CAS 来操作。(JDK1.6 以后 `synchronized` 锁做了很多优化) 整个看起来就像是优化过且线程安全的 `HashMap`,虽然在 JDK1.8 中还能看到 `Segment` 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
|
||||
- **`Hashtable`(同一把锁)** :使用 `synchronized` 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
|
||||
|
||||
**两者的对比图:**
|
||||
下面,我们再来看看两者底层数据结构的对比图。
|
||||
|
||||
**Hashtable:**
|
||||
**Hashtable** :
|
||||
|
||||

|
||||

|
||||
|
||||
<p style="text-align:right;font-size:13px;color:gray">https://www.cnblogs.com/chengxiao/p/6842045.html></p>
|
||||
|
||||
**JDK1.7 的 ConcurrentHashMap:**
|
||||
**JDK1.7 的 ConcurrentHashMap** :
|
||||
|
||||

|
||||

|
||||
|
||||
**JDK1.8 的 ConcurrentHashMap:**
|
||||
`ConcurrentHashMap` 是由 `Segment` 数组结构和 `HashEntry` 数组结构组成。
|
||||
|
||||

|
||||
`Segment` 数组中的每个元素包含一个 `HashEntry` 数组,每个 `HashEntry` 数组属于链表结构。
|
||||
|
||||
**JDK1.8 的 ConcurrentHashMap** :
|
||||
|
||||

|
||||
|
||||
JDK1.8 的 `ConcurrentHashMap` 不再是 **Segment 数组 + HashEntry 数组 + 链表**,而是 **Node 数组 + 链表 / 红黑树**。不过,Node 只能用于链表的情况,红黑树的情况需要使用 **`TreeNode`**。当冲突链表达到一定长度时,链表会转换成红黑树。
|
||||
|
||||
`TreeNode`是存储红黑树节点,被`TreeBin`包装。`TreeBin`通过`root`属性维护红黑树的根结点,因为红黑树在旋转的时候,根结点可能会被它原来的子节点替换掉,在这个时间点,如果有其他线程要写这棵红黑树就会发生线程不安全问题,所以在 `ConcurrentHashMap` 中`TreeBin`通过`waiter`属性维护当前使用这棵红黑树的线程,来防止其他线程的进入。
|
||||
|
||||
```java
|
||||
static final class TreeBin<K,V> extends Node<K,V> {
|
||||
TreeNode<K,V> root;
|
||||
volatile TreeNode<K,V> first;
|
||||
volatile Thread waiter;
|
||||
volatile int lockState;
|
||||
// values for lockState
|
||||
static final int WRITER = 1; // set while holding write lock
|
||||
static final int WAITER = 2; // set when waiting for write lock
|
||||
static final int READER = 4; // increment value for setting read lock
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
|
||||
|
||||
#### JDK1.7(上面有示意图)
|
||||
#### JDK1.8 之前
|
||||
|
||||
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
|
||||

|
||||
|
||||
首先将数据分为一段一段(这个“段”就是 `Segment`)的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
|
||||
|
||||
**`ConcurrentHashMap` 是由 `Segment` 数组结构和 `HashEntry` 数组结构组成**。
|
||||
|
||||
Segment 继承了 `ReentrantLock`,所以 `Segment` 是一种可重入锁,扮演锁的角色。`HashEntry` 用于存储键值对数据。
|
||||
`Segment` 继承了 `ReentrantLock`,所以 `Segment` 是一种可重入锁,扮演锁的角色。`HashEntry` 用于存储键值对数据。
|
||||
|
||||
```java
|
||||
static class Segment<K,V> extends ReentrantLock implements Serializable {
|
||||
}
|
||||
```
|
||||
|
||||
一个 `ConcurrentHashMap` 里包含一个 `Segment` 数组。`Segment` 的结构和 `HashMap` 类似,是一种数组和链表结构,一个 `Segment` 包含一个 `HashEntry` 数组,每个 `HashEntry` 是一个链表结构的元素,每个 `Segment` 守护着一个 `HashEntry` 数组里的元素,当对 `HashEntry` 数组的数据进行修改时,必须首先获得对应的 `Segment` 的锁。
|
||||
一个 `ConcurrentHashMap` 里包含一个 `Segment` 数组,`Segment` 的个数一旦**初始化就不能改变**。 `Segment` 数组的大小默认是 16,也就是说默认可以同时支持 16 个线程并发写。
|
||||
|
||||
#### JDK1.8 (上面有示意图)
|
||||
`Segment` 的结构和 `HashMap` 类似,是一种数组和链表结构,一个 `Segment` 包含一个 `HashEntry` 数组,每个 `HashEntry` 是一个链表结构的元素,每个 `Segment` 守护着一个 `HashEntry` 数组里的元素,当对 `HashEntry` 数组的数据进行修改时,必须首先获得对应的 `Segment` 的锁。也就是说,对同一 `Segment` 的并发写入会被阻塞,不同 `Segment` 的写入是可以并发执行的。
|
||||
|
||||
`ConcurrentHashMap` 取消了 `Segment` 分段锁,采用 CAS 和 `synchronized` 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))
|
||||
#### JDK1.8 之后
|
||||
|
||||
`synchronized` 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
|
||||

|
||||
|
||||
## Collections 工具类
|
||||
Java 8 几乎完全重写了 `ConcurrentHashMap`,代码量从原来 Java 7 中的 1000 多行,变成了现在的 6000 多行。
|
||||
|
||||
Collections 工具类常用方法:
|
||||
`ConcurrentHashMap` 取消了 `Segment` 分段锁,采用 `Node + CAS + synchronized` 来保证并发安全。数据结构跟 `HashMap` 1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))。
|
||||
|
||||
1. 排序
|
||||
2. 查找,替换操作
|
||||
3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
|
||||
Java 8 中,锁粒度更细,`synchronized` 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升。
|
||||
|
||||
### JDK 1.7 和 JDK 1.8 的 ConcurrentHashMap 实现有什么不同?
|
||||
|
||||
- **线程安全实现方式** :JDK 1.7 采用 `Segment` 分段锁来保证安全, `Segment` 是继承自 `ReentrantLock`。JDK1.8 放弃了 `Segment` 分段锁的设计,采用 `Node + CAS + synchronized` 保证线程安全,锁粒度更细,`synchronized` 只锁定当前链表或红黑二叉树的首节点。
|
||||
- **Hash 碰撞解决方法** : JDK 1.7 采用拉链法,JDK1.8 采用拉链法结合红黑树(链表长度超过一定阈值时,将链表转换为红黑树)。
|
||||
- **并发度** :JDK 1.7 最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是 Node 数组的大小,并发度更大。
|
||||
|
||||
## Collections 工具类(不重要)
|
||||
|
||||
**`Collections` 工具类常用方法**:
|
||||
|
||||
- 排序
|
||||
- 查找,替换操作
|
||||
- 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
|
||||
|
||||
### 排序操作
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user