一、什麼是List
List是一個有序的集合,和set不同的是,List允許存儲項的值爲空,也允許存儲相等值的存儲項。
List繼承了 Collection的接口,因此包括 Collection提供的各種方法,初次之外還擴展了自己的方法,如下圖所示。
二、List的分類
(一)ArrayList
1、什麼是ArrayList
ArrayList是一個數組實現的列表,底層是數組,由於數據是存入數組中的,所以它的特點也和數組一樣,查詢很快,但是中間部分的插入和刪除很慢。
2、與Array的區別
- ArrayList內部封裝了一個Object類型的數組,所以本質上沒有區別,ArrayList的很多方法都是直接在內部數組的基礎上調用的Array的方法。但是ArrayList還有其他功能因此更加複雜。
- ArrayList可以存儲不同類型的數據,但是Array只能存儲相同數據類型的數據。
- Array的長度不可變,ArrayList是變長的,雖然底層是通過擴展數據長度來實現的,但是表面上看是變長的。
- 存取和增刪元素
對於一般的引用類型來說,這部分的影響不是很大,但是對於值類型來說,往ArrayList裏面添加和修改元素,都會引起裝箱和拆箱的操作,頻繁的操作可能會影響一部分效率。另外,ArrayList是動態數組,它不包括通過Key或者Value快速訪問的算法,所以實際上調用IndexOf、Contains等方法是執行的簡單的循環來查找元素,所以頻繁的調用此類方法並不比你自己寫循環並且稍作優化來的快,如果有這方面的要求,建議使用Hashtable或SortedList等鍵值對的集合。
3、方法探究
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.默認長度
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
* DEFAULT_CAPACITY when the first element is added.
*/
//從這裏可以看到,ArrayList的底層是由數組實現的,並且默認數組的默認大小是10
private transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
ArrayList的構造函數
ArrayList有2個構造函數,一個是默認無參的,一個是有參的(數組大小)
如果我們可以預估數組大小,最好使用使用有參構造函數。因爲如果使用無參的構造函數,會首先把EMPTY_ELEMENTDATA(默認是10)賦值給elementData
然後根據插入個數於當前數組size比較,不停調用Arrays.copyOf()方法,擴展數組大小造成性能浪費。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
add操作
//從尾部插入
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,把index開始所有數據向後移動一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//將新元素插入到index位置
elementData[index] = element;
size++;
}
private void ensureCapacityInternal(int minCapacity) {
//如果數組爲空
if (elementData == 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/2),但性能會好一些,
int newCapacity = oldCapacity + (oldCapacity >> 1);//根據老數組長度計算新數組長度,默認每次都是自增50%的大小
//下面判斷保證新數組元素的個數在正常範圍內
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);//用新數組繼續下面的處理
}
(二)Vector
Vector可以看做是線程安全版的ArrayList,利用synchronized來實現線程安全,所以他的一大弊端就是性能不高,而且這個類太古老已經被棄用。
因此如果我們對線程安全性要求不高就可以使用ArrayList,如果必須保證線程安全可以使用Collections中提供的方法,轉變成線程安全的類。
(三)LinkedList
LinkedList底層是一個雙向鏈表。LinkedList繼承於AbstractSequentialList,和ArrayList一個套路。內部維護了3個成員變量,一個是當前鏈表的頭節點,一個是尾部節點,還有是鏈表長度。
方法探究
add(E e)操作
//從指定位置插入(中間插入)
public void add(int index, E element) {
checkPositionIndex(index);
//如果插入位置和size相等相當於尾部插入
if (index == size)
linkLast(element);
else
//否則就是中部插入
linkBefore(element, node(index));
}
//尾部插入
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
原來尾節點的next指向新增的節點
l.next = newNode;
//數量加一
size++;
modCount++;
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//pred爲插入節點的指向的前一個位置
final Node<E> pred = succ.prev;
//新增的節點
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
//如果指向前一個爲空則說明這個鏈表爲空,將新節點賦給頭結點
if (pred == null)
first = newNode;
else
//否則將原來index的pred的next指向新節點
pred.next = newNode;
//數量加1
size++;
modCount++;
}
indexOf操作
從頭開始遍歷整個鏈表,如果沒有就反-1,有就返回當前下標
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
remove操作
//無參的刪除方法默認刪除頭結點
public E remove() {
return removeFirst();
}
//刪除中間節點
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//刪除頭結點
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//x的prev指向x的next
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;
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
//刪除頭節點非常簡單,就是把頭節點的值清空,next清空
f.item = null;
f.next = null; // help GC
//把nextNode設爲頭節點
first = next;
if (next == null)
last = null;
else
//清空next的prev
next.prev = null;
//size減1
size--;
modCount++;
return element;
}
三、總結
List的特性:
- 按順序查找
- 可以存null值
- 可以存重複的值
對比LinkedList和ArrayList的實現方式不同,可以在不同的場景下使用不同的List 。ArrayList是由數組實現的,方便查找,返回數組下標對應的值即可,適用於多查找的場景 ,LinkedList由鏈表實現,插入和刪除方便,適用於多次數據替換的場景。
ArrayList、LinkedList、Vector的區別和實現原理