1. List集合
1.1 ArrayList
1.11 基本特点
ArrayList是一个实现了List接口的可变数组
可以插入null
它的size, isEmpty, get, set, iterator,add这些方法的时间复杂度是O(1),如果add n个数据则时间复杂度是O(n).
ArrayList不是synchronized的,所以线程不安全
-
关注点 结论 ArrayList是否为空 允许 ArrayList是否允许重复数据 允许 ArrayList是否有序 有序 ArrayList是否线程安全 非线程安全
1.12 源码分析
构造函数
//默认容量 private static final int DEFAULT_CAPACITY = 10; //空数组 private static final Object[] EMPTY_ELEMENTDATA = {}; //默认空数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //存放数据的数组 transient Object[] elementData public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
add方法
- 去add后的大小和默认值(10)的最大值
- 查看这个最大值是否超过了数组的长度
- 超过则扩容,newCapacity = oldCapacity + (oldCapacity >> 1)
- 判断newCapacity 是否超过数组长度最大值,超过newCapacity 则取Integer.MAX_VALUE
- 创建新数组 进行复制
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 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); 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); } //数组所能允许的最大长度;如果超出就会报`内存溢出异常` -- 可怕后果就是宕机 //根本作用在于只是为了避免一些机器内存溢出,是否-8其实并不重要,实际最大长度还是Integer.MAX_VALUE //之所以要-8是因为有些版本的虚拟机会保留8个字节长度的header,下面以HotSpot为例 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //注意:最大容量是 Integer.MAX_VALUE 而不是 MAX_ARRAY_SIZE=Integer.MAX_VALUE-8 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
remove方法
直接使用System.arraycopy把需要删除index后面的都往前移一位然后再把最后一个去掉
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work, return oldValue; }
1.13 ArrayList的优缺点
- 优点:
- ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
- ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已
- 缺点:
- 删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
- 插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
1.14 ArrayList和Vector的区别
区别:ArrayList是线程非安全的,Vector是线程安全的;扩容方式有区别,Vector里有个增长系数参数,可以构造方法中设置,如果没有设置则默认为0。在扩容的时候如果这个增长系数为0,则新容量是旧容量的两倍,否则就是旧容量加上这个增长系数作为新容量
解决线程安全性的办法:
Collections.synchronizedList方法把你的ArrayList变成一个线程安全的List,比如:
List<String> synchronizedList = Collections.synchronizedList(list); synchronizedList.add("aaa"); synchronizedList.add("bbb"); for (int i = 0; i < synchronizedList.size(); i++){ System.out.println(synchronizedList.get(i)); }
使用Vector
1.15 为什么ArrayList的elementData是用transient修饰
- 因为序列化ArrayList的时候,ArrayList里面的elementData未必是满的,所以没有必要序列化整个elementData呢,因此ArrayList中重写了writeObject方法:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
- 每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化那些有的元素,这样:
- 加快了序列化的速度
- 减小了序列化之后的文件大小
1.2 LinkedList
双向链表
1.21 与ArrayList对比
- ArrayList:数组形式访问List链式集合数据,元素可重复,访问元素较快
- LinkedList:链表方式的List链式集合,元素可重复,元素的插入删除较快
1.22 特性
关注点 | 结论 |
---|---|
LinkedList是否允许空 | 允许 |
LinkedList是否允许重复数据 | 允许 |
LinkedList是否有序 | 有序 |
LinkedList是否线程安全 | 非线程安全 |
1.23 源码分析
- 查找使用二分法
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
1.3 CopyOnWriteArrayList
java.util.concurrent包下的并发安全类,任何可变操作都伴随着copy动作
1.31 特性
关注点 | 结论 |
---|---|
CopyOnWriteArrayList是否允许为空 | 允许 |
CopyOnWriteArrayList是否允许重复数据 | 允许 |
CopyOnWriteArrayList是否有序 | 有序 |
CopyOnWriteArrayList是否为线程安全 | 线程安全 |
1.32 源码分析
实例化CopyOnWriteArrayList
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ transient final ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private volatile transient Object[] array; ... public CopyOnWriteArrayList() { setArray(new Object[0]); } final void setArray(Object[] a) { array = a; } }
对于CopyOnWriteArrayList来说,底层就是一个Object[] array,然后实例化一个CopyOnWriteArrayList
添加操作(add)
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(); } }
步骤:
- 加锁
- 拿到原数组,得到新数组的大小(原数组大小+1),实例化出一个新的数组来
- 把原数组的元素复制到新数组中去
- 新数组最后一个位置设置为待添加的元素(因为新数组的大小是按照原数组大小+1来的)
- 把Object array引用指向新数组
- 解锁
1.33 优缺点及使用场景
- 缺点:修改代价十分昂贵,每次修改都伴随着一次的数组复制
- 优点:在并发下不会产生任何的线程安全问题,也就是绝对的线程安全
- 读写分离:取CopyOnWriteArrayList的时候读取的是CopyOnWriteArrayList中的Object[] array,但是修改的时候,操作的是一个新的Object[] array,读和写操作的不是同一个对象,这就是读写分离
- 最终一致:对CopyOnWriteArrayList来说,线程1读取集合里面的数据,未必是最新的数据。因为线程2、线程3、线程4四个线程都修改了CopyOnWriteArrayList里面的数据,但是线程1拿到的还是最老的那个Object[] array,新添加进去的数据并没有,所以线程1读取的内容未必准确。不过这些数据虽然对于线程1是不一致的,但是对于之后的线程一定是一致的,它们拿到的Object[] array一定是三个线程都操作完毕之后的Object array[],这就是最终一致。
- 使用场景:CopyOnWriteArrayList中元素的增加,CopyOnWriteArrayList的修改代价将越来越昂贵,因此,CopyOnWriteArrayList适用于读操作远多于修改操作的并发场景中