摘要:將之前第位置的元素置空,返回被刪除的元素。平常常用的迭代器方法就是判斷當前索引是否等于。最重要的是會更新,此時調用了父類的方法,會使,所以更新了,讓后續的檢查不會拋異常。
本篇主要介紹ArrayList的用法和源碼分析,基于jdk1.8,先從List接口開始。
ListList接口定義了如下方法:
int size(); boolean isEmpty(); boolean contains(Object o); Iteratoriterator(); Object[] toArray(); T[] toArray(T[] a); boolean add(E e); void add(int index, E element); boolean addAll(Collection extends E> c); boolean addAll(int index, Collection extends E> c); boolean remove(Object o); E remove(int index); boolean removeAll(Collection> c); boolean retainAll(Collection> c); void clear(); E get(int index); E set(int index, E element); int indexOf(Object o); int lastIndexOf(Object o); List subList(int fromIndex, int toIndex); ListIterator listIterator(); ListIterator listIterator(int index);
乍一看,這么多方法。其實很多方法是同樣的功能,方法重載而已。
接下來逐個介紹下List定義的方法。
size 獲得List元素的個數
isEmpty 判斷List是否為空
contains/containsAll 判斷List中是否有該元素,或者有該集合中的所有元素
iterator 獲得迭代器對象用于迭代
toArray 將List轉換成數組
add/addAll 添加元素至List中,默認直接添加到最后,也可以選擇指定的位置,還可以添加整個集合
remove/removeAll 刪除元素,可以根據元素刪除,也可以根據索引刪除,還可以根據集合刪除
retainAll 取與目標集合的交集
clear 清空List的所有元素
get 根據索引獲得元素
set 覆蓋索引處的元素
indexOf 獲得該元素的索引
lastIndexOf 獲得該元素的索引(從后往前)
subList 獲得子List根據start 和 end
listIterator 獲得ListIterator對象
方法名言簡意賅,基本上都可以從方法名知道方法的目的。
接下來分析List的常用實現類:
ArrayList
LinkedList
Vector
本篇介紹ArrayList
ArrayList類圖在我的理解中,ArrayList是一個封裝的數組,提供了一些便利的方法供使用者使用,規避了使用原生數組的風險。
ArrayList的定義public class ArrayListextends AbstractList implements List , RandomAccess, Cloneable, java.io.Serializable
繼承了AbstractList即成為了List,就要實現定義的所有方法。
實現了RandomAccess接口 就是提供了隨機訪問能力,可以通過下標獲得指定元素
實現了Cloneable接口 代表是可克隆的,需要實現clone方法
實現了Serializable接口 代表ArrayList是可序列化的
下面介紹下ArrayList的主要方法
添加元素boolean add(E e)
先附上源碼
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
此方法的執行邏輯:
判斷數組長度是否夠,不夠則擴容。默認擴容1.5倍
elementData[size++] = e;將新元素添加進去。
private void ensureCapacityInternal(int minCapacity) { //是否是空List,是則使用初始容量擴容 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; //增加修改次數 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//擴容1.5倍,采用位運算 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);//最大容量就是Integer的最大值 // minCapacity is usually close to size, so this is a win: //采用Arrays的copyOf進行深拷貝,其中調用的本地方法System.arraycopy,此方法是在內存中操作因此速度會很快。 elementData = Arrays.copyOf(elementData, newCapacity); //至此擴容結束 }
void add(int index, E element)方法稍有不同
首先檢查index是否合法
擴容
將新元素插入指定位置
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //將index -> (size -1)的元素都往后移動一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
boolean addAll(Collection extends E> c)
boolean addAll(int index, Collection extends E> c)
這兩個方法和上面的add大同小異,第一步都是判斷容量,并擴容。
容量大小從1變為c的length,elementData[index] = element;賦值也變為數組拷貝,
直接上代碼,秒懂。
public boolean addAll(Collection extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } public boolean addAll(int index, Collection extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(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; }add總結
無論是哪種add,都需要先執行方法 ensureCapacityInternal(size + 1) 進行容量判斷及擴容,確保之后的操作不會產生數組越界。
建議在我們日常工作時,如果大概知道元素的數量,可以在初始化的時候指定大小,這樣可以減少擴容的次數,提升性能。
刪除元素E remove(int index)
檢查是否索引不正確。
計算移動的元素的個數,數組往前移動。
將之前第size位置的元素置空,返回被刪除的元素。
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
boolean remove(Object o)
區分要刪除的元素是null還是非null。
找到該元素的索引,執行上面方法邏輯的2、3步。
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { 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) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
這種方法依賴元素的equals方法,循環遍歷數組,因為ArrayList是允許null元素的,所以無論是使用if (o.equals(elementData[index])) 還是 if (elementData[index].equals(o)) 均可能產生空指針,所以多帶帶對null進行處理,邏輯都是一樣的。
奇怪的是這個fastRemove方法,原本以為會有些特殊處理,結果發現代碼和上面remove(int index)中的一模一樣,為什么上面的remove中不調用這個fastRemove呢?難道寫兩個remove方法的不是同一人?邏輯不影響,只是代碼冗余了一點。
boolean removeAll(Collection> c)
public boolean removeAll(Collection> c) { Objects.requireNonNull(c); return batchRemove(c, false); }
刪除存在于目標集合的方法,使用了batchRemove(Collection> c, boolean complement)這個方法,這個方法作了封裝,在retainAll(Collection> c)取并集這個方法也有使用。
retainAll中是這樣使用的:
return batchRemove(c, true);
和我們的removeAll只差第二個參數boolean complement,remove是false,retail是true,那到底是什么意思呢?complement的原意的補充,在這里我理解為保留,remove就不保留,retail就保留,接著我們分析batchRemove這個方法。
private boolean batchRemove(Collection> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
定義了 r 和 w兩個int,r用于遍歷原來的數組,w的意思是新的數組的size。
假如定義2個數組用于舉例,
數組1,五個元素 1,2,3,4,5
數組2,五個元素 3,4,6,7,8
數組1調用removeAll(數組2)
此時進入batchRemove,我們來一步一步走看r,w如何變化
此時complement是false,即數組2中沒有數組1中第r個元素才滿足if條件
r = 0, w = 0, elementData[r] = 1, c.contains(elementData[r]) = false.
執行elementData[w++] = elementData[r],
數組1:1,2,3,4,5
r = 1, w = 1, elementData[r] = 2, c.contains(elementData[r]) = false.
執行elementData[w++] = elementData[r],
數組1:1,2,3,4,5
r = 2, w = 2, elementData[r] = 3, c.contains(elementData[r]) = true.
此時if條件不滿足了,w不自增了,代表elementData[r]要不存在與新數組中,要被刪除,所以跳過了。
r = 3, w = 2, elementData[r] = 4, c.contains(elementData[r]) = true.
if條件依舊不滿足,w不自增,此元素也是要刪除。
r = 4, w = 2, elementData[r] = 5, c.contains(elementData[r]) = false.
執行elementData[w++] = elementData[r],
數組1:1,2,5,4,5
此時for循環結束,elementData數組目前是這樣的,接著執行finally中的代碼,
首先執行
// Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; }
我們的情況是 r=size,那什么時候r會不等于size呢,jdk中寫了注釋,就是在if判斷時,調用數組2的contains方法,可能會拋空指針等異常。這時數組還沒有遍歷完,那r肯定是小于size的。
那沒判斷的那些數據還要不要處理?保守起見jdk還是會將他保存在數組中,因為最終w是作為新的size,所以w加上了沒處理過的個數size - r。
接著執行下面一段
if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; }
這段代碼意圖很清晰,w因為是新的size值,所以將w及其之后的都置位null,增加修改次數,
給size賦予新值之后就結束了。
刪除元素盡量使用指定下標的方法,性能好。
每次刪除都會進行數組移動(除非刪除最后一位),如果頻繁刪除元素,請使用LinkedList
boolean contains(Object o)
contains 底層調用的是indexOf方法
public boolean contains(Object o) { return indexOf(o) >= 0; }
int indexOf(Object o)
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; }
就是循環遍歷,一個個比對,有則返回對應下標,無則返回-1
對應的lastIndexOf是從最后往前遍歷。
public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
E get(int index)
rangeCheck(index); return elementData(index);
先檢查index,再返回數組對應元素。
E set(int index, E element)
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
先檢查index,在覆蓋index的元素,返回舊元素。
void clear()
modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0;
增加修改次數,將所有元素置空,size設置為0,需要注意的是,數組的大小是沒有變化的。
int size()
size方法就是直接將size變量直接返回
public int size() { return size; }
boolean isEmpty()
判斷size是否等于0
public boolean isEmpty() { return size == 0; }迭代器
Iterator
public Iteratoriterator() { return new Itr(); }
如代碼所示,創建了一個Itr對象并返回,接下來看Itr類的定義
private class Itr implements Iterator{ int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
首先介紹下三個成員變量
cursor,代表下一個訪問元素的在elementData數組中的索引,初始值是0。
lastRet,代表上一個訪問元素在elementData數組中的索引,初始值是-1。
expectedModCount,預期的modcount的值,在Itr對象創建的時候從父類ArrayList的modcount獲得,用于判斷在使用該對象迭代時,有么有對數組進行修改,有則會拋出ConcurrentModificationException。
平常常用的迭代器方法
hasNext()
就是判斷當前索引是否等于size。
E next()
首先檢查list是否被修改過,expectedModCount是在創建對象時就獲得了,如果在之后對list進行了其他修改操作的話,modCount就會增加,就會拋出ConcurrentModificationException。
沒有異常就按下表返回坐標,cursor自增,lastRet也自增。
void remove()
首先判斷是否能刪除,若能則調用父類的remove方法,刪除元素,接著會更新cursor和lastRet。
最重要的是會更新expectedModCount,此時調用了父類的remove方法,會使modCount+1,所以更新了 expectedModCount,讓后續的檢查不會拋異常。
ListIterator
listIterator和iterator一樣,只不過listIterator有更多的方法,相當于iterator的加強版。
定義如下
private class ListItr extends Itr implements ListIterator{ ListItr(int index) { cursor = index; } public boolean hasPrevious() { return cursor != 0; } public E previous() { checkForComodification(); try { int i = cursor - 1; E previous = get(i); lastRet = cursor = i; return previous; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.set(lastRet, e); expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; AbstractList.this.add(i, e); lastRet = -1; cursor = i + 1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }
沒有什么需要注意的地方,基本的方法都從Iterator中繼承過來并添加一些方法。
List
這又是一個內部類
class SubListextends AbstractList { private final AbstractList l; private final int offset; private int size; SubList(AbstractList list, int fromIndex, int toIndex) { if (fromIndex < 0) throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); if (toIndex > list.size()) throw new IndexOutOfBoundsException("toIndex = " + toIndex); if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); l = list; offset = fromIndex; size = toIndex - fromIndex; this.modCount = l.modCount; } 。 。 。 }
持有了父類的引用,內部的所有方法都是通過父類的這個引用去完成的,
所以需要注意的是,subList不是返回一個新的List,還是原來的引用,所以改變subList的數據,原有的數據也會更改。
終于把基本的方法都介紹完了,從源碼的角度分析了所有的方法,感覺對ArrayList知根知底了,用它時肯定會更加得心應手了,看了源碼才有原來實現都這么簡單啊這樣的感覺,不過從中也學習到了大牛規范的代碼風格,良好的結構,可讀性很高。下篇分析List的另一個實現類,LinkedList。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67776.html
摘要:編程思想第版這本書要常讀,初學者可以快速概覽,中等程序員可以深入看看,老鳥還可以用之回顧的體系。以下視頻整理自慕課網工程師路徑相關免費課程。 我自己總結的Java學習的系統知識點以及面試問題,目前已經開源,會一直完善下去,歡迎建議和指導歡迎Star: https://github.com/Snailclimb/Java-Guide 筆者建議初學者學習Java的方式:看書+視頻+實踐(初...
摘要:建議先熟悉一遍修煉秘籍之命令篇,本秘籍食用更佳正文核心秘訣功法之究極總結操作次數操作行為操作范圍下面,我會將此秘訣親自傳授于你。 前言 少年,我看你骨骼精奇,是萬中無一的武學奇才,維護世界和平就靠你了,我這有本秘籍《Vim修煉秘籍》,見與你有緣,就十塊賣給你了! 如果你是一名 Vimer,那么恭喜你,你的 Vim 技能馬上要升級了 ?! 如果你之前不了解過 Vim ,那么也沒關系,本文...
摘要:介紹本篇博客將繼續上一篇博客爬蟲之使用的模塊爬取各國國旗的內容,將用來實現這個爬蟲,下載全世界國家的國旗圖片。 介紹 ??本篇博客將繼續上一篇博客:Python爬蟲之使用Fiddler+Postman+Python的requests模塊爬取各國國旗 的內容,將用Java來實現這個爬蟲,下載全世界國家的國旗圖片。項目不再過多介紹,具體可以參考上一篇博客。??我們將全世界國家的名稱放在一個...
摘要:是現在廣泛流行的代從開始學習系列之向提交代碼掘金讀完本文大概需要分鐘。為了進行高效的垃圾回收,虛擬機把堆內存劃分成新生代老年代和永久代中無永久代,使用實現三塊區域。 React Native 開源項目 - 仿美團客戶端 (Android、iOS 雙適配) - Android - 掘金推薦 React Native 學習好項目,仿照美團客戶端... 極簡 GitHub 上手教程 - 工具...
閱讀 1017·2023-04-25 22:27
閱讀 872·2021-11-22 14:56
閱讀 984·2021-11-11 16:54
閱讀 1678·2019-08-30 15:54
閱讀 3500·2019-08-30 13:20
閱讀 1213·2019-08-30 10:55
閱讀 2080·2019-08-26 13:34
閱讀 3281·2019-08-26 11:53