ArrayBlockingQueue源码解析

什么是ArrayBlockingQueue

ArrayBlockingQueue底层是由数组实现的定长阻塞队列(阻塞表示如果没有原始那么获取元素会阻塞当前线程)

ArrayBlockingQueue用来干嘛

ArrayBlockingQueue一般用于生产者消费者模型业务(排队机制,先进先出)

源码解析

数据的存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -817911632652898426L;
/** The queued items 存储元素容器*/
final Object[] items;
/** items index for next take, poll, peek or remove 使用过的元素 */
int takeIndex;
/** items index for next put, offer, or add 添加过的元素 */
int putIndex;
/** Number of elements in the queue 当前元素数量 */
int count;

数据的操作

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public boolean add(E e) {
return super.add(e);
}
super.add
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
checkNotNull(e);//ArrayBlockingQueue不能存储null对象
final ReentrantLock lock = this.lock;//插入操作线程安全
lock.lock();
try {
if (count == items.length)//如果当前count==items.length表示队列已经忙了,不能插入
return false;
else {
insert(e);//插入元素
return true;
}
} finally {
lock.unlock();
}
}
private void insert(E x) {
items[putIndex] = x;//第一次put为0
putIndex = inc(putIndex);//递增
++count;//数量递增
notEmpty.signal();//通知获取原始方法可以进行获取
}
final int inc(int i) {//如果当前putIndex==items.length那么putIndex重新从零开始
return (++i == items.length) ? 0 : i;
}
//同样为添加元素,lock.lockInterruptibly如果检测到有Thread.interrupted();会直接抛出异常
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}

remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) {
if (o.equals(items[i])) {//从头部开始遍历元素判断
removeAt(i);
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
//queue size = 10 putSize = 5 tackSize = 0
//queue 1,2,3,4,5
removeAt 3
step1: removeAt != takeIndex
i =
nexti = 4
void removeAt(int i) {
final Object[] items = this.items;
// if removing front item, just advance
if (i == takeIndex) {
items[takeIndex] = null;//引用设置为空
takeIndex = inc(takeIndex);//takeIndex++
} else {
// slide over all others up through putIndex.
for (;;) {
int nexti = inc(i);//>队列的头部 递增(putIndex一个循环的0-n)
if (nexti != putIndex) {//递增后部位putIndex全部向前移动位置
items[i] = items[nexti];
i = nexti;
} else {
items[i] = null;//元素设置为空
putIndex = i;
break;
}
}
}
--count;//元素递减
notFull.signal();//通知notFull.awit()
}

update

1
2
```
get

public E poll() {//获取队列头部元素,获取后设置为空
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : extract();//如果当前队列为空直接返回null,不为空调用extract()
} finally {
lock.unlock();
}
}
//获取队列头部元素,获取后设置为空
//take获取原始如果队列为空会进入阻塞状态知道等到有添加元素才会去返回
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//lock.lockInterruptibly如果检测到有Thread.interrupted();会直接抛出异常
try {
while (count == 0)
notEmpty.await();//如果没有元素进入等待状态,等待被唤醒
return extract();
} finally {
lock.unlock();
}
}
//peek查看队列头部元素
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : itemAt(takeIndex);//如果元素为空直接返回null,不为空条用itemAt(takeIndex)
} finally {
lock.unlock();
}
}
private E extract() {
final Object[] items = this.items;
E x = this.cast(items[takeIndex]);//泛型转换并且获得当前元素
items[takeIndex] = null;//当前元素设置为空
takeIndex = inc(takeIndex);//获取原始递增
–count;//队列元素递减
notFull.signal();//通知notFull.await()可以进行插入元素
return x;//返回当前获取原始
}
//获取元素
final E itemAt(int i) {
return this.cast(items[i]);
}
```

什么时候扩容

定长队列,不能进行扩容

是否线程安全

线程安全

使用注意事项

  • ArrayBlockingQueue为定长队列
  • ArrayBlockingQueue的添加和获取方法都有提供阻塞和非阻塞的根据需要使用

引用

LinkedBlockingQueue源码解析

什么是LinkedBlockingQueue

LinkedBlockingQueue底层是由节点链表实现的定长阻塞队列(阻塞表示如果没有原始那么获取元素会阻塞当前线程)

LinkedBlockingQueue用来干嘛

LinkedBlockingQueue一般用于生产者消费者模型业务(排队机制,先进先出)

源码解析

数据的存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -6903933977591709194L;
/**
* Linked list node class
*/
static class Node<E> {//存储数据的节点
E item;
Node<E> next;
Node(E x) { item = x; }
}
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;//链表的最大长度,如果不设置值默认为Integer.MAX_VALUE
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger(0);//统计数量线程安全
/**
* Head of linked list.
* Invariant: head.item == null
*/
private transient Node<E> head;//头节点
/**
* Tail of linked list.
* Invariant: last.next == null
*/
private transient Node<E> last;//尾节点
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();//tackLock
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();//tackLock条件不为空
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();//putLock
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();//putLock条件没满
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);//默认last=head=空节点
}

数据的操作

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();//不能存储空元素
int c = -1;
Node<E> node = new Node(e);//创建节点
final ReentrantLock putLock = this.putLock;//获得putLock
final AtomicInteger count = this.count;//获取当前数量
putLock.lockInterruptibly();//获取锁,如果有调用Thread.Interrupted()直接抛出异常
try {
while (count.get() == capacity) {//如果当前队列以满,进入等待状态
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
public boolean offer(E e, long timeout, TimeUnit unit) offer(e)类似
throws InterruptedException {
if (e == null) throw new NullPointerException();//不能存储空元素
long nanos = unit.toNanos(timeout);//装换为纳秒
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);//等待一段时间
}
enqueue(new Node<E>(e));
c = count.getAndIncrement();//递增
if (c + 1 < capacity)//如果未满唤醒notFull.awit
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();//唤醒notEmpty.await()
return true;
}
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
//拆分为两步 last.next = node,last = node
//每次head.next=当前的last然后last.next指向node
last = last.next = node;
}

remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public boolean remove(Object o) {
if (o == null) return false;
fullyLock();//删除数据时全部lock
try {
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
return false;
} finally {
fullyUnlock();
}
}
void unlink(Node<E> p, Node<E> trail) {
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null;
trail.next = p.next;//前后元素执行,大年元素设置为空
if (last == p)
last = trail;
if (count.getAndDecrement() == capacity)//count获取数量同时递减(获取数量为递减钱数量)
notFull.signal();//唤醒 notFull.await()
}

get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//获取元素,消费,可能被中断
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();//如果有调用Thread.Interrupted()抛出异常
try {
while (count.get() == 0) {
notEmpty.await();//元素为空进入等待状态
}
x = dequeue();//
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
//获取元素,消费
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
//查看元素
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
[null,aaa,bbb] queue
[null,bbb] delete after queue
去掉头部null元素获取aaa元素修改aaa元素的item=null
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;//first第一个有值的节点
h.next = h; // help GC
head = first;
E x = first.item;//获取元素
first.item = null;//设置为空
return x;
}

什么时候扩容

定长链表不支持扩容

是否线程安全

线程安全

使用注意事项

  • 默认创建方式链表醉大长度为Ineger.MAX_SIZE

引用

List总结

对比

1
为体现差距使用 快,中等,一般
对比 增加 删除 修改 查询
ArrayList 一般
LinkedList 一般
copyOnWriteList 一般 一般 一般
  • ArrayList删除元素设计到元素的移动
  • LinkedList只能遍历(first last除外)
  • copyOnWriteList增加删除都设计到数组的拷贝

闲话

1
copyOnWriteList没有实际使用过,感觉应用场景不多

LinkedList源码解析

什么是LinkedList

ArrayList底层是由链表组成的一种数据结构,可以进行动态的增删改查

LinkedList用来干嘛

LinkedList一般用于对数据的存储

源码解析

  1. 数据的存储
  2. 数据的操作
  3. 什么时候扩容
  4. 是否线程安全

    带上问题去找答案

数据的存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;

数据的操作

add (addFirst addLast类似)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;//获取last
final Node<E> newNode = new Node<>(l, e, null);//创建Node
last = newNode;//last为新的节点
//如果当前l为空,表示是第一次添加,那么first也会=新的节点
//如果第一次添加就是first=last=newNode
if (l == null)
first = newNode;
//l不为空也就是说不是第一次添加
//当前的last=newNode,而现在由于创建Node的时候已经吧newNode.prev=last也就是说现在是维护双向的关系
else
l.next = newNode;
size++;
modCount++;
}
//创建Node参数prev上一个,element当前元素,next下一个。添加的时候给定prev为last,element为当前,next为空
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}

删除对象 linkedList可以存储null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public boolean remove(Object o) {
//删除元素为空从first开始遍历判断为空
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/*
解除关系
1.将当前Node的prev next item都设置为空
2.将prev节点的next直接指向next(如果prev为空将first指向next)
3.强next节点的prev直接指向prev(如果next为空将last指向prev)
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;//当前元素
final Node<E> next = x.next;//当前元素的下一个节点
final Node<E> prev = x.prev;//当前元素的上一个节点
if (prev == null) {//如果上一个节点为空,那么first将直接指向next
first = next;
} else {
prev.next = next;//当前元素不为空将prev的next直接指向当前元素的下一个节点()
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}

获取

1
因为是链表结构,只支持getFirst,getLast

什么时候扩容

链表没有终点不需要扩容

是否线程安全

线程不安全

使用注意事项

  • LinkedList不支持直接定位到元素

引用

CopyOnWriteArrayList源码解析

什么是CopyOnWriteArrayList

CopyOnWriteArrayList底层是由数组组成的一种数据结构,可以进行动态的增删改查

CopyOnWriteArrayList用来干嘛

CopyOnWriteArrayList一般用于对数据的存储(最好针对少量数据,添加会涉及到整个数组的复制)

源码解析

  1. 数据的存储
  2. 数据的操作
  3. 什么时候扩容
  4. 是否线程安全

    带上问题去找答案

    数据的存储

1
2
3
4
5
6
7
8
9
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/** 用于实现add的同步操作 */
transient final ReentrantLock lock = new ReentrantLock();
/** volatile针对读取时获取最新值,同时作为容器 */
private volatile transient Object[] array;

数据的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
添加
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {//同步操作
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//添加操作设计到整个数组的复制,影响性能
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
删除
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {//同步代码
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));//数组复制
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
获取
public E get(int index) {//基于volatile获取最新值
return get(getArray(), index);
}

什么时候扩容

每次添加删除,针对array做copy操作

是否线程安全

基于Lock实现并发写入的安全,针对并发修改的读取,修改基于copy后的新数组,读取如果未set获取到的还是原数组。如果set后读取到的就是最新的值

使用注意事项

  1. 避免CopyOnWriteArrayList过长,copy影响性能

引用

ArrayList源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
闲来无事,有空会继续写下
List
LinkedList
CopyOnWriteArrayList
Set
HashSet
LinkedHashSet
Map
HashMap
IdentityHashMap
LinkedHashMap
TreeMap
ConcurrentHashMap
Queue
ArrayBlockingQueue
LinkedBlockingQueue
ConcurrentLinkedQueue
为节省空间对于非重点代码不做展示

什么是ArrayList

ArrayList底层是由数组组成的一种数据结构,可以进行动态的增删改查

ArrayList用来干嘛

ArrayList一般用于对数据的存储

源码解析针对重要点

  1. 数据的存储
  2. 数据的操作
  3. 什么时候扩容
  4. 是否线程安全
    带上问题去找答案

    数据的存储

    1
    2
    3
    4
    5
    6
    7
    8
    9
    从我们使用ArrayList开始
    new ArrayList<>();
    public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
    }
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private transient Object[] elementData;
    elementData为储存数据所用容器,通过默认构造方法创建的ArrayList容器为空

数据的操作

添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;//size表示当前使用下表,直接将元素放入到elementData的指定位置
return true;
}
指定位置的添加,设计到元素的后移
public void add(int index, E element) {
rangeCheckForAdd(index);//检查是否超出
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//元素后裔
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));//很常见的异常
}

删除

1
2
3
4
5
6
7
8
9
10
11
12
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
final void checkForComodification() {
if (modCount != expectedModCount)//针对在对于list进行遍历时进行其他操作,modCount会改变,而expectedModCount值在listInterator时给定的会抛出异常
throw new ConcurrentModificationException();
}

查询

1
2
3
4
public E get(int index) {
rangeCheck(index);//这就没啥好多的了
return elementData(index);
}

什么时候扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) { //如果容器为空,初始化容器,两个值中取最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//操作次数+1
// overflow-conscious code
if (minCapacity - elementData.length > 0) //扩容,当前元素数量已经等于容器了需要进行扩容
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//当前容器大小
int newCapacity = oldCapacity + (oldCapacity >> 1);// oldCapacity + oldCapacity*0.5
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);//计算是否超出
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//扩容
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow //如果为负数抛出内存溢出错误
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? //还没有为负数,那么元素最大大小改为int的最大值,注意MAX_ARRAY_SIZE为最大值-8
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

是否线程安全

ArrayList不是线程安全的

1
2
3
4
5
6
7
8
以添加为例
elementData[size++] = e;
一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:
1. 在 Items[Size] 的位置存放此元素;
2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

使用ArrayList的注意事项

  1. arrayList不是线程安全的,不要有多个线程同时操作一个arrayList
  2. 不要在循环中对arrayList做其他操作,会引发异常
  3. 针对已经确定大小的List请直接传入参数,避免多次扩容

参考