java集合框架之List集合
阿新 • • 發佈:2019-01-14
list集合是工作中使用最頻繁的一種集合。它是一種有序的集合結構。可以用於儲存重複的元素。通過索引位置我們可以快速的定位元素進行操作。他的實現類主要有三個ArrayList,LinkedList,Vector。
- ArrayList
ArrayLis是使用最頻繁的一種集合。它是可以動態增長和縮減的索引序列,它是基於陣列實現的List類,為什麼說它是基於陣列實現的呢我們可以看JDK的原始碼進行分析。
//我們先檢視ArrayList定義的變數
transient Object[] elementData; // non-private to simplify nested class access
//再看其中比較重要的構造方法
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(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;
}
}
//通過這兩個構造方法我們可以看出。在建立ArrayList類的過程中。實際上是建立了一個數組。這個資料根據構造方法進行判斷。
// 如果傳入的是長度。則根據長度構造空陣列。如果傳入的是集合。則將集合元素拷貝進建立的陣列結構中。
// 這裡有個需要注意的是elementData變數,它是用transient 進行了修飾。我們知道這個關鍵字的作用是不被序列化。
// 為什麼這個變數特意用這個修飾呢。由於在ArrayList中的elementData這個陣列的長度是變長的,java在擴容的時候,有一個擴容因
//子,也就是說這個陣列的長度是大於等於ArrayList的長度的,我們不希望在序列化的時候將其中的空元素也序列化到磁碟中去,
//所以需要手動的序列化陣列物件,所以使用了transient來禁止自動序列化這個陣列
//下面再看它的新增方法
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return 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進行新增元素的時候。先進行判斷集合長度是否為0,如果為0則傳入預設值10進行擴容。如果長度不為0.則進行長度
// 判斷。如果陣列未飽和則至今將元素放入。如果長度超出則進行擴容。檢視grow()方法我們可以看出通過 Arrays.copyOf()
//方法將建立一個原來長度1.5倍的新陣列。並將元素複製進去。
// 另外還有一個新增方法也用的較多
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++;
}
//也是先進行擴容。然後再將插入位置往後的所有元素複製到擴容後的數組裡面。索引之後的所有元素都發生了位置改變,
// 所以如果是頻繁的進行索引插入而且集合長度非常大的話。會極度降低效率。
//再看其移除方法
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;
}
同樣也會造成陣列元素位置的重新調整。
- LinkedList
LinkedList類是雙向列表,列表中的每個節點都包含了對前一個和後一個元素的引用.類是雙向列表,列表中的每個節點都包含了對前一個和後一個元素的引用。
//先看他的2個構造方法
public 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) {
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;
} 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;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
//通過這兩個構造方法我們可以看出這兩個集合有很大的不同它不再是採用陣列進行儲存了。而是用Node存了元素。
//每個Node記錄了上一個元素以及下一個元素。形成了類似鎖鏈的結構。
// 再看其新增和刪除的方法
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++;
}
// 將每個新的元素都加入到原來鎖鏈的下一個元素中。並將集合的last變數賦值為最新的元素Node。
//如果是按位置進行插入
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(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;
}
}
//它是先從頭開始進行鏈式查詢。找到索引對應的元素。不能像ArrayList一樣直接定位元素地址。
//找到元素以後對元素的上個元素和下個元素進行替換。在鎖鏈中加入了一個新的環扣。而其他的鎖鏈並不會發生變化。
//不需要像ArrayList一樣進行位置調整。
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//再看其移除方法
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;
}
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
// 也是通過鎖鏈查詢到元素對應的node並將node的上下兩個元素的位置替換。解除掉刪除元素的連線繫結。
//不會對其他元素造成影響
所以通過這幾個方法的分析我們可以得出這兩種集合的特點
1.對於隨機訪問的get和set方法,ArrayList要優於LinkedList,因為LinkedList要從頭開始查詢
2.對於新增和刪除操作add和remove,LinkedList比較佔優勢,因為ArrayList要移動很多資料
3.ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,
而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間。
- Vector
Vector與ArrayList一樣,也是通過陣列實現的,不同的是它支援執行緒的同步。
//先看其構造方法
protected int capacityIncrement;
public Vector() {
this(10);
}
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
//不同於ArrayList。它提供了四種構造方法但是其實現還是依靠陣列進行實現的。
//其中有個引數capacityIncrement。這個引數通過查詢其使用。發現是用於擴容的我們檢視grow()。
// 看到其擴容計算方式:int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
// 是100%進行擴容的不同於ArrayList的50%。
//檢視其新增方法
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
//其方法和ArrayList的實現是基本一致的。不容的在於方法用同步鎖進行了鎖定。不僅是新增方法。
// 其它方法也進行了同步鎖定。保證了執行緒安全。但是也降低了其效能
通過查詢得知Vector還有一個子類Stack。它通過五個操作對類Vector進行了擴充套件 ,允許將向量視為堆疊。它提供了通常的push和pop操作,以及取堆疊頂點的peek方法、測試堆疊是否為空的empty方法、在堆疊中查詢項並確定到堆疊頂距離的search方法。
這兩個都是jdk1.0的過時API,應該避免使用。
如果需要集合保證執行緒安全推薦使用CopyOnWriteArrayList和ConcurrentLinkedQueue。這兩個以後進行分析