java基礎解析系列(十)---ArrayList和LinkedList源碼及使用分析
目錄
- java基礎解析系列(一)---String、StringBuffer、StringBuilder
- java基礎解析系列(二)---Integer緩存及裝箱拆箱
- java基礎解析系列(三)---HashMap原理
- java基礎解析系列(四)---LinkedHashMap的原理及LRU算法的實現
- java基礎解析系列(五)---HashMap並發下的問題以及HashTable和CurrentHashMap的區別
- java基礎解析系列(六)---注解原理及使用
- java基礎解析系列(七)---ThreadLocal原理分析
- java基礎解析系列(八)--fail-fast機制及CopyOnWriteArrayList的原理
- 這是我的博客目錄,歡迎閱讀
ArrayList
成員變量
- 數組元素
111 private transient Object[] elementData;
- 數組中元素的個數
118 private int size;
構造方法
- 初始化容量為10
Constructs an empty list with an initial capacity of ten.
137
138 public ArrayList() {
139 this(10);
140 }
- 可以設置初始容量
127 public ArrayList(int initialCapacity) {
128 super();
129 if (initialCapacity < 0)
130 throw new IllegalArgumentException("Illegal Capacity: "+
131 initialCapacity);
132 this.elementData = new Object[initialCapacity];
133 }
ensureCapacity方法
178 public void ensureCapacity(int minCapacity) {
179 modCount++;
180 int oldCapacity = elementData.length;
181 if (minCapacity > oldCapacity) {
182 Object oldData[] = elementData;
183 int newCapacity = (oldCapacity * 3)/2 + 1;
184 if (newCapacity < minCapacity)
185 newCapacity = minCapacity;
186 // minCapacity is usually close to size, so this is a win:
187 elementData = Arrays.copyOf(elementData, newCapacity);
188 }
189 }
- 181行,判斷minCapacoty是否大於elementData數組的長度
- 如果181結果為true,183行設置新的容量為舊的容量*3/2+1
- 187行進行擴容,創建一個新容量的數組,然后將舊的數組元素復制到新數組中
順序add方法
377 public boolean add(E e) {
378 ensureCapacity(size + 1); // Increments modCount!!
379 elementData[size++] = e;
380 return true;
381 }
- 378行執行ensureCapacity方法,看插入一個元素后是否需要擴容
- 379行,將待添加元素放置到下標size的位置,size+1
指定index的add方法
392 public void add(int index, E element) {
393 rangeCheckForAdd(index);
394
395 ensureCapacity(size+1); // Increments modCount!!
396 System.arraycopy(elementData, index, elementData, index + 1,
397 size - index);
398 elementData[index] = element;
399 size++;
400 }
- 393執行rangeCheckForAdd方法
577 private void rangeCheckForAdd(int index) {
578 if (index > size || index < 0)
579 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
580 }
- rangeCheckForAdd方法判斷index是否超出范圍或者小於0
- 395執行ensureCapacity方法看是否需要擴容
- 396行將index開始的元素全部往后移動一位
- 然后設置index位置的元素為插入元素
remove方法
411 public E remove(int index) {
412 rangeCheck(index);
413
414 modCount++;
415 E oldValue = elementData(index);
416
417 int numMoved = size - index - 1;
418 if (numMoved > 0)
419 System.arraycopy(elementData, index+1, elementData, index,
420 numMoved);
421 elementData[--size] = null; // Let gc do its work
422
423 return oldValue;
424 }
- 412行rangeCheck看傳入的index是否在范圍之內
569 private void rangeCheck(int index) {
570 if (index >= size)
571 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
572 }
- 415通過下標獲得該元素
- 419行進行數組的移動,elementData數組中位置在 index+1 到 index+numMoved-1 之間的組件被分別復制到elementData數組中的 index 到 index+numMoved-1 位置。
- 把index后面的元素向前移動一位后,將size-1的位置設置為null,方便gc
get方法
348 public E get(int index) {
349 rangeCheck(index);
350
351 return elementData(index);
352 }
- 349行查找index是否超出范圍
- 351直接通過坐標從數組中返回
LinkedList
成員變量
95 private transient Entry<E> header = new Entry<E>(null, null, null);
96 private transient int size = 0;
- 95行head為一個頭結點
- size為鏈表大小
構造方法
101 public LinkedList() {
102 header.next = header.previous = header;
103 }
- 102行將header的前節點和后節點設置為header本身,從這里也可以看出這是一個雙向鏈表
add方法
214 public boolean add(E e) {
215 addBefore(e, header);
216 return true;
217 }
- 215行執行addBefore方法
794 private Entry<E> addBefore(E e, Entry<E> entry) {
795 Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
796 newEntry.previous.next = newEntry;
797 newEntry.next.previous = newEntry;
798 size++;
799 modCount++;
800 return newEntry;
801 }
- 795行創建一個新的節點
- 796行和797行修改節點的指針,將新節點放入鏈表
get方法
331 public E get(int index) {
332 return entry(index).element;
333 }
- 執行entry方法
380 private Entry<E> entry(int index) {
381 if (index < 0 || index >= size)
382 throw new IndexOutOfBoundsException("Index: "+index+
383 ", Size: "+size);
384 Entry<E> e = header;
385 if (index < (size >> 1)) {
386 for (int i = 0; i <= index; i++)
387 e = e.next;
388 } else {
389 for (int i = size; i > index; i--)
390 e = e.previous;
391 }
392 return e;
393 }
- 從這個方法可以看出並不是就是從前往后一個一個尋找,而是先將size>>1也就是將size除以2,看此時要查看的下標是小於還是大於這個數,如果小於這個數,說明位於鏈表中點的前面,用next指針尋找,正向尋找,反之用previous指針來尋找,這樣可以減少遍歷的次數
- 不過不能像ArrayList通過index直接定位,還是要一個一個尋找
remove方法
232 public boolean remove(Object o) {
233 if (o==null) {
234 for (Entry<E> e = header.next; e != header; e = e.next) {
235 if (e.element==null) {
236 remove(e);
237 return true;
238 }
239 }
240 } else {
241 for (Entry<E> e = header.next; e != header; e = e.next) {
242 if (o.equals(e.element)) {
243 remove(e);
244 return true;
245 }
246 }
247 }
248 return false;
249 }
- 可以看到,從前往后遍歷,找到Object o后執行remove方法
803 private E remove(Entry<E> e) {
804 if (e == header)
805 throw new NoSuchElementException();
806
807 E result = e.element;
808 e.previous.next = e.next;
809 e.next.previous = e.previous;
810 e.next = e.previous = null;
811 e.element = null;
812 size--;
813 modCount++;
814 return result;
815 }
- 刪除這個節點后,重新調整鏈表
總結與對比
- 效率方面,這兩個集合對應數據結構中的兩個線性表,一個數組一個鏈表,數組可以通過下標可以快速定位元素,所以自然查找和修改效率高。鏈表不能快速定位元素,只能一個一個找,所以自然查找效率沒有數組快,而鏈表的優勢在於他能快速插入快速刪除因為只需修改一下節點的指針就可以,不需要像ArrayList移動元素。
- 容量方面,因為數組是有容量的,所以當容量不足的時候,需要擴容,擴容后就以為者需要進行一次復制,所以如果使用ArrayList的時候,要初始化一個合適的容量,避免擴容的開銷。而鏈表就沒有大小限制,插入一個元素,只要插入一個節點就行了
- 編程世界里面,同一個問題會有很多的方案,有優點也會一定會有缺點,只是在哪種場景下,優點大於缺點罷了
我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)
作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。