collection中幾種常用的集合類型特點
| 集合類型 | 是否允許空 | 是否允許重複數據 | 是否有序 | 是否線程安全
| ------------- |-------------| -----| -----|
| ArrayList | 是| 是 | 是 | 否
| Vector | 是| 是 | 是 | 是(相對線程安全)
| LinkedList | 是| 是 | 是 | 否
|CopyOnWriteArrayList | 是| 是 | 是 | 是(絕對的線程安全)
所有代碼均是jdk1.8版本下
底層實現
ArrayList
private transient Object[] elementData;
是由一個transient修飾的object數組實現
ps:transient是不希望elementData被序列化,因爲arrayList初始化之後是有固定的長度的,如默認的10,但elementData裏面可能只有3個元素,沒必要序列化整個elementData
Vector
與ArrayList類似,vector底層也是由Object[] 數組實現
protected Object[] elementData;
不同的是 vector是沒有transient來修飾的。意味了在序列化的時候會將數組中爲空的元素也序列化
LinkedList
LinkedList的各項特質與ArrayList一致,但是底層實現上是完全不一樣的。
LinkedList是基於鏈表實現,是一種雙向鏈表,鏈表是一種線性的存儲結構,鏈表中的每個元素都由三部分組成。
Node<E> prev, E element, Node<E> next
prev和next分別指向前一個存儲單元和後一個存儲單元的引用地址
CopyOnWriteArrayList
顧名思義,這個類就是arraylist的併發版本,位於juc包下。根據CopyOnWrite可以看出這個類是讀寫分離的。任何可變的操作都是伴隨着複製。
底層也是由
private transient volatile Object[] array;
輕量級volatile修飾的一個Object數組
初始化
ArrayList
/**如不指定初始化集合大小,默認是10,這個10不是new ArrayList<>()初始化的時候賦值的,是在第一次調用的時候賦值*/
List<Object> list = new ArrayList<>();
/**初始化一個大小爲16的數組*/
List<Object> list1 = new ArrayList<>(16);
Vector
/**不指定容量的大小, 會調new Vector<>(10) 方法創建一個大小爲10的數組*/
Vector<Object> vector = new Vector<>();
/**指定容量大小*/
Vector<Object> vector1 = new Vector<>(16);
/**指定容量大小和增長因子*/
Vector<Object> vector2 = new Vector<>(16, 2);
List<Object> list = new ArrayList<>();
/**將Collection的集合初始化到Vector中,大小是list的長度*/
Vector<Object> vector3 = new Vector<>(list);
LinkedList
/**linkedlist的初始化沒有任何容量跟增長因子*/
List<Object> list = new LinkedList<>();
List<Object> tempList= new ArrayList<>();
/**將Collection的集合初始化到LinkedList中,大小是list的長度*/
List<Object> list1 = new LinkedList<>(tempList);
/**LinkedList的內部類*/
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
CopyOnWriteArrayList
private transient volatile Object[] array;
/**getter/setter方法*/
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
/**創建一個空數組*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
與array list的區別是並沒有初始大小的數組,而是空數組
添加
ArrayList
transient Object[] elementData;
//默認容量大小
private static final int DEFAULT_CAPACITY = 10;
//默認空數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//數組的長度
private int size;
//最大的數組長度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 在數組隊尾添加一個元素
*/
public boolean add(E e) {
//確保內部容量
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
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 ensureCapacityInternal(int minCapacity) {
//如果當前數組爲空,則獲取 默認容量 跟 參數容量中大的一個
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//當前容器修改的次數
modCount++;
/**如果不可用了,增加容量*/
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
/**
* 看到好多文檔,博客上直接寫明擴容的長度就是原來的1.5倍,
* 個人理解,oldCapacity的值可能是 0,10,15,22, 33, 48, 62 並不是嚴格意義上的1.5倍
* 初始後的第一次擴容,oldCapacity = 10 ,二進制是 1010 右移 0101是5,新容量是15
* 第二次擴容 oldCapacity 15 二進制是1111 右移 1 得 0111 是 7 新容量是22
*/
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);
}
新增的最後都調用了Arrays.copyOf System.arrayCopy 等函數,創建一個新的數組,將原來的數組賦值到新數組上
Vector
與ArrayList實現方式類似,不同的方法簽名上是有Synchronized來修飾的。
//擴容的方式
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
/**如果指定了增長因子,則每次擴容加上增長因子,如果沒有,則翻倍*/
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//獲取當前鏈表中最後一個元素
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
CopyOnWriteArrayList
final transient ReentrantLock lock = new ReentrantLock();
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 void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
對當前操作的方法加上重入鎖,每次有更新動作都會複製一個新數組,並將原數組的引用轉移到新數組上。
刪除
ArrayList
/**
移除指定下標的元素
*/
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;
}
在指定位置插入元素的時間複雜度是o(size - index)
/**
* 移除第一個等於o的元素
*/
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
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
}
Vector
與ArrayList實現方式類似,不同的方法簽名上是有Synchronized來修飾的。
LinkedList
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
public boolean remove(Object o) {
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;
}
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;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
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;
}
}
移除操作跟新增操作都是操作node的prev跟next將它們的引用指向新的元素
爲了找到插入,刪除的元素,linkedlist的操作方式是分別從首尾兩端向index處進行遍歷,時間複雜度是o(index)
CopyOnWriteArrayList
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();
}
}
總結
ArrayList
從ArrayList的源碼不難看出它的核心方法是擴容和數組複製
優點如下:
- 底層是數組實現,是一種隨機訪問模式,實現了RandomAccess接口,查找速度快
- 順序添加元素的時候快,
缺點:
插入和刪除時涉及到數組的複製拷貝,如果數組的元素較多,會影響性能。
LinkedList
1.插入效率
順序插入時ArrayList會比較快,因爲linkedlist每次新增一個對象出來,如果對象比較大,那麼new的時間勢必會長一點,再加上一些引用賦值的操作,所以順序插入LinkedList必然慢於ArrayList。
ArrayLIst隨機插入的時間複雜度是o(size - index)
LinkedList隨機插入的時間複雜度是o(index) index 最大不會超過size的一半
如果待插入、刪除的元素是在數據結構的前半段尤其是非常靠前的位置的時候,LinkedList的效率將大大快過ArrayList,因爲ArrayList將批量copy大量的元素;越往後,對於LinkedList來說,因爲它是雙向鏈表,所以在第2個元素後面插入一個數據和在倒數第2個元素後面插入一個元素在效率上基本沒有差別,但是ArrayList由於要批量copy的元素越來越少,操作速度必然追上乃至超過LinkedList
2.內存會比ArrayList耗費更多一點
3.使用各自遍歷效率最高的方式,ArrayList的遍歷效率會比LinkedList的遍歷效率高一些
ps:如果使用普通for循環遍歷LinkedList,其遍歷速度將慢得令人髮指。
CopyOnWriteArrayList
優點:
絕對的線程安全
缺點:
每次寫操作都會伴隨數組的複製,性能開銷大
帶來的兩個很重要的分佈式理念:
讀寫分離和最終一致