Java容器類
Java容器類是java提供的工具包,包含了常用的資料結構:集合、連結串列、佇列、棧、陣列、對映等。
Java容器主要可以劃分為4個部分:List列表、Set集合、Map對映、工具類(Iterator迭代器、Enumeration列舉類、Arrays和Collections)
通過上圖,可以把握兩個基本主體,即Collection和Map。
- Colletcion是一個介面,是高度抽象出來的集合,它包含了集合的基本操作和屬性。Collection包含了List和Set兩大分支。
- List是一個有序的佇列,每一個元素都有它的索引。第一個元素的索引值是0。List的實現類有LinkedList, ArrayList, Vector, Stack。
- Set是一個不允許有重複元素的集合。 Set的實現類有HastSet和TreeSet。HashSet依賴於HashMap,它實際上是通過HashMap實現的;TreeSet依賴於TreeMap,它實際上是通過TreeMap實現的。
- Map是一個對映介面,即key-value鍵值對。Map中的每一個元素包含“一個key”和“key對應的value”。 AbstractMap是一個抽象類,它實現了Map中大部分的API。而HashMap,TreeMap,WeakHashMap都是繼承AbstractMap。Hashtable雖然繼承Dictionary,但是實現的Map介面。
- Iterator是遍歷集合的工具,即我們通常通過Iterator迭代器來遍歷集合。我們說Collection依賴於Iterator,是因為Collection的實現類都要實現iterator()函式,返回一個Iterator物件。ListIterator是專門為遍歷List而存在的。
- Arrays和Collections是運算元組、集合的兩個工具類。
Collection介面
public interface Collection<E> extends Iterable<E> {}
複製程式碼
它是一個介面,是高度抽象出來的集合,它包含了集合的基本操作:新增、刪除、清空、遍歷(讀取)、是否為空、獲取大小、是否保護某元素等等。
在Java中所有實現了Collection介面的類都必須提供兩套標準的建構函式,一個是無參,用於建立一個空的Collection,一個是帶有Collection引數的有參建構函式,用於建立一個新的Collection,這個新的Collection與傳入進來的Collection具備相同的元素。
例如ArrayList:
public ArrayList() {
throw new RuntimeException("Stub!");
}
public ArrayList(Collection<? extends E> c) {
throw new RuntimeException("Stub!");
}
複製程式碼
List介面
public interface List<E> extends Collection<E> {}
List是一個繼承於Collection的介面,List是集合的一種。List是有序的佇列,List中每一個元素都有一個索引;第一個元素索引值是0,往後就依次+1,List中允許有重複的元素。
既然List是繼承於Collection介面,它自然就包含了Collection中的全部函式介面;由於List是有序佇列,它也額外的有自己的API介面。主要有“新增、刪除、獲取、修改指定位置的元素”、“獲取List中的子佇列”等。
// Collection的API
abstract boolean add(E object)
abstract boolean addAll(Collection<? extends E> collection)
abstract void clear()
abstract boolean contains(Object object)
abstract boolean containsAll(Collection<?> collection)
abstract boolean equals(Object object)
abstract int hashCode()
abstract boolean isEmpty()
abstract Iterator<E> iterator()
abstract boolean remove(Object object)
abstract boolean removeAll(Collection<?> collection)
abstract boolean retainAll(Collection<?> collection)
abstract int size()
abstract <T> T[] toArray(T[] array)
abstract Object[] toArray()
// 相比與Collection,List新增的API:
abstract void add(int location, E object)
abstract boolean addAll(int location, Collection<? extends E> collection)
abstract E get(int location)
abstract int indexOf(Object object)
abstract int lastIndexOf(Object object)
abstract ListIterator<E> listIterator(int location)
abstract ListIterator<E> listIterator()
abstract E remove(int location)
abstract E set(int location, E object)
abstract List<E> subList(int start, int end)
複製程式碼
實現List介面的集合主要有:ArrayList、LinkedList、Vector、Stack。
ArrayList
public class ArrayList<E> extends AbstractList<E> implements List<E>,
RandomAccess, Cloneable, Serializable {}
複製程式碼
ArrayList 是一個陣列佇列,相當於動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些介面。
- RandmoAccess為List提供快速訪問功能。在ArrayList中,我們即可以通過元素的序號快速獲取元素物件,這就是快速隨機訪問。
- ArrayList中的操作不是執行緒安全的,所以為了防止意外的非同步訪問,最好在建立時宣告:
List list = Collections.synchronizedList(new ArrayList(...));
ArrayList有七個欄位加一個定義在AbstractList的modCount:
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// Android-note: Also accessed from java.util.Collections
transient Object[] elementData; // non-private to simplify nested class access
private int size;
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
protected transient int modCount = 0;
複製程式碼
ArrayList的預設容量DEFAULT_CAPACITY
為10,EMPTY_ELEMENTDATA
和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是兩個常量。
// 預設建構函式
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// initialCapacity是ArrayList的預設容量大小。當由於增加資料導致容量不足時,容量會新增上一次容量大小的一半。
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);
}
}
// 建立一個包含collection的ArrayList
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;
}
}
複製程式碼
當使用有參建構函式,並且initialCapacity
為0或者Colletion中沒有元素的時候,返回的就是EMPTY_ELEMENTDATA
。當使用預設建構函式publicArrayList(),返回DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。
這兩個陣列都是空的並不會存放值。當第一次往ArrayList新增元素的時候,其實是將元素存放到elementData中,所以真正用來存放元素的是elementData。
add方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index); //判斷索引位置是否正確
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//將ArrayList容器從index開始的所有元素向右移動到index+numNew的位置,從而騰出numNew長度的空間放c
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
複製程式碼
add(E e)
將元素直接新增到列表的尾部。另外3種通過System.arraycopy()
將陣列進行拷貝。
add(int index, E element)
通過將index的位置空出來,進行陣列資料的右移,這是非常麻煩和耗時的,所以如果指定的資料集合需要進行大量插入(中間插入)操作,需要考慮效能的消耗。
addAll(Collection<? extends E> c)
按照指定 collection 的迭代器返回的元素順序,將該 collection 中的所有元素新增到此列表的尾部。
addAll(int index, Collection<? extends E> c)
從指定的位置開始,將指定 collection 中的所有元素插入到此列表中。
remove方法:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//向左移的位數,下標從0開始,需要再多減1
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;
}
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()方法用於移除指定位置的元素,和remove方法類似,區別是void型別
fastRemove(index);
return true;
}
}
return false;
}
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
public boolean removeAll(Collection<?> c) {
//Checks that the specified object reference is not null
Objects.requireNonNull(c);
//false是移除相同元素,方法retainAll中置為true,是保留相同元素
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r, elementData, w, size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
複製程式碼
擴容
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
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);
}
複製程式碼
ArrayList每次新增元素時都會需要進行容量檢測判斷,若新增元素後元素的個數會超過ArrayList的容量,就會進行擴容操作來滿足新增元素的需求。所以當我們清楚知道業務資料量或者需要插入大量元素前,可以使用ensureCapacity來手動增加ArrayList例項的容量,以減少遞增式再分配的數量。
迭代效率
public static void loopOfFor(List<Integer> list){
int value;
int size = list.size();
// 基本的for
for (int i = 0; i < size; i++)
{
value = list.get(i);
}
}
/**
* 使用forecah方法遍歷陣列
* @param list
*/
public static void loopOfForeach(List<Integer> list){
int value;
// foreach
for (Integer integer : list)
{
value = integer;
}
}
/**
* 通過迭代器方式遍歷陣列
* @param list
*/
public static void loopOfIterator(List<Integer> list){
int value;
// iterator
for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();)
{
value = iterator.next();
}
}
複製程式碼
在遍歷ArrayList中,效率最高的是loopOfFor,loopOfForeach和loopOfIterator之間關係不明確,但在增大執行次數時,loopOfIterator效率高於loopOfForeach。
LinkedList
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>,
Deque<E>, Cloneable, Serializable {}
複製程式碼
LinkedList繼承於AbstractSequentialList,實現了List, Deque, Cloneable, java.io.Serializable這些介面。
AbstractSequentialList繼承AbstractList,在功能上,最大限度地減少了實現受“連續訪問”資料儲存所需的工作。
簡單的說是你的列表需要快速的新增刪除資料等,用此抽象類,若是需要快速隨機的訪問資料等用AbstractList抽象類。
同ArrayList一樣,LinkedList中的操作不是執行緒安全的,所以為了防止意外的非同步訪問,最好在建立時宣告:
List list = Collections.synchronizedList(new LinkedList(...));
LinkedList實現了一個雙向列表,由first欄位和last欄位指向列表的頭部和尾部。列表的每個節點是一個Node物件。
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;
}
}
複製程式碼
// 預設建構函式:建立一個空的連結串列
public LinkedList() {
header.next = header.previous = header;
}
// 包含“集合”的建構函式:建立一個包含“集合”的LinkedList
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
//若插入的位置小於0或者大於連結串列長度,則丟擲IndexOutOfBoundsException異常
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;//插入元素個數
if (numNew == 0)
return false;
Node<E> pred, succ; //定義前導與後繼
if (index == size) { //如果在隊尾插入
succ = null; //後繼置空
pred = last; //前導指向隊尾元素last
} else { //在指定位置插入
succ = node(index); //後繼指向該位置
pred = succ.prev; //先導指向前一個元素
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);//建立一個新節點,指定先導,後繼置空
if (pred == null)//如果先導不存在
first = newNode;//表頭first指向此節點
else
pred.next = newNode;//先導存在,則將其next指向新節點
pred = newNode;//先導移動,繼續建立新節點
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
複製程式碼
LinkedList提供了一系列API用於插入和刪除元素。 例linkFirst()
,linkLast()
,linkBefore()
,
unlinkFirst()
,unlinkLast()
,unlink()
。
在get、set、add、remove方法中都用到了一個 node方法,它將輸入的index與連結串列長度的1/2進行對比,小於則從first開始操作,否則從last開始操作,節省一般的查詢時間。
/**
* 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;
}
}
複製程式碼
LinkedList通過代價較低在List中間進行插入和移除,提供了優化的順序訪問,但是在隨機訪問方面相對較慢。
上面都提到了ArrayList、LinkedList都是非執行緒安全的,面對多執行緒對操作時,可能會產生的fail-fast事件,丟擲異常java.util.ConcurrentModificationException。而ConcurrentModificationException是在操作Iterator時丟擲的異常。Iterator裡定義了一個叫expectedModCount
的變數,初始化等於modCount
的值。從ArrayList原始碼可以看到各種操作都會修改modCount
的值。
解決方案用CopyOnWriteArrayList
代替ArrayList
//CopyOnWriteArrayList
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray(); //copy一份原來的array
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e; //在copy的陣列上add
setArray(newElements); //原有引用指向修改後的資料
return true;
} finally {
lock.unlock();
}
}
複製程式碼
CopyOnWriteArrayList
在各種操作中都是先copy一份原來的array,然後操作,最後將原有的資料引用指向修改後的資料。
Vector
public class Vector extends AbstractListimplements List, RandomAccess, Cloneable,
java.io.Serializable{}
複製程式碼
與ArrayList相似,但是Vector是同步的。所以說Vector是執行緒安全的動態陣列。它的操作與ArrayList幾乎一樣。
Vector,ArrayList與LinkedList區別,應用場景是什麼?
- Vector實現了基於動態Object陣列的資料結構,執行緒安全,可以設定增長因子,效率比較低,不建議使用。
- ArrayList實現了基於動態Object陣列的資料結構,非執行緒安全,地址連續,查詢效率比較高,插入和刪除效率比較低。適合查詢操作頻繁的場景。
- LinkedList實現了基於連結串列的資料結構,非執行緒安全,地址不連續,查詢效率比較低,插入和刪除效率比較高。適合插入和刪除操作頻繁的場景。