「 深入淺出 」集合List
第一篇文章 「 深入淺出 」java集合Collection和Map 主要講了對集合的整體介紹,本篇文章主要講List相對於Collection新增的一些重要功能以及其重要子類ArrayList、LinkedList、Vector
一、List集合
關於List集合的介紹與方法,可參考第一篇文章 「 深入淺出 」java集合Collection和Map
迭代方法ListIterator
相對於其它集合,List集合添加了一種新的迭代方法ListIterator
ListIterator的方法如下:
ListIterator介面在Iterator介面基礎上增加了如下方法:
boolean hasPrevious()
E previous():返回迭代器的前一個元素。
void add(Object o):將指定的元素插入列表。
int nextIndex():下一個索引號
int previousIndex():上一個索引號
void set(E e):修改迭代器當前元素的值
void add(E e):在迭代器當前位置插入一個元素
ListIterator介面比Iterator介面多了兩個功能:
1.ListIterator可在遍歷過程中新增和修改
2.ListIterator可逆向遍歷
使用示例如下:
public class ListIteratorDemo { public static void main(String[] args) { // 建立列表 List<Integer> list = new ArrayList<Integer>(); // 向列表中增加10個元素 for (int i = 0; i < 10; i++) { list.add(i); } // 獲得ListIterator物件 ListIterator<Integer> it = list.listIterator(); // 正序遍歷修改與新增 while (it.hasNext()) { Integer i = it.next(); //修改元素值 it.set(i+1); if(i == 5 ){ //新增元素值 it.add(55); } //! it.set(i+1); // 注意:如果修改的程式碼在這個位置會報錯 //set操作不能放在add操作之後 // 這裡不做解析,欲知詳情,請看原始碼 } System.out.println("正向遍歷"); //正向遍歷 for(Integer i:list){ System.out.println(i+" "); } System.out.println("逆向遍歷"); //逆向遍歷 //經過上面迭代器it遍歷後,迭代器it已到達最後一個節點 while (it.hasPrevious()) { System.out.println(it.previous() + " "); } } }
二、ArrayList和Vector
ArrayList和Vector很相似,所以就一起介紹了
ArrayList和Vector類都是基於陣列實現的List類,所以ArrayList和Vector類封裝了一個動態的、允許再分配的Object[]陣列。ArrayList和Vector物件使用initalCapacity引數來設定該陣列的長度,當向ArrayList和Vector中新增元素超過了該陣列的長度時,它們的initalCapacity會自動增加。
下面我們通過閱讀JDK 1.8 ArrayList原始碼來了解ArrayList
無參建構函式
預設初始化為容量為10
/**
* Constructs an empty list with an initial capacity of ten。
意思是:構造一個空陣列,預設的容量為10
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
有參建構函式
建立指定容量的ArrayList
//動態Object陣列,用來儲存加入到ArrayList的元素
Object[] elementData;
//ArrayList的建構函式,傳入引數為陣列大小
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//建立一個對應大小的陣列物件
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//傳入數字為0,將elementData 指定為一個靜態型別的空陣列
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
add方法
執行add方法時,先確保容量足夠大,若容量不夠,則會進行擴容;
擴容大小為原來的1.5倍
(這個需要注意一下,面試經常考)
//新增元素e
public boolean add(E e) {
ensureCapacityInternal(size + 1);
//將對應索引下的元素賦值為e:
elementData[size++] = e;
return true;
}
//得到最小擴容量
private void ensureCapacityInternal(int minCapacity) {
//如果此時ArrayList是空陣列,則將最小擴容大小設定為10:
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//判斷是否需要擴容:
ensureExplicitCapacity(minCapacity);
}
//判斷是否需要擴容
private void ensureExplicitCapacity(int minCapacity) {
//運算元+1
modCount++;
//判斷最小擴容容量-陣列大小是否大於0:
if (minCapacity - elementData.length > 0)
//擴容:
grow(minCapacity);
}
//ArrayList動態擴容的核心方法:
private void grow(int minCapacity) {
//獲取現有陣列大小:
int oldCapacity = elementData.length;
//位運算,得到新的陣列容量大小,為原有的1.5倍:
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新擴容的大小依舊小於傳入的容量值,那麼將傳入的值設為新容器大小:
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容器大小,大於ArrayList最大長度:
if (newCapacity - MAX_ARRAY_SIZE > 0)
//計算出最大容量值:
newCapacity = hugeCapacity(minCapacity);
//陣列複製:
elementData = Arrays.copyOf(elementData, newCapacity);
}
//計算ArrayList最大容量:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
//如果新的容量大於MAX_ARRAY_SIZE
//將會呼叫hugeCapacity將int的最大值賦給newCapacity
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
remove方法
有以下兩種刪除方法:
-
remove(int index)是針對於索引來進行刪除,不需要去遍歷整個集合,效率更高;
-
remove(Object o)是針對於物件來進行刪除,需要遍歷整個集合進行equals()方法比對,所以效率較低;
不過,無論是哪種形式的刪除,最終都會呼叫System.arraycopy()方法進行陣列複製操作,等同於移動陣列位置,所以效率都會受到影響
//在ArrayList的移除index位置的元素
public E remove(int index) {
//檢查索引是否合法:不合法拋異常
rangeCheck(index);
//運算元+1:
modCount++;
//獲取當前索引的value:
E oldValue = elementData(index);
//獲取需要刪除元素 到最後一個元素的長度,也就是刪除元素後,後續元素移動的個數;
int numMoved = size - index - 1;
//如果移動元素個數大於0 ,也就是說刪除的不是最後一個元素:
if (numMoved > 0)
// 將elementData陣列index+1位置開始拷貝到elementData從index開始的空間
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//size減1,並將最後一個元素置為null
elementData[--size] = null;
//返回被刪除的元素:
return oldValue;
}
//在ArrayList的移除物件為O的元素,不返回被刪除的元素:
public boolean remove(Object o) {
//如果o==null,則遍歷集合,判斷哪個元素為null:
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//快速刪除,和前面的remove(index)一樣的邏輯
fastRemove(index);
return true;
}
} else {
//同理:
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//快速刪除:
private void fastRemove(int index) {
//運算元+1
modCount++;
//獲取需要刪除元素 到最後一個元素的長度,也就是刪除元素後,後續元素移動的個數;
int numMoved = size - index - 1;
//如果移動元素個數大於0 ,也就是說刪除的不是最後一個元素:
if (numMoved > 0)
// 將elementData陣列index+1位置開始拷貝到elementData從index開始的空間
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//size減1,並將最後一個元素置為null
elementData[--size] = null;
}
get方法
通過elementData()方法獲取對應索引元素,在返回時候進行型別轉換
//獲取index位置的元素
public E get(int index) {
//檢查index是否合法:
rangeCheck(index);
//獲取元素:
return elementData(index);
}
//獲取陣列index位置的元素:返回時型別轉換
E elementData(int index) {
return (E) elementData[index];
}
set方法
通過elementData獲取舊元素,再設定新元素值相應index位置,最後返回舊元素
//設定index位置的元素值了element,返回該位置的之前的值
public E set(int index, E element) {
//檢查index是否合法:判斷index是否大於size
rangeCheck(index);
//獲取該index原來的元素:
E oldValue = elementData(index);
//替換成新的元素:
elementData[index] = element;
//返回舊的元素:
return oldValue;
}
調整容量大小
ArrayList還提供了兩個額外的方法來調整其容量大小
-
void ensureCapacity(int minCapacity): 增加容量,以確保它至少能夠容納最小容量引數所指定的元素數。
-
void trimToSize():將容量調整為列表的當前大小。
Vector實現原理與ArrayList基本相同,可參考上述內容
ArrayList和Vector的主要區別
-
ArrayList是執行緒不安全的,Vector是執行緒安全的。
-
Vector的效能比ArrayList差。
LinkedList
LinkedList是基於雙向連結串列實現的,內部儲存主要是Node物件,該物件儲存著元素值外,還指向上一節點和下一節點。
注意,因為LinkedList是基於連結串列實現的,沒有容量的說法,所以更沒有擴容之說
集合基礎框架
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
//LinkedList的元素個數:
transient int size = 0;
//LinkedList的頭結點:Node內部類
transient java.util.LinkedList.Node<E> first;
//LinkedList尾結點:Node內部類
transient java.util.LinkedList.Node<E> last;
//空實現:頭尾結點均為null,連結串列不存在
public LinkedList() {
}
//呼叫新增方法:
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//節點的資料結構,包含前後節點的引用和當前節點
private static class Node<E> {
//結點元素:
E item;
//結點後指標
java.util.LinkedList.Node<E> next;
//結點前指標
java.util.LinkedList.Node<E> prev;
Node(java.util.LinkedList.Node<E> prev, E element, java.util.LinkedList.Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}
LinkedList的常用方法只做簡單介紹不貼原始碼,具體可自行看原始碼
add方法
LinkedList有兩種新增方法
-
add(E e)連結串列最後新增一個元素
-
add(int index, E element)指定位置下新增一個元素;
LinedList新增元素主要分為以下步驟:
1.將新增的元素轉換為LinkedList的Node物件節點;
2.增加該Node節點的前後引用,即該Node節點的prev、next屬性,讓其分別指上、下節點;
3.修改該Node節點的前後Node節點中pre/next屬性,使其指向該節點。
remove方法
LinkedList的刪除也提供了2種形式
-
remove(int index)直接通過索引刪除元素
-
remove(Object o)通過物件刪除元素,需要逐個遍歷LinkedList的元素,重複元素只刪除第一個:
刪除後,需要修改上節點的next指向當前下一節點,下節點的prev指向當前上一節點
set方法
set(int index, E element)方法通過node(index)獲取到相應的Node,再修改元素的值
get方法
這是我們最常用的方法,其中核心方法node(int index),需要從頭遍歷或從後遍歷找到相應Node節點
在通過node(int index)獲取到對應節點後,返回節點中的item屬性,該屬性就是我們所儲存的元素。
//獲取相應角標的元素:
public E get(int index) {
//檢查索引是否正確:
checkElementIndex(index);
//獲取索引所屬結點的 元素值:
return node(index).item;
}
//獲取對應角標所屬於的結點:
java.util.LinkedList.Node<E> node(int index) {
//位運算:如果位置索引小於列表長度的一半,則從頭開始遍歷;否則,從後開始遍歷;
if (index < (size >> 1)) {
java.util.LinkedList.Node<E> x = first;
//從頭結點開始遍歷:遍歷的長度就是index的長度,獲取對應的index的元素
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//從集合尾結點遍歷:
java.util.LinkedList.Node<E> x = last;
//同樣道理:
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
ArrayList和LinkedList的主要區別
-
ArrayList基於陣列實現的,LinkedList是基於雙向連結串列實現的
-
ArrayList隨機訪問效率高,隨機插入、隨機刪除效率低,需要移動元素位置
LinkedList隨機插入、隨機刪除效率高,隨機訪問效率低,因需要遍歷連結串列
好叻,搞完,溜了溜了
下一期為<集合Set>,敬請期待
近期推薦:
好人?壞人?做真實的人
「 優質資源 」收藏!最新精選優質資源!
java小心機(5)| 淺談類成員初始化順序
更多精彩內容,可閱讀原文
您的點贊、轉發是對我最大的支援!
THANDKS
- End -
一個立志成大腿而每天努力奮鬥的年輕人
伴學習伴成長,成長之路你並不孤單!