1. 程式人生 > >深入理解Java集合之List

深入理解Java集合之List

List筆錄

List相較於set、map,是按照一定順序儲存,List主要分為3類,ArrayList, LinkedList和Vector。以下是List的結構圖,本文章重點講解ArrayList與LinkedList的底層實現原理。


ArrayList

ArrayList採用了快速失敗的機制,通過記錄modCount引數來實現。在面對併發的修改時,迭代器很快就會完全失敗,而不是冒著在將來某個不確定時間發生任意不確定行為的風險。

優點:隨機訪問元素

缺點:中間插入和移除元素速度較慢。
定義
public class ArrayList<E> extends AbstractList<E>  implements List<E>, RandomAccess, Cloneable, java.io.Serializable 
1.AbstractList提供了List介面的預設實現(個別方法為抽象方法)。
2.List介面定義了列表必須實現的方法。
3.RandomAccess是一個標記介面,介面內沒有定義任何內容。
4. 實現了Cloneable介面的類,可以呼叫Object.clone方法返回該物件的淺拷貝。

5. 通過實現 java.io.Serializable 介面以啟用其序列化功能。未實現此介面的類將無法使其任何狀態序列化或反序列化。序列化介面沒有方法或欄位,僅用於標識可序列化的語義。 

底層原理
1.底層使用陣列實現
有兩個私有屬性:
private transient Object[] elementData; 
(elementData儲存內部元素,Java的serialization提供了一種持久化物件例項的機制。當持久化物件時,可能有一個特殊的物件資料成員,我們不想用serialization機制來儲存它。為了在一個特定物件的一個域上關閉serialization,可以在這個域前加上關鍵字transient)
private int size; 
(size儲存元素數量)
2.構造方法:
1.構造一個預設初始容量為10的空列表。
public ArrayList() {
	this(10);
} 


2.構造一個指定初始容量的空列表
public ArrayList(int initialCapacity) {  
	super();  
	if (initialCapacity < 0)  
		throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);  
	this.elementData = new Object[initialCapacity];  
} 

3.構造一個包含指定collection的元素的列表,這些元素按照collection的迭代器返回它們的順序排列。(返回若不是Object[]將呼叫Arrays.copyOf方法將其轉為Object[])


public ArrayList(Collection<? extends E> c) {
	elementData = c.toArray();
	size = elementData.length;
	// c.toArray might (incorrectly) not return Object[] (see 6260652)
	if (elementData.getClass() != Object[].class)
		elementData = Arrays.copyOf(elementData, size, Object[].class);
}


3.儲存:

ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)這些新增元素的方法。

public boolean add(E e) {
	ensureCapacity(size + 1); // Increments modCount!!
	elementData[size++] = e;
	return true;
}


看到add(E e)中先呼叫了ensureCapacity(size+1)方法,之後將元素的索引賦給elementData[size],而後size自增。例如初次新增時,size為0,add將elementData[0]賦值為e,然後size設定為1(類似執行以下兩條語句elementData[0]=e;size=1)。將元素的索引賦給elementData[size]不是會出現陣列越界的情況嗎?這裡關鍵就在ensureCapacity(size+1)中了。
public void ensureCapacity(int minCapacity) {
	modCount++;
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
		Object oldData[] = elementData;
		int newCapacity = (oldCapacity * 3) / 2 + 1;
		if (newCapacity < minCapacity) {
			newCapacity = minCapacity;
		}

		// minCapacity is usually close to size, so this is a win:
		elementData = Arrays.copyOf(elementData, newCapacity);
	}
}

// 將指定的元素插入此列表中的指定位置。
// 如果當前位置有元素,則向右移動當前位於該位置的元素以及所有後續元素(將其索引加1)。
public void add(int index, E element) {
	if (index > size || index < 0)
		throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
				+ size);
	// 如果陣列長度不足,將進行擴容。
	ensureCapacity(size + 1); // Increments modCount!!
	// 將 elementData中從Index位置開始、長度為size-index的元素,
	// 拷貝到從下標為index+1位置開始的新的elementData陣列中。
	// 即將當前位於該位置的元素以及所有後續元素右移一個位置。
	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;
	ensureCapacity(size + numNew); // Increments modCount
	System.arraycopy(a, 0, elementData, size, numNew);
	size += numNew;
	return numNew != 0;
}


先將集合c轉換成陣列,根據轉換後陣列的程度和ArrayList的size拓展容量,之後呼叫System.arraycopy方法複製元素到elementData的尾部,調整size。根據返回的內容分析,只要集合c的大小不為空,即轉換後的陣列長度不為0則返回true。
public boolean addAll(int index, Collection<? extends E> c) {
	if (index > size || index < 0)
		throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
				+ size);
	Object[] a = c.toArray();
	int numNew = a.length;
	ensureCapacity(size + numNew); // Increments modCount
	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;
}


先判斷index是否越界。其他內容與addAll(Collection<? extends E> c)基本一致,只是複製的時候先將index開始的元素向後移動X(c轉為陣列後的長度)個位置(也是一個複製的過程),之後將陣列內容複製到elementData的index位置至index+X。 
// 用指定的元素替代此列表中指定位置上的元素,並返回以前位於該位置上的元素。
public E set(int index, E element) {
	RangeCheck(index);
	E oldValue = (E) elementData[index];
	elementData[index] = element;
	return oldValue;
} 

 
4.讀取

// 返回此列表中指定位置上的元素。
public E get(int index) {
	RangeCheck(index);
	return (E) elementData[index];
}

private void RangeCheck(int index) {
	if (index >= size)
		throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
				+ size);
}


5.刪除
// 移除此列表中指定位置上的元素。
public E remove(int index) {
	RangeCheck(index);
	modCount++;
	E oldValue = (E) elementData[index];
	int numMoved = size - index - 1;
	if (numMoved > 0)
		System.arraycopy(elementData, index + 1, elementData, index,
				numMoved);
	elementData[--size] = null; // Let gc do its work
	return oldValue;
}

// 移除此列表中首次出現的指定元素(如果存在)。這是應為ArrayList中允許存放重複的元素。
public boolean remove(Object o) {
	// 由於ArrayList中允許存放null,因此下面通過兩種情況來分別處理。
	if (o == null) {
		for (int index = 0; index < size; index++) {
			if (elementData[index] == null) {
				// 類似remove(int index),移除列表中指定位置上的元素。
				fastRemove(index);
				return true;
			}
		}
	} else {
		for (int index = 0; index < size; index++) {
			if (o.equals(elementData[index])) {
				fastRemove(index);
				return true;
			}
		}
	}
	return false;
}

6.資料清除:
public void clear() {
	modCount++;
	// Let gc do its work
	for (int i = 0; i < size; i++) {
		elementData[i] = null;
	}
	size = 0;
}


7.資料複製:
public Object clone() {
	try {
		ArrayList<E> v = (ArrayList<E>) super.clone();
		v.elementData = Arrays.copyOf(elementData, size);
		v.modCount = 0;
		return v;
	} catch (CloneNotSupportedException e) {
		// this shouldn't happen, since we are Cloneable
		throw new InternalError();
	}
}


8.查詢資料是否存在:
public boolean contains(Object o) {
	return indexOf(o) >= 0;
}


9.查詢資料座標:
public int indexOf(Object o) {
	if (o == null) {
		for (int i = 0; i < size; i++)
			if (elementData[i] == null)
				return i;
	} else {
		for (int i = 0; i < size; i++)
			if (o.equals(elementData[i]))
				return i;
	}
	return -1;
}

10.轉化為陣列:
public Object[] toArray() {
	return Arrays.copyOf(elementData, size);
}
11.
public void trimToSize() {
	modCount++;
	int oldCapacity = elementData.length;
	if (size < oldCapacity) {
		elementData = Arrays.copyOf(elementData, size);
	}
}

將elementData的陣列設定為ArrayList實際的容量,動態增長的多餘容量被刪除了。


LinkedList

優點:中間插入和移除元素代價比較小,優化了順序訪問,在Queue或者棧中應用
缺點:隨機訪問比較慢
定義:

public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable
1.LinkedList 是一個繼承於AbstractSequentialList的雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作。
2.LinkedList 實現 List 介面,能對它進行佇列操作。
3.LinkedList 實現 Deque 介面,即能將LinkedList當作雙端佇列使用。
4.LinkedList 實現了Cloneable介面,即覆蓋了函式clone(),能克隆。
5.LinkedList 實現java.io.Serializable介面,這意味著LinkedList支援序列化,能通過序列化去傳輸。
6.LinkedList 是非同步的。
底層原理:LinkedList底層的資料結構是基於雙向迴圈連結串列的,且頭結點中不存放資料
1.私有屬性:
private transient Entry<E> header = new Entry<E>(null, null, null); 

private transient int size = 0;

以下是節點類Entry的實現:

private static class Entry<E> {
	E element;
	Entry<E> next;
	Entry<E> previous;
	Entry(E element, Entry<E> next, Entry<E> previous) {
		this.element = element;
		this.next = next;
		this.previous = previous;
	}
}


next和previous分別表示該節點的下一個節點跟下一個節點。element是該節點包含的值
2.構造方法:
第一個無參構造方法:
public LinkedList() {
	header.next = header.previous = header;
}


第一個構造方法將header的next跟pervious都指向header,這就是一個雙向迴圈連結串列的初始化;整個連結串列就是隻有header一個結點,表現為空連結串列。
第二個接收一個Collection引數c:
public LinkedList(Collection<? extends E> c) {
	this();
	addAll(c);
}


先呼叫第一個構造方法,構造一個空的LinkedList,然後把c通過addAll方法加入進去。
3.增加元素
無論add的哪個實現都需要用到addBefore這個方法,這個方法是私有方法,無法直接從外部程式呼叫,若需要,只能通過反射。
private Entry<E> addBefore(E e, Entry<E> entry) {
	Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
	newEntry.previous.next = newEntry;
	newEntry.next.previous = newEntry;
	size++;
	modCount++;
	return newEntry;
}


// 將元素(E)新增到LinkedList中
public boolean add(E e) {
	// 將節點(節點資料是e)新增到表頭(header)之前。
	// 即,將節點新增到雙向連結串列的末端。
	addBefore(e, header);
	return true;
}


傳入的是結點資料e,呼叫addBefore,首先在addBefore()方法內建立一個新的節點newEntry,使newEntry的上一個結點是header.previous,也就是尾部節點,因為這是一個雙向迴圈連結串列,下一個節點是header,因為新加入的節點需要作為尾節點,作為雙向迴圈連結串列,尾節點的下一個指向header。因為是雙向的,所以需要讓周圍的節點指向newEntry,然後增加size;
以下的實際增加過程跟上述描述差不多,都是呼叫了addBefore()方法。
    
public void add(int index, E element) {
	addBefore(element, (index == size ? header : entry(index)));
}

public void addFirst(E e) {
	addBefore(e, header.next);
}

public void addLast(E e) {
	addBefore(e, header);
}


4.刪除資料:
public E remove(int index) {
	Entry e = get(index);
	remove(e);
	return e.element;
}


 呼叫了remove()方法,這個方法同樣是私有方法,這就是雙向連結串列刪除節點的實現。
    
private void remove(E e) {
	if (e == header)
		throw new NoSuchElementException();
	// 將前一節點的next引用賦值為e的下一節點
	e.previous.next = e.next;
	// 將e的下一節點的previous賦值為e的上一節點
	e.next.previous = e.previous;
	// 上面兩條語句的執行已經導致了無法在連結串列中訪問到e節點,而下面解除了e節點對前後節點的引用
	e.next = e.previous = null;
	// 將被移除的節點的內容設為null
	e.element = null;
	// 修改size大小
	size--;
}


5.clear元素:
public void clear() {
	Entry<E> e = header.next;
	// e可以理解為一個移動的“指標”,因為是迴圈連結串列,所以回到header的時候說明已經沒有節點了
	while (e != header) {
		// 保留e的下一個節點的引用
		Entry<E> next = e.next;
		// 解除節點e對前後節點的引用
		e.next = e.previous = null;
		// 將節點e的內容置空
		e.element = null;
		// 將e移動到下一個節點
		e = next;
	}
	// 將header構造成一個迴圈連結串列,同構造方法構造一個空的LinkedList
	header.next = header.previous = header;
	// 修改size
	size = 0;
	modCount++;
}



6.獲取資料:

public E get(int index) {
	return entry(index).element;
}

// 獲取雙向連結串列中指定位置的節點
private Entry<E> entry(int index) {
	if (index < 0 || index >= size)
		throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
				+ size);
	Entry<E> e = header;
	// 獲取index處的節點。
	// 若index < 雙向連結串列長度的1/2(位運算),則從前先後查詢;
	// 否則,從後向前查詢。
	if (index < (size >> 1)) {
		for (int i = 0; i <= index; i++)
			e = e.next;
	} else {
		for (int i = size; i > index; i--)
			e = e.previous;
	}
	return e;
}

7.查詢資料是否存在:
public boolean contains(Object o) {
	return indexOf(o) != -1;
}

// 從前向後查詢,返回“值為物件(o)的節點對應的索引” 不存在就返回-1
public int indexOf(Object o) {
	int index = 0;
	if (o == null) {
		for (Entry e = header.next; e != header; e = e.next) {
			if (e.element == null)
				return index;
			index++;
		}
	} else {
		for (Entry e = header.next; e != header; e = e.next) {
			if (o.equals(e.element))
				return index;
			index++;
		}
	}
	return -1;
}


8.資料複製:
public Object clone() {
	LinkedList<E> clone = null;
	try {
		clone = (LinkedList<E>) super.clone();
	} catch (CloneNotSupportedException e) {
		throw new InternalError();
	}
	clone.header = new Entry<E>(null, null, null);
	clone.header.next = clone.header.previous = clone.header;
	clone.size = 0;
	clone.modCount = 0;
	for (Entry<E> e = header.next; e != header; e = e.next)
		clone.add(e.element);
	return clone;
}


呼叫父類的clone()方法初始化物件連結串列clone,將clone構造成一個空的雙向迴圈連結串列,之後將header的下一個節點開始將逐個節點新增到clone中。最後返回克隆的clone物件。


public Object[] toArray() {
	Object[] result = new Object[size];
	int i = 0;
	for (Entry<E> e = header.next; e != header; e = e.next) {
		result[i++] = e.element;
	}
	return result;
}

public <T> T[] toArray(T[] a) {
	// 先判斷出入的陣列a的大小是否足夠,若大小不夠則拓展。
	// 這裡用到了發射的方法,重新例項化了一個大小為size的陣列。
	// 之後將陣列a賦值給陣列result,遍歷連結串列向result中新增的元素。
	// 最後判斷陣列a的長度是否大於size,若大於則將size位置的內容設定為null。返回a*/
	if (a.length < size) {
		a = (T[]) java.lang.reflect.Array.newInstance(a.getClass()
				.getComponentType(), size);
	}
	int i = 0;
	Object[] result = a;
	for (Entry<E> e = header.next; e != header; e = e.next) {
		result[i++] = e.element;
	}
	if (a.length > size) {
		a[size] = null;
	}
	return a;
}








相關推薦

深入理解Java集合List

List筆錄List相較於set、map,是按照一定順序儲存,List主要分為3類,ArrayList, LinkedList和Vector。以下是List的結構圖,本文章重點講解ArrayList與LinkedList的底層實現原理。ArrayListArrayList採用

深入理解Java集合Map

Map筆錄    Map 提供了一個更通用的元素儲存方法。 Map 集合類用於儲存元素對(稱作“鍵”和“值”),其中每個鍵對映到一個值。標準的Java類庫中包含了Map的幾種基本實現,包括HashMap、TreeMap、LinkedHashMap、WeakHashMap、Co

1.深入理解java集合List

下圖是java集合框架圖,Collection、Map是集合框架的頂級類,Iterator是集合迭代器。 Collection介紹(主要講解實現類,主要特點,適用場景,實現原理) 1、List介面,主要實現類Vector、ArrayList、LinkedList    

深入理解JAVA集合系列四:ArrayList源碼解讀

結束 了解 數組下標 size new 數組元素 開始 ini rem 在開始本章內容之前,這裏先簡單介紹下List的相關內容。 List的簡單介紹 有序的collection,用戶可以對列表中每個元素的插入位置進行精確的控制。用戶可以根據元素的整數索引(在列表中的位置)訪

深入理解JAVA集合系列三:HashMap的死循環解讀

現在 最新 star and 場景 所有 image cap 時也 由於在公司項目中偶爾會遇到HashMap死循環造成CPU100%,重啟後問題消失,隔一段時間又會反復出現。今天在這裏來仔細剖析下多線程情況下HashMap所帶來的問題: 1、多線程put操作後,get操作導

深入理解Java集合框架》系列文章

stack 數據結構 tro www. rpe ack 不能 一個 標準 https://www.cnblogs.com/CarpenterLee/p/5545987.html Introduction 關於C++標準模板庫(Standard Template Libr

深入理解Java集合框架】紅黑樹講解(上)

時間復雜度 row lee tel framework 關系 eight logs return 來源:史上最清晰的紅黑樹講解(上) - CarpenterLee 作者:CarpenterLee(轉載已獲得作者許可,如需轉載請與原作者聯系) 文中所有圖片點擊之後均可查看大

Java集合List常見操作

collect 定位 lastindex 索引 hot move 值對象 安排 ast 一.定義集合(Collection)是Java存儲對象常見的一種方式;集合中的各個對象最終存儲在類似數組[]中。那麽,為什麽有了數組還要用集合呢?因為,集合中的長度可變,所以不用一開始就

【學習筆記】 唐大仕—Java程式設計 第5講 深入理解Java語言5.2 多型及虛方法呼叫

/** * 多型及虛方法呼叫 * @author cnRicky * @date 2018.11.7 */ 多型 多型(Polymorphism)是指一個程式中相同的名字表示不同的含義的情況 多型有兩種情形 編譯時多型:  *過載(Overload)(多個同名的不同方法)  *如 p.sayH

【學習筆記】 唐大仕—Java程式設計 第5講 深入理解Java語言5.3 物件構造與初始化

物件構造與初始化 構造方法 構造方法(constructor) 物件都有構造方法 如果沒有,編譯器加一個default構造方法 抽象類(abstract)有沒有構造方法? 答案:抽象類也有構造方法。實際上,任何類都有自己的構造方法

【學習筆記】 唐大仕—Java程式設計 第5講 深入理解Java語言5.4 物件清除與垃圾回收

/** * 物件清除與垃圾回收 * @author cnRicky * @date 2018.11.10 */ 物件清除與垃圾回收 物件清除 我們知道:new建立物件 那麼如何銷燬物件? Java中是自動清除 不需要使用delete等方法人為銷燬它

【學習筆記】 唐大仕—Java程序設計 第5講 深入理解Java語言5.4 對象清除與垃圾回收

let 要求 什麽 jdk1 style 1.10 垃圾 ati 內存 /** * 對象清除與垃圾回收 * @author cnRicky * @date 2018.11.10 */ 對象清除與垃圾回收 對象清除 我們知道:new創建對象 那麽如何銷毀對象? Java

【學習筆記】 唐大仕—Java程式設計 第5講 深入理解Java語言5.5 內部類與匿名類

/** * 內部類與匿名類 * @author cnRicky * @date 2018.11.10 */ 內部類與匿名類 內部類(inner class)是在其他類中的類 匿名類(anonymous class)是一種特殊的內部類,它沒有類名 內部類(Inner class)

【學習筆記】 唐大仕—Java程式設計 第5講 深入理解Java語言5.7 其他幾個高階語法

/** * 其他幾個高階語法 學習筆記 * @author cnRicky * @date 2018.11.11 */   其他幾個高階語法 新的語法 從JDK1.5起,增加了一些新的語法 大部分是編譯器自動翻譯的,稱為Complier sugar("糖"用起來很方便,但

java集合List總結

主要驗證一下List集合的常用操作: list中新增,獲取,刪除元素; 新增方法是:.add(e);  獲取方法是:.get(index);  刪除方法是:.remove(index); 按照索引刪除;  .remove(Object o); 按照元素內容刪除;

深入理解java集合】-ArryList實現原理

一、ArrayList簡介 1、概述 ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。 ArrayList不是執行緒安全的,只能用在單執行緒環境下,多執行緒環境下可以考慮用Collections.

深入理解java集合】-LinkedList實現原理

一、LinkeddList簡介 1、LinkedList概述 LinkedList是一個一個繼承於AbstractSequentialList,並實現了List介面和Deque介面的雙端連結串列。 LinkedList底層的連結串列結構使它支援高效的插入和刪除操作,

深入理解Java併發synchronized實現原理

關聯文章: 本篇主要是對Java併發中synchronized關鍵字進行較為深入的探索,這些知識點結合博主對synchronized的個人理解以及相關的書籍的講解(在結尾參考資料),如有誤處,歡迎留言。 執行緒安全是併發程式

深入理解java集合】-TreeMap實現原理

一、紅黑樹介紹 1、R-B Tree概念 紅黑樹(Red Black Tree,簡稱R-B Tree) 是一種自平衡二叉查詢樹,它雖然是複雜的,但它的最壞情況執行時間也是非常良好的,並且在實踐中是高效的: 它可以在O(log n)時間內做查詢,插入和刪除,這裡的n 是

深入理解java集合的底層操作

集合:層次結構: (1)Collection (介面)其常用子介面 Set    List  Queue  Set 的子介面和其實現類如下 (一)EnumSet (抽象類)  implements  Set    (二)SortedSet(介面)     exntends