第一篇文章 「 深刻淺出 」java集合Collection和Map 主要講了對集合的總體介紹,本篇文章主要講List相對於Collection新增的一些重要功能以及其重要子類ArrayList、LinkedList、Vectorjava
關於List集合的介紹與方法,可參考第一篇文章 「 深刻淺出 」java集合Collection和Mapnode
相對於其它集合,List集合添加了一種新的迭代方法ListIterator
ListIterator的方法以下:面試
ListIterator接口在Iterator接口基礎上增長了以下方法:
boolean hasPrevious(): 若是以逆向遍歷列表。若是迭代器有上一個元素,則返回 true。
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類都是基於數組實現的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方法時,先確保容量足夠大,若容量不夠,則會進行擴容;擴容大小爲原來的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; }
有如下兩種刪除方法:
不過,不管是哪一種形式的刪除,最終都會調用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; }
經過elementData()方法獲取對應索引元素,在返回時候進行類型轉換
//獲取index位置的元素 public E get(int index) { //檢查index是否合法: rangeCheck(index); //獲取元素: return elementData(index); } //獲取數組index位置的元素:返回時類型轉換 E elementData(int index) { return (E) elementData[index]; }
經過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還提供了兩個額外的方法來調整其容量大小
Vector實現原理與ArrayList基本相同,可參考上述內容
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有兩種添加方法
LinedList添加元素主要分爲如下步驟:
1.將添加的元素轉換爲LinkedList的Node對象節點;
2.增長該Node節點的先後引用,即該Node節點的prev、next屬性,讓其分別指上、下節點;
3.修改該Node節點的先後Node節點中pre/next屬性,使其指向該節點。
LinkedList的刪除也提供了2種形式
刪除後,須要修改上節點的next指向當前下一節點,下節點的prev指向當前上一節點
set(int index, E element)方法經過node(index)獲取到相應的Node,再修改元素的值
這是咱們最經常使用的方法,其中核心方法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; } }
好叻,搞完,溜了溜了
下一期爲<集合Set>,敬請期待
近期推薦:
好人?壞人?作真實的人
「 優質資源 」收藏!最新精選優質資源!
java當心機(5)| 淺談類成員初始化順序
更多精彩內容,可閱讀原文
您的點贊、轉發是對我最大的支持!
THANDKS
一個立志成大腿而天天努力奮鬥的年輕人
伴學習伴成長,成長之路你並不孤單!