auto commit

This commit is contained in:
CyC2018
2020-11-17 00:32:18 +08:00
parent f5ad47b470
commit 7e61fc1360
380 changed files with 2371 additions and 46715 deletions

View File

@ -1,32 +1,22 @@
# Java 容器
<!-- GFM-TOC -->
* [概览](#一概览)
* [Collection](#collection)
* [Map](#map)
* [容器中的设计模式](#二容器中的设计模式)
* [迭代器模式](#迭代器模式)
* [适配器模式](#适配器模式)
* [源码分析](#三源码分析)
* [ArrayList](#arraylist)
* [Vector](#vector)
* [CopyOnWriteArrayList](#copyonwritearraylist)
* [LinkedList](#linkedlist)
* [HashMap](#hashmap)
* [ConcurrentHashMap](#concurrenthashmap)
* [LinkedHashMap](#linkedhashmap)
* [WeakHashMap](#weakhashmap)
* [参考资料](#参考资料)
* [Java 容器](#java-容器)
* [概览](#一概览)
* [容器中的设计模式](#二容器中的设计模式)
* [源码分析](#三源码分析)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 概览
## 概览
容器主要包括 Collection Map 两种Collection 存储着对象的集合 Map 存储着键值对两个对象的映射表
## Collection
### Collection
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208220948084.png"/> </div><br>
### 1. Set
#### 1. Set
- TreeSet基于红黑树实现支持有序性操作例如根据一个范围查找元素的操作但是查找效率不如 HashSetHashSet 查找的时间复杂度为 O(1)TreeSet 则为 O(logN)
@ -34,7 +24,7 @@
- LinkedHashSet具有 HashSet 的查找效率并且内部使用双向链表维护元素的插入顺序
### 2. List
#### 2. List
- ArrayList基于动态数组实现支持随机访问
@ -42,13 +32,13 @@
- LinkedList基于双向链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素不仅如此LinkedList 还可以用作栈队列和双向队列
### 3. Queue
#### 3. Queue
- LinkedList可以用它来实现双向队列
- PriorityQueue基于堆结构实现可以用它来实现优先队列
## Map
### Map
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20201101234335837.png"/> </div><br>
@ -61,9 +51,9 @@
- LinkedHashMap使用双向链表来维护元素的顺序顺序为插入顺序或者最近最少使用LRU顺序
# 容器中的设计模式
## 容器中的设计模式
## 迭代器模式
### 迭代器模式
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208225301973.png"/> </div><br>
@ -80,7 +70,7 @@ for (String item : list) {
}
```
## 适配器模式
### 适配器模式
java.util.Arrays#asList() 可以把数组类型转换为 List 类型
@ -102,16 +92,16 @@ List list = Arrays.asList(arr);
List list = Arrays.asList(1, 2, 3);
```
# 源码分析
## 源码分析
如果没有特别说明以下源码分析基于 JDK 1.8
IDEA double shift 调出 Search EveryWhere查找源码文件找到之后就可以阅读源码
## ArrayList
### ArrayList
### 1. 概览
#### 1. 概览
因为 ArrayList 是基于数组实现的所以支持快速随机访问RandomAccess 接口标识着该类支持快速随机访问
@ -128,9 +118,9 @@ private static final int DEFAULT_CAPACITY = 10;
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208232221265.png"/> </div><br>
### 2. 扩容
#### 2. 扩容
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够如果不够时需要使用 grow() 方法进行扩容新容量的大小为 `oldCapacity + (oldCapacity >> 1)`也就是旧容量的 1.5
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够如果不够时需要使用 grow() 方法进行扩容新容量的大小为 `oldCapacity + (oldCapacity \>\> 1)`也就是旧容量的 1.5
扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中这个操作代价很高因此最好在创建 ArrayList 对象时就指定大概的容量大小减少扩容操作的次数
@ -168,7 +158,7 @@ private void grow(int minCapacity) {
}
```
### 3. 删除元素
#### 3. 删除元素
需要调用 System.arraycopy() index+1 后面的元素都复制到 index 位置上该操作的时间复杂度为 O(N)可以看到 ArrayList 删除元素的代价是非常高的
@ -185,7 +175,7 @@ public E remove(int index) {
}
```
### 4. 序列化
#### 4. 序列化
ArrayList 基于数组实现并且具有动态扩容特性因此保存元素的数组不一定都会被使用那么就没必要全部进行序列化
@ -250,16 +240,16 @@ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);
```
### 5. Fail-Fast
#### 5. Fail-Fast
modCount 用来记录 ArrayList 结构发生变化的次数结构发生变化是指添加或者删除至少一个元素的所有操作或者是调整内部数组的大小仅仅只是设置元素的值不算结构发生变化
在进行序列化或者迭代等操作时需要比较操作前后 modCount 是否改变如果改变了需要抛出 ConcurrentModificationException代码参考上节序列化中的 writeObject() 方法
## Vector
### Vector
### 1. 同步
#### 1. 同步
它的实现与 ArrayList 类似但是使用了 synchronized 进行同步
@ -279,7 +269,7 @@ public synchronized E get(int index) {
}
```
### 2. 扩容
#### 2. 扩容
Vector 的构造函数可以传入 capacityIncrement 参数它的作用是在扩容时使容量 capacity 增长 capacityIncrement如果这个参数的值小于等于 0扩容时每次都令 capacity 为原来的两倍
@ -320,12 +310,12 @@ public Vector() {
}
```
### 3. ArrayList 的比较
#### 3. ArrayList 的比较
- Vector 是同步的因此开销就比 ArrayList 要大访问速度更慢最好使用 ArrayList 而不是 Vector因为同步操作完全可以由程序员自己来控制
- Vector 每次扩容请求其大小的 2 也可以通过构造函数设置增长的容量 ArrayList 1.5
### 4. 替代方案
#### 4. 替代方案
可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList
@ -340,9 +330,9 @@ List<String> synList = Collections.synchronizedList(list);
List<String> list = new CopyOnWriteArrayList<>();
```
## CopyOnWriteArrayList
### CopyOnWriteArrayList
### 1. 读写分离
#### 1. 读写分离
写操作在一个复制的数组上进行读操作还是在原始数组中进行读写分离互不影响
@ -378,7 +368,7 @@ private E get(Object[] a, int index) {
}
```
### 2. 适用场景
#### 2. 适用场景
CopyOnWriteArrayList 在写操作的同时允许读操作大大提高了读操作的性能因此很适合读多写少的应用场景
@ -389,9 +379,9 @@ CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景
## LinkedList
### LinkedList
### 1. 概览
#### 1. 概览
基于双向链表实现使用 Node 存储链表节点信息
@ -412,18 +402,18 @@ transient Node<E> last;
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208233940066.png"/> </div><br>
### 2. ArrayList 的比较
#### 2. ArrayList 的比较
ArrayList 基于动态数组实现LinkedList 基于双向链表实现ArrayList LinkedList 的区别可以归结为数组和链表的区别
- 数组支持随机访问但插入删除的代价很高需要移动大量元素
- 链表不支持随机访问但插入删除只需要改变指针
## HashMap
### HashMap
为了便于理解以下源码分析以 JDK 1.7 为主
### 1. 存储结构
#### 1. 存储结构
内部包含了一个 Entry 类型的数组 tableEntry 存储着键值对它包含了四个字段 next 字段我们可以看出 Entry 是一个链表即数组中的每个位置被当成一个桶一个桶存放一个链表HashMap 使用拉链法来解决冲突同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry
@ -486,7 +476,7 @@ static class Entry<K,V> implements Map.Entry<K,V> {
}
```
### 2. 拉链法的工作原理
#### 2. 拉链法的工作原理
```java
HashMap<String, String> map = new HashMap<>();
@ -496,11 +486,11 @@ map.put("K3", "V3");
```
- 新建一个 HashMap默认大小为 16
- 插入 &lt;K1,V1> 键值对先计算 K1 hashCode 115使用除留余数法得到所在的桶下标 115%16=3
- 插入 &lt;K2,V2> 键值对先计算 K2 hashCode 118使用除留余数法得到所在的桶下标 118%16=6
- 插入 &lt;K3,V3> 键值对先计算 K3 hashCode 118使用除留余数法得到所在的桶下标 118%16=6插在 &lt;K2,V2> 前面
- 插入 &lt;K1,V1\> 键值对先计算 K1 hashCode 115使用除留余数法得到所在的桶下标 115%16=3
- 插入 &lt;K2,V2\> 键值对先计算 K2 hashCode 118使用除留余数法得到所在的桶下标 118%16=6
- 插入 &lt;K3,V3\> 键值对先计算 K3 hashCode 118使用除留余数法得到所在的桶下标 118%16=6插在 &lt;K2,V2\> 前面
应该注意到链表的插入是以头插法方式进行的例如上面的 &lt;K3,V3> 不是插在 &lt;K2,V2> 后面而是插入在链表头部
应该注意到链表的插入是以头插法方式进行的例如上面的 &lt;K3,V3\> 不是插在 &lt;K2,V2\> 后面而是插入在链表头部
查找需要分成两步进行
@ -509,7 +499,7 @@ map.put("K3", "V3");
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208235258643.png"/> </div><br>
### 3. put 操作
#### 3. put 操作
```java
public V put(K key, V value) {
@ -588,7 +578,7 @@ Entry(int h, K k, V v, Entry<K,V> n) {
}
```
### 4. 确定桶下标
#### 4. 确定桶下标
很多操作都需要先确定一个键值对所在的桶下标
@ -624,7 +614,7 @@ public final int hashCode() {
**4.2 取模**
x = 1<<4 x 2 4 次方它具有以下性质
x = 1\<\<4 x 2 4 次方它具有以下性质
```
x : 00010000
@ -657,7 +647,7 @@ static int indexFor(int h, int length) {
}
```
### 5. 扩容-基本原理
#### 5. 扩容-基本原理
HashMap table 长度为 M需要存储的键值对数量为 N如果哈希函数满足均匀性的要求那么每条链表的长度大约为 N/M因此查找的复杂度为 O(N/M)
@ -736,7 +726,7 @@ void transfer(Entry[] newTable) {
}
```
### 6. 扩容-重新计算桶下标
#### 6. 扩容-重新计算桶下标
在进行扩容时需要把键值对重新计算桶下标从而放到对应的桶上在前面提到HashMap 使用 hash%capacity 来确定桶下标HashMap capacity 2 n 次方这一特点能够极大降低重新计算桶下标操作的复杂度
@ -752,7 +742,7 @@ new capacity : 00100000
- 0那么 hash%00010000 = hash%00100000桶位置和原来一致
- 1hash%00010000 = hash%00100000 + 16桶位置是原位置 + 16
### 7. 计算数组容量
#### 7. 计算数组容量
HashMap 构造函数允许用户传入的容量不是 2 n 次方因为它可以自动地将传入的容量转换为 2 n 次方
@ -785,20 +775,20 @@ static final int tableSizeFor(int cap) {
}
```
### 8. 链表转红黑树
#### 8. 链表转红黑树
JDK 1.8 开始一个桶存储的链表长度大于等于 8 时会将链表转换为红黑树
### 9. Hashtable 的比较
#### 9. Hashtable 的比较
- Hashtable 使用 synchronized 来进行同步
- HashMap 可以插入键为 null Entry
- HashMap 的迭代器是 fail-fast 迭代器
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的
## ConcurrentHashMap
### ConcurrentHashMap
### 1. 存储结构
#### 1. 存储结构
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191209001038024.png"/> </div><br>
@ -845,7 +835,7 @@ final Segment<K,V>[] segments;
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
```
### 2. size 操作
#### 2. size 操作
每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数
@ -918,7 +908,7 @@ public int size() {
}
```
### 3. JDK 1.8 的改动
#### 3. JDK 1.8 的改动
JDK 1.7 使用分段锁机制来实现并发更新操作核心类为 Segment它继承自重入锁 ReentrantLock并发度与 Segment 数量相等
@ -926,9 +916,9 @@ JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败
并且 JDK 1.8 的实现也在链表过长时会转换为红黑树
## LinkedHashMap
### LinkedHashMap
### 存储结构
#### 存储结构
继承自 HashMap因此具有和 HashMap 一样的快速查找特性
@ -963,7 +953,7 @@ void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
```
### afterNodeAccess()
#### afterNodeAccess()
当一个节点被访问时如果 accessOrder true则会将该节点移到链表尾部也就是说指定为 LRU 顺序之后在每次访问一个节点时会将这个节点移到链表尾部保证链表尾部是最近访问的节点那么链表首部就是最近最久未使用的节点
@ -994,7 +984,7 @@ void afterNodeAccess(Node<K,V> e) { // move node to last
}
```
### afterNodeInsertion()
#### afterNodeInsertion()
put 等操作之后执行 removeEldestEntry() 方法返回 true 时会移除最晚的节点也就是链表首部节点 first
@ -1018,7 +1008,7 @@ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
}
```
### LRU 缓存
#### LRU 缓存
以下是使用 LinkedHashMap 实现的一个 LRU 缓存
@ -1056,9 +1046,9 @@ public static void main(String[] args) {
[3, 1, 4]
```
## WeakHashMap
### WeakHashMap
### 存储结构
#### 存储结构
WeakHashMap Entry 继承自 WeakReference WeakReference 关联的对象在下一次垃圾回收时会被回收
@ -1068,7 +1058,7 @@ WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
```
### ConcurrentCache
#### ConcurrentCache
Tomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能
@ -1115,7 +1105,7 @@ public final class ConcurrentCache<K, V> {
```
# 参考资料
## 参考资料
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
- [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)
@ -1129,10 +1119,3 @@ public final class ConcurrentCache<K, V> {
- [Java 集合细节asList 的缺陷](http://wiki.jikexueyuan.com/project/java-enhancement/java-thirtysix.html)
- [Java Collection Framework The LinkedList Class](http://javaconceptoftheday.com/java-collection-framework-linkedlist-class/)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>