來源:期待l 發(fā)布時間:2019-02-23 17:11:10 閱讀量:1092
本文是根據(jù)源碼進(jìn)行學(xué)習(xí)的,如果我有什么理解不對的地方請多指正,謝謝您
上面基本就是List集合類的類圖關(guān)系了,圖中省略掉了比如Cloneable
等標(biāo)記接口,那么List分別具體的主要實(shí)現(xiàn)類有:ArrayList
,Vector
,LinkedList
,Stack
,那么這篇文章會對這四個實(shí)現(xiàn)類進(jìn)行介紹(由于篇幅原因,本文只說到了ArrayList
和LinkedList
)
這是最常用的List的實(shí)現(xiàn)類,那么這個類的存儲是由數(shù)組實(shí)現(xiàn)的,如果超過數(shù)組規(guī)定閥值,那么就會進(jìn)行自動擴(kuò)容,自動擴(kuò)容其實(shí)就是將數(shù)組數(shù)據(jù)復(fù)制到一個新的更大的數(shù)組中以達(dá)到擴(kuò)容的目的,我們來看一下ArrayList的部分屬性源碼
//默認(rèn)容量,將在添加第一個元素時擴(kuò)展為 DEFAULT_CAPACITYprivate static final int DEFAULT_CAPACITY = 10;//共享空數(shù)組實(shí)例private static final Object[] EMPTY_ELEMENTDATA = {};//這是保存數(shù)據(jù)的數(shù)組,非私有以簡化嵌套類訪問//arraylist 的容量是此array數(shù)組的長度//當(dāng)?shù)谝粋€元素被添加時,當(dāng)任何一個空ArrayList的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA都會被擴(kuò)展為DEFAULT_CAPACITYtransient Object[] elementData;//共享空數(shù)組實(shí)例,用于默認(rèn)大小的空實(shí)例//將其與 EMPTY_ELEMENTDATA 區(qū)分開來,以了解添加第一個元素時要增加多少private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};private int size; ...
ArrayList提供了三種構(gòu)造方法,如下
public ArrayList() {...}public ArrayList(int initialCapacity) {...}public ArrayList(Collection<? extends E> c) {...}
空參 : 我們從第一個ArrayList的空參的構(gòu)造方法介紹,下面是源碼的實(shí)現(xiàn)
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
怎么理解之前的代碼注釋當(dāng)?shù)谝粋€元素被添加時,任何一個空ArrayList的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA都會被擴(kuò)展為DEFAULT_CAPACITY
呢?ArrayList為空并且兩個屬性值相等的時候,說明是調(diào)用了無參構(gòu)造,在構(gòu)造執(zhí)行完的時候,并沒有被構(gòu)造為默認(rèn)大小,而是當(dāng)?shù)谝粋€元素添加時候,判斷的條件成立即兩屬性值相等,才會進(jìn)行擴(kuò)展為默認(rèn)大小
所以說當(dāng)我們構(gòu)建空參對象的時候,初始值數(shù)組長度是為0的,并沒有直接擴(kuò)充為長度為10的數(shù)組,代碼驗(yàn)證
public static void main(String[] args) throws Exception { ArrayList<String> arrayList = new ArrayList<>(); getSize(arrayList); arrayList.add("x"); getSize(arrayList); }private static void getSize(ArrayList<String> arrayList) throws Exception { Field elementData = arrayList.getClass().getDeclaredField("elementData"); elementData.setAccessible(true); Object[] o = (Object[]) elementData.get(arrayList); System.out.println(o.length); }//0 10//而當(dāng)我們以0為初始化長度參數(shù)創(chuàng)建ArrayList的時候,其實(shí)就是告訴他我一個都不存,所以他就創(chuàng)建了一個0長度的數(shù)組,而當(dāng)我們添加數(shù)據(jù)的時候,就會自動擴(kuò)容一個//驗(yàn)證 : 可以將初始化改成這樣 ArrayList<String> arrayList = new ArrayList<>(0);//然后輸出為 0 1//所以這也就是為什么要區(qū)分DEFAULTCAPACITY_EMPTY_ELEMENTDATA 與 EMPTY_ELEMENTDATA
從中我們可以看到,只是初始化空參的ArrayList的話,那么只是將一個空數(shù)組賦值給elementData屬性,那么EMPTY_ELEMENTDATA
也是空數(shù)組對象,他是用來干啥的呢?他只是用作是構(gòu)造有參空ArrayLIst的時候=0.而DEFAULTCAPACITY_EMPTY_ELEMENTDATA
才是我們構(gòu)造空參ArrayList時候使用的對象,即這樣的,從下面一個分析另一個構(gòu)造的時候就能看出來
//使用EMPTY_ELEMENTDATAList<String> arrayList = new ArrayList<>(0);//使用DEFAULTCAPACITY_EMPTY_ELEMENTDATAList<String> arrayList = new ArrayList<>();
我們還注意到DEFAULTCAPACITY_EMPTY_ELEMENTDATA
與EMPTY_ELEMENTDATA
都是以private static final
修飾,所以這兩個空數(shù)組是屬于類的,僅存在一份,說這個的意思就是,當(dāng)你創(chuàng)建兩個容量為0的ArrayList的時候,都會指向一個堆內(nèi)存中的對象,我們可以嘗試一下
public static void main(String[] args) throws Exception { ArrayList<String> list1 = new ArrayList<>(); Field elementData1 = list1.getClass().getDeclaredField("elementData"); elementData1.setAccessible(true); Object o1 = elementData1.get(list1); ArrayList<String> list2 = new ArrayList<>(); Field elementData2 = list2.getClass().getDeclaredField("elementData"); elementData2.setAccessible(true); Object o2 = elementData1.get(list2); System.out.println(o1 == o2); } //true
所以ArrayList其實(shí)已經(jīng)想好了為我們的空集合做一個緩存,而當(dāng)我們向空集合中添加數(shù)據(jù)的時候,elementData就會指向其他的對象,這個是add方法的源碼范圍,所以一會再說,到這第一個空參的構(gòu)造方法已經(jīng)介紹的差不多了,下面是有int類型參數(shù)的構(gòu)造方法的源碼實(shí)現(xiàn)
public ArrayList(int initialCapacity) { //不為0,按照指定長度創(chuàng)建數(shù)組 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //為0就直接指向創(chuàng)建好的數(shù)組 this.elementData = EMPTY_ELEMENTDATA; } else { //參數(shù)不合法 throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); } }
不用細(xì)說,這個是很容易的,也可以看出來為什么ArrayList要設(shè)計(jì)兩個空數(shù)組以備使用,這個構(gòu)造沒什么可說的,那么下面就是以集合為參數(shù)的創(chuàng)建方式的源碼
//將參數(shù)轉(zhuǎn)換為ArrayListpublic ArrayList(Collection<? extends E> c) { //因?yàn)镃ollection中就定義了toArray方法,所以他的實(shí)現(xiàn)類就都會實(shí)現(xiàn)自己的toArray,所以可以直接調(diào)用返回?cái)?shù)組而不會出錯 elementData = c.toArray(); //如果返回的數(shù)組的長度不是空的數(shù)組的話 if ((size = elementData.length) != 0) { //防范c.ToArray錯誤不返回Object[] if (elementData.getClass() != Object[].class) //那么就將elementData中的元素都轉(zhuǎn)換為Object類型 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { //到這就是空數(shù)組,所以直接引用創(chuàng)建好的空數(shù)組即可,還能節(jié)省空間 this.elementData = EMPTY_ELEMENTDATA; } }
到這ArrayList的創(chuàng)建方式大概的就過了一邊,那么下面的ArrayList的實(shí)現(xiàn)方法,我就只挑幾個核心方法來看一下源碼
首當(dāng)其沖的就是add,我們使用一下ArrayList來add元素,然后我們進(jìn)行代碼走讀,那么我們看一下源碼
//使用ArrayList<String> arrayList = new ArrayList<>(); arrayList.add("A");//源碼開始public boolean add(E e) { modCount++; //代表操作ArrayList的次數(shù),有關(guān)于fast-fail機(jī)制,之后再說 //參數(shù)值:并不是A參數(shù)是一個URL, 數(shù)組對象 , 2 add(e, elementData, size); //調(diào)用類中方法 return true; //返回結(jié)果} // 結(jié)束代碼
上面可以說是很簡單了,但是在debug的時候我發(fā)現(xiàn)存儲元素的elementData數(shù)組中其實(shí)已經(jīng)有東西了,如下圖
然后當(dāng)我debug step over到結(jié)束代碼的時候,程序跳到這這樣一個代碼
public synchronized void addURL(URL url) { if (closed || url == null) return; synchronized (unopenedUrls) { if (! path.contains(url)) { unopenedUrls.addLast(url); path.add(url); } } }//再跳public InternalError(String message) { super(message); }//還跳static { System.loadLibrary("instrument"); }//還有很多...
上面的一些代碼其實(shí)不用知道其意思,但是可以告訴我們的是,ArrayList中的elementData不單純是存儲我們需要存儲的元素的,而是在首次add的時候會借助elementData這個數(shù)組去加載一些文件或者其他東西,而在第二次add的時候就不需要這個步驟了,并且在首次加載完一些路徑后或者庫后,elementData就會將他們清除,以為已經(jīng)加載上了,然后這時候才會來存儲我們的元素,錄了一段小視頻,可以下載來看一下
經(jīng)過后面再次實(shí)驗(yàn), 除了首次使用ArrayList的add方法會加載一些東西,當(dāng)我們再次new出ArrayList的時候,首次使用add方法就不會再家在任何東西了,如下的測試代碼
List<String> list = new ArrayList<>(); list.add("X"); List<String> list2 = new ArrayList<>(); list2.add("X");
其中在我debug時候會看到很多getLoader
`loders一些方法或者屬性,那么在這感覺是首次使用ArrayList的類加載機(jī)制在起作用,我在社區(qū)提問題了,所以大家可以看一下,聯(lián)想到類加載機(jī)制非常感謝回復(fù)我的
1382148494135822`大哥, 問答頁 : 關(guān)于ArrayList的問題,請大佬進(jìn)來看看
回到add方法上來,我們發(fā)現(xiàn)它會調(diào)用類中的另一個add方法.源碼如下
private void add(E e, Object[] elementData, int s) { //如果滿了就擴(kuò)容 if (s == elementData.length) elementData = grow(); //然后將要存的元素存入到數(shù)組指定位置 elementData[s] = e; //加一 size = s + 1; }
這個方法中用到的grow方法源碼如下
private Object[] grow() { return grow(size + 1); }private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); }//返回至少與給定最小容量相同的容量private int newCapacity(int minCapacity) { int oldCapacity = elementData.length; //10+(10>>1)=15 ,即擴(kuò)容1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //擴(kuò)容后如果還比最小的容量小或者相等的話 if (newCapacity - minCapacity <= 0) { //判斷是否是初始化的情況 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) //是的話就直接返回默認(rèn)容量10,從這就可以看出來才剛開始初始化大小 return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow錯誤,最小值不可能是負(fù)數(shù) throw new OutOfMemoryError(); //如果不是初始化也不是參數(shù)錯誤,那么就返回最小的容量 return minCapacity; } //到這表示擴(kuò)容后的容量比最小容量要大 //是否達(dá)到了最大長度,如果沒到達(dá)就返回?cái)U(kuò)容后的長度,否則就調(diào)用hugeCapacity return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); }private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow錯誤 throw new OutOfMemoryError(); //如果達(dá)到了規(guī)定的最大數(shù)組長度,那么就擴(kuò)容到整數(shù)的最大長度,否則就是當(dāng)前默認(rèn)的數(shù)組最大長度 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
還有一個指定位置的add方法,下面是源碼實(shí)現(xiàn)
public void add(int index, E element) { //檢查是否數(shù)組越界 rangeCheckForAdd(index); modCount++; final int s; //臨時保存size Object[] elementData; //臨時保存elementData if ((s = size) == (elementData = this.elementData).length) elementData = grow(); //如果長度等于size代表要擴(kuò)容了 //核心方法System.arraycopy,他會將你要操作的index地方的位置空出了 System.arraycopy(elementData, index, elementData, index + 1, s - index); //然后index空出來的位置賦值 elementData[index] = element; //加一 size = s + 1; }
好了ArrayList的add方法已經(jīng)介紹完了,如果有不對的地方請指正
源碼實(shí)現(xiàn)
public boolean addAll(Collection<? extends E> c) { //轉(zhuǎn)換數(shù)組 Object[] a = c.toArray(); modCount++; //獲取傳入集合的長度 int numNew = a.length; //是空集合的話直接返回 if (numNew == 0) return false; Object[] elementData; final int s; //如果傳入集合的長度大于elementData中空余的位置個數(shù)就增長 if (numNew > (elementData = this.elementData).length - (s = size)) elementData = grow(s + numNew); //增長完了就將傳入的數(shù)據(jù)copy進(jìn)去 System.arraycopy(a, 0, elementData, s, numNew); //元素個數(shù)修改 size = s + numNew; //返回成功 return true; }
源碼實(shí)現(xiàn)
public E get(int index) { //檢查index Objects.checkIndex(index, size); //然后調(diào)用方法 return elementData(index); }E elementData(int index) { //直接下標(biāo)取元素 return (E) elementData[index]; }
很簡單就不說了
源碼實(shí)現(xiàn)按照索引刪除
public E remove(int index) { //檢查index Objects.checkIndex(index, size); final Object[] es = elementData; //用于返回值 E oldValue = (E) es[index]; fastRemove(es, index); return oldValue; }private void fastRemove(Object[] es, int i) { modCount++; final int newSize; //去掉一個元素后的長度如果大于指定刪除的索引的位置 if ((newSize = size - 1) > i) //把刪除元素后面的元素往前挪一位 System.arraycopy(es, i + 1, es, i, newSize - i); //避免內(nèi)存泄露 es[size = newSize] = null; }
源碼實(shí)現(xiàn)按照對象刪除
public boolean remove(Object o) { final Object[] es = elementData; final int size = this.size; int i = 0; //元素首次出現(xiàn)位置記錄 //尋找元素的邏輯 found: { if (o == null) { for (; i < size; i++) if (es[i] == null) break found; } else { for (; i < size; i++) if (o.equals(es[i])) break found; } //到這代表沒找到.返回false return false; } //找到了就按照下標(biāo)刪除 fastRemove(e s, i); return true; }
源碼實(shí)現(xiàn)
public int indexOf(Object o) { return indexOfRange(o, 0, size); }int indexOfRange(Object o, int start, int end) { Object[] es = elementData; if (o == null) { for (int i = start; i < end; i++) { if (es[i] == null) { //尋找到首個想等元素的時候返回索引 return i; } } } else { for (int i = start; i < end; i++) { if (o.equals(es[i])) { //尋找到首個想等元素的時候返回索引 return i; } } } //未找到返回-1 return -1; }
這個實(shí)現(xiàn)比較簡單,并且ArrayList的一些*indexOf*
類似的方法的大致思路都是如此的
這是序列化的方法
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { //將操作次數(shù)記錄 int expectedModCount = modCount; //將類中的no-static和no-transient屬性寫到流中 s.defaultWriteObject(); s.writeInt(size); //依次寫出元素 for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } //如果再次過程中數(shù)組中內(nèi)容遭到改變,序列化停止并且拋出異常 if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
這是反序列化的方法
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // 讀取大小和其他的內(nèi)容 s.defaultReadObject(); //讀取容量 s.readInt(); //如果讀取到size>0 if (size > 0) { // 與clone()一樣,根據(jù)大小而非容量分配數(shù)組 SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size); //創(chuàng)建存儲數(shù)組 Object[] elements = new Object[size]; //依次將數(shù)據(jù)讀出來并賦值 for (int i = 0; i < size; i++) { elements[i] = s.readObject(); } //然后賦值給本類的全局變量 elementData = elements; } else if (size == 0) { //size為0代表空集合,那么就直接使用常量替換 elementData = EMPTY_ELEMENTDATA; } else { //非法的,出異常 throw new java.io.InvalidObjectException("Invalid size: " + size); } }
好了到這我自己認(rèn)為比較重要的方法源碼就都列出來的,所以將ArrayList的介紹就告一段落了,那么到這我就只有一個疑問就是關(guān)于elementData在首次add時加載jar或者路徑是做什么用的,如果您知道,請?jiān)u論在下面,非常謝謝您
總結(jié)一下ArrayList,我們在分析源碼的時候,除了在首次add時會添加路徑之類的信息會設(shè)計(jì)到其他類的同步加載方法,但是本類的方法并沒有涉及同步,所以ArrayList并不是線程安全的,并且他是懶加載的,首次默認(rèn)初始化并不會就初始化為初始化容量,而是在之后才會初始化為初始化容量,這個類的核心就是System.arraycopy
方法,所以以后我們在操作數(shù)組移動的時候,我們也可以使用這個native方法使得程序更加的快速準(zhǔn)確,在add和get的時候是相當(dāng)迅速而直接的,就是將制定位置元素后移并在此位置上插入元素,所以ArrayList的增加和查詢是很迅速的,但是我們也需要注意,當(dāng)ArrayList的元素滿的時候他是創(chuàng)建新數(shù)組進(jìn)行copy的,所以當(dāng)ArrayList的元素相當(dāng)大的時候,這無疑是一個恐怖的事情,所以ArrayList并不適合存儲很多元素
可以先參考一下這篇關(guān)于鏈表的文章,如果你有了解一點(diǎn)就可以不用看了 : 鏈表
這是一個鏈表實(shí)現(xiàn)的List的實(shí)現(xiàn)類,對于ArrayList這個類要并不存在擴(kuò)容copy的問題,如果你了解一點(diǎn)鏈表內(nèi)容就會知道,增加元素在鏈表中無非就是改變引用的事情,所以它并沒有這樣的問題,好了讓我們直接上源碼吧
依舊先看一下他的成員屬性
//元素個數(shù) transient int size = 0; //頭節(jié)點(diǎn) transient Node<E> first; //尾節(jié)點(diǎn) transient Node<E> last;
transient代表不會被序列化,那么Node是啥東西,看一下源碼
private static class Node<E> { E item; //本元素值 Node<E> next; //前元素指向 Node<E> prev; //后元素指向 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
從中可以LinkedList實(shí)現(xiàn)是一個雙鏈表,我們接下來看看他的初始化方法的實(shí)現(xiàn)
public LinkedList() { }
空參構(gòu)造什么都沒做,接下來看其他的初始化方法
public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
那么這個有參的構(gòu)造方法其實(shí)主要邏輯是addAll方法,我暫時先不說這個方法,那么我們先來看看其他的核心實(shí)現(xiàn)方法
源碼如下
public boolean add(E e) { linkLast(e); //添加到鏈表最后面 return true; }void linkLast(E e) { //將最后一個元素引用保留 final Node<E> l = last; //創(chuàng)建一個新的元素根據(jù)傳入的add參數(shù),新的對象的前一個元素的引用 //就是之前的最后一個元素 final Node<E> newNode = new Node<>(l, e, null); //將最后的元素指針指向新元素 last = newNode; //如果之前的尾元素是空的代表是空鏈表, if (l == null) //即首尾都是此元素 first = newNode; else //就不是空鏈表,那么倒數(shù)第二個的元素的下一個元素就是新元素 l.next = newNode; size++; modCount++; }
還有一種是根據(jù)index位置插入的
public void add(int index, E element) { //是否越界 checkPositionIndex(index); //如果index等于元素個數(shù) if (index == size) //那么就添加到尾部 linkLast(element); else //否則就按位置添加 //node方法,傳入index,返回指定元素索引處的(非空)節(jié)點(diǎn) linkBefore(element, node(index)); }void linkBefore(E e, Node<E> succ) { //已經(jīng)確保了succ不為空,在上面node方法中確保的 //取出指定index索引上的元素的前一個元素引用 final Node<E> pred = succ.prev; //創(chuàng)建新的元素,新元素的前一個元素就是目前指定index上的元素的前一個元素 //下一個元素是index上面的元素 final Node<E> newNode = new Node<>(pred, e, succ); //將指定索引位置的原元素的前指針指向新元素 succ.prev = newNode; //如果是在頭部添加,那么當(dāng)前元素的前一個元素肯定為空 if (pred == null) //然后新元素就成為了頭元素 first = newNode; else //否則就將index-1位置的元素的后指針指向新元素 pred.next = newNode; size++; modCount++; }
如果你熟悉鏈表,那么上面的代碼就很簡單就能理解了,如果看不懂,可以自己畫一下圖,瞬間明白
這也是構(gòu)造中調(diào)用的方法,我們來看一下實(shí)現(xiàn)
public boolean addAll(Collection<? extends E> c) { return addAll(size, c); }public boolean addAll(int index, Collection<? extends E> c) { //檢查位置是否越界 checkPositionIndex(index); //集合轉(zhuǎn)數(shù)組 Object[] a = c.toArray(); //確定集合元素個數(shù) int numNew = a.length; //如果是空集合返回 if (numNew == 0) return false; //記錄前指向和當(dāng)前節(jié)點(diǎn) Node<E> pred, succ; //如果相等代表在最后追加 if (index == size) { //在最后追加,就不需要當(dāng)前元素了,因?yàn)閘ast已經(jīng)指向了 succ = null; //添加集合的時候的,集合中首個被添加的元素的前一個節(jié)點(diǎn)就是當(dāng)前鏈表中的最后一個節(jié)點(diǎn) pred = last; } else { //到這就代表不是在尾部追加,而是將元素追加到鏈表中間位置 //node方法之前說過就是根據(jù)index來返回index位置上的節(jié)點(diǎn) //node返回節(jié)點(diǎn)后保存引用 succ = node(index); //記錄當(dāng)前index節(jié)點(diǎn)的前節(jié)點(diǎn)的引用 pred = succ.prev; //到這就記錄好了當(dāng)前節(jié)點(diǎn)和前節(jié)點(diǎn)的引用 } //開始循環(huán)創(chuàng)建集合中的元素的節(jié)點(diǎn)了,然后修改相關(guān)指向 for (Object o : a) { //將集合元素強(qiáng)轉(zhuǎn)一下,泛型約束不會classcast E e = (E) o; //創(chuàng)建節(jié)點(diǎn),此節(jié)點(diǎn)的前一個元素指向已經(jīng)確定 Node<E> newNode = new Node<>(pred, e, null); //代表從頭開始添加 if (pred == null) //新節(jié)點(diǎn)就是first節(jié)點(diǎn) first = newNode; else //新節(jié)點(diǎn)前指向是pred,perd.next指向新元素,所以到這形成了前元素和新元素的雙向鏈接 pred.next = newNode; //到這就連接好了舊節(jié)點(diǎn)和新節(jié)點(diǎn),需要移動pred指向,指向新節(jié)點(diǎn) //然后將新節(jié)點(diǎn)當(dāng)做舊節(jié)點(diǎn),然后在于新創(chuàng)建的節(jié)點(diǎn)做雙向鏈接 pred = newNode; } //到這就把鏈表從頭到集合所有元素都連接完成了,需要處理集合的尾節(jié)點(diǎn)和鏈表的原index節(jié)點(diǎn)的鏈接問題了 //如果原來的尾index節(jié)點(diǎn)沒有 if (succ == null) { //那么last就指向集合的尾節(jié)點(diǎn) last = pred; } else { //代表之前的鏈表有index節(jié)點(diǎn),那么就修改index節(jié)點(diǎn)和集合的尾節(jié)點(diǎn)的鏈接 pred.next = succ; succ.prev = pred; } //做一些計(jì)數(shù)操作 size += numNew; modCount++; return true; }
到這其實(shí)add方法基本就解析完了,那么順便前面的構(gòu)造也就沒有問題了,下面是其他方法的源代碼
remove()
public E remove() { return removeFirst(); }//可以看到默認(rèn)從頭開始刪除public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); }private E unlinkFirst(Node<E> f) { // assert f == first && f != null; //取出頭元素屬性值 final E element = f.item; //取出頭元素的下一個元素的引用 final Node<E> next = f.next; //將頭元素的屬性值置空 f.item = null; f.next = null; // help GC //然后將first指針指向下一個元素 first = next; //如果存在就只有一個元素的情況 if (next == null) //first和last都是null last = null; else //否則就將原來頭節(jié)點(diǎn)的下一個節(jié)點(diǎn)的前引用取消,因?yàn)椴淮嬖诹?自己已經(jīng)變成了頭結(jié)點(diǎn) next.prev = null; size--; modCount++; return element; }
remove(Object)
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; } //整體邏輯就是:已經(jīng)獲得了一個node,那么node的前后引用關(guān)系就找到了,然后剩下的就是改引用關(guān)系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; //如果前引用是null,就代表是頭元素 if (prev == null) { //頭指針,指向下一個元素 first = next; } else { //那么前引用就不是空的 //就將此元素的前元素的后指針指向此元素的后一個元素 prev.next = next; //此節(jié)點(diǎn)的前置引用可以取消了 x.prev = null; } //如果后置引用為空 if (next == null) { //代表是尾節(jié)點(diǎn),尾指針,指向前一個 last = prev; } else { //代表不是尾節(jié)點(diǎn),就將次元素的后一個元素的前引用指向次元素的前一個元素 next.prev = prev; //次元素的后置引用可以取消了 x.next = null; } //到這x節(jié)點(diǎn)已經(jīng)完全脫離鏈表,置空 x.item = null; size--; modCount++; return element; }
removeFirst()
public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); //方法實(shí)現(xiàn)上面已經(jīng)寫出了}
removeLast()
public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); // 跟之前的unlinkFirst類似就不再詳細(xì)說了}
remove(int)
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); //實(shí)現(xiàn)有說明在前面}
removeFirstOccurrence&removeLastOccurrence
其源碼就不去實(shí)現(xiàn)了,這個方法的作用是在于:如果指定的刪除元素在鏈表中就刪除,(區(qū)別:刪除最開始出現(xiàn)的&刪除最后一個出現(xiàn)的),如果不存在鏈表不變
getFirst() & getLast()
,實(shí)現(xiàn)比較簡單就不注釋了
public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; }public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; }
get(int)
,實(shí)現(xiàn)比較簡單就不注釋了
public E get(int index) { checkElementIndex(index); return node(index).item; }
set(int,E)
public E set(int index, E element) { //檢查索引 checkElementIndex(index); //利用node方法取出index上的節(jié)點(diǎn) Node<E> x = node(index); //保存作為返回 E oldVal = x.item; //替換 x.item = element; return oldVal; }
對于LinkedList還支持隊(duì)列操作,其實(shí)現(xiàn)也是比較簡單的,都是依靠于之前介紹的add方法和remove方法(unlink),所以不打算貼出源碼了,所支持的操作有類似peek
,poll
,push
,pop
等等,只了列舉部分
至于LinkedList的序列化機(jī)制也是類似ArrayList的序列化方式和步驟,都是先將類中no-static和no-transient屬性寫到流中,然后寫size,然后依次寫元素,反序列化即相同步驟即可
總結(jié):我們可以看到LinkedList是雙向鏈表的實(shí)現(xiàn),并沒有首尾相連,所以也不是環(huán)形鏈表,并且其中不存在初始化容量概念,并且不存在ArrayList中的容量限制常量,所以說這個類可以做到理論上的無限大,并且從中沒發(fā)現(xiàn)同步代碼塊,所以這個類也不是同步的,需要我們在使用的時候注意使用場景,對于其他的操作就是常規(guī)的鏈表操作