X Tutup
Skip to content

Commit 02db053

Browse files
committed
更新LinkedList
1 parent e49b40c commit 02db053

File tree

4 files changed

+62
-11
lines changed

4 files changed

+62
-11
lines changed

blog/test.md

Whitespace-only changes.

notes/JavaArchitecture/02 Java 集合框架.md

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public class ArrayList<E> extends AbstractList<E>
189189
private static final int DEFAULT_CAPACITY = 10;
190190
```
191191

192-
<div align="center"> <img src="../pics/ArrayList_base.png" width=""/></div><br/>
192+
<div align="center"> <img src="../pics/ArrayList_base.png" width="650"/></div><br/>
193193

194194

195195

@@ -241,6 +241,14 @@ private void grow(int minCapacity) {
241241
// minCapacity is usually close to size, so this is a win:
242242
elementData = Arrays.copyOf(elementData, newCapacity);
243243
}
244+
245+
private static int hugeCapacity(int minCapacity) {
246+
if (minCapacity < 0) // overflow
247+
throw new OutOfMemoryError();
248+
return (minCapacity > MAX_ARRAY_SIZE) ?
249+
Integer.MAX_VALUE :
250+
MAX_ARRAY_SIZE;
251+
}
244252
```
245253

246254

@@ -271,8 +279,7 @@ modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化
271279
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。
272280

273281
```java
274-
private void writeObject(java.io.ObjectOutputStream s)
275-
throws java.io.IOException{
282+
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
276283
// Write out element count, and any hidden stuff
277284
int expectedModCount = modCount;
278285
s.defaultWriteObject();
@@ -326,30 +333,43 @@ public synchronized E get(int index) {
326333

327334
### 3. Vector 替代方案
328335

336+
#### synchronizedList
337+
329338
为了获得线程安全的 ArrayList,可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。
330339

331340
```java
332341
List<String> list = new ArrayList<>();
333342
List<String> synList = Collections.synchronizedList(list);
334343
```
335344

345+
346+
347+
#### CopyOnWriteArrayList
348+
349+
<div align="center"> <img src="../pics/cow.png" width=""/></div><br/>
350+
351+
352+
336353
也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类。
337354

338355
```java
339356
List<String> list = new CopyOnWriteArrayList<>();
340357
```
341358

342-
CopyOnWriteArrayList 是一种 CopyOnWrite 容器,从以下源码看出:读取元素是从原数组读取;添加元素是在复制的新数组上。读写分离,因而可以在并发条件下进行不加锁的读取,读取效率高,适用于读操作远大于写操作的场景。
359+
CopyOnWrite容器即写时复制的容器。通俗的理解是**当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器**。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种**读写分离**的思想,读和写不同的容器。
343360

344361
```java
345-
public boolean add(E e) {
362+
public boolean add(T e) {
346363
final ReentrantLock lock = this.lock;
347364
lock.lock();
348365
try {
349366
Object[] elements = getArray();
350367
int len = elements.length;
351-
Object[] newElements = Arrays.copyOf(elements, len + 1);
368+
// 复制出新数组
369+
Object[] newElements = Arrays.copyOf(elements, len + 1);
370+
// 把新元素添加到新数组里
352371
newElements[len] = e;
372+
// 把原数组引用指向新数组
353373
setArray(newElements);
354374
return true;
355375
} finally {
@@ -360,22 +380,47 @@ public boolean add(E e) {
360380
final void setArray(Object[] a) {
361381
array = a;
362382
}
383+
```
363384

364-
@SuppressWarnings("unchecked")
365-
private E get(Object[] a, int index) {
366-
return (E) a[index];
385+
读的时候不需要加锁,如果读的时候有多个线程正在向ArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的ArrayList。
386+
387+
```java
388+
public E get(int index) {
389+
return get(getArray(), index);
367390
}
368391
```
369392

393+
**CopyOnWrite的缺点**
394+
395+
CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。
396+
397+
**内存占用问题**。因为 CopyOnWrite 的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。
398+
399+
针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如 ConcurrentHashMap 。
400+
401+
**数据一致性问题**。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。
402+
403+
关于C++的STL中,曾经也有过Copy-On-Write的玩法,参见陈皓的《[C++ STL String类中的Copy-On-Write](http://blog.csdn.net/haoel/article/details/24058)》,后来,因为有很多线程安全上的事,就被去掉了。
404+
405+
406+
407+
参考资料:
408+
409+
- [聊聊并发-Java中的Copy-On-Write容器 | 并发编程网 – ifeve.com](http://ifeve.com/java-copy-on-write/)
410+
370411

371412

372413
## LinkedList
373414

374-
<div align="center"> <img src="../pics/LinkedList.png" width=""/></div><br/>
415+
<div align="center"> <img src="../pics/LinkedList_base.png" width="600"/></div><br/>
375416

376417
### 1. 概览
377418

378-
如图所示 `LinkedList` 底层是基于双向链表实现的,也是实现了 `List` 接口,所以也拥有 List 的一些特点(JDK1.7/8 之后取消了循环,修改为双向链表)。
419+
如图所示 LinkedList 底层是基于双向链表实现的,也是实现了 List 接口,所以也拥有 List 的一些特点 (JDK1.7/8 之后取消了循环,修改为双向链表) 。
420+
421+
*LinkedList*同时实现了*List*接口和*Deque*接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(*Queue*),同时又可以看作一个栈(*Stack*)。这样看来,*LinkedList*简直就是个全能冠军。当你需要使用栈或者队列时,可以考虑使用*LinkedList*,一方面是因为Java官方已经声明不建议使用*Stack*类,更遗憾的是,Java里根本没有一个叫做*Queue*的类(它是个接口名字)。关于栈或队列,现在的首选是*ArrayDeque*,它有着比*LinkedList*(当作栈或队列使用时)有着更好的性能。
422+
423+
379424

380425
基于双向链表实现,内部使用 Node 来存储链表节点信息。
381426

@@ -394,6 +439,8 @@ transient Node<E> first;
394439
transient Node<E> last;
395440
```
396441

442+
443+
397444
### 2. add()
398445

399446
```java
@@ -420,6 +467,8 @@ void linkLast(E e) {
420467

421468
可见每次插入都是移动指针,和 ArrayList 的拷贝数组来说效率要高上不少。
422469

470+
471+
423472
### 3. get()
424473

425474
```java
@@ -451,6 +500,8 @@ Node<E> node(int index) {
451500

452501
这样的效率是非常低的,特别是当 index 越接近 size 的中间值时。
453502

503+
504+
454505
### 4. 总结
455506

456507
- LinkedList 插入,删除都是移动指针效率很高。

notes/pics/LinkedList_base.png

25.2 KB
Loading

notes/pics/cow.png

21 KB
Loading

0 commit comments

Comments
 (0)
X Tutup