【数据结构】ArrayList原理及实现学习总结
一、ArrayList介紹
ArrayList是一種線性數據結構,它的底層是用數組實現的,相當于動態數組。與Java中的數組相比,它的容量能動態增長。類似于C語言中的動態申請內存,動態增長內存。?
當創建一個數組的時候,就必須確定它的大小,系統會在內存中開辟一塊連續的空間,用來保存數組,因此數組容量固定且無法動態改變。ArrayList在保留數組可以快速查找的優勢的基礎上,彌補了數組在創建后,要往數組添加元素的弊端。實現的基本方法如下:?
1. 快速查找:在物理內存上采用順序存儲結構,因此可根據索引快速的查找元素。?
2. 容量動態增長: 當數組容量不夠用時(表1),創建一個比原數組容量大的新數組(表2),將數組中的元素“搬”到新數組(表3),再將新的元素也放入新數組(表4),最后將新數組賦給原數組即可。(從左到右依次為表1,表2、表3、表4)
二、ArrayList繼承關系
ArrayList繼承于AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。?
實現了所有List接口的操作,并ArrayList允許存儲null值。除了沒有進行同步,ArrayList基本等同于Vector。在Vector中幾乎對所有的方法都進行了同步,但ArrayList僅對writeObject和readObject進行了同步,其它比如add(Object)、remove(int)等都沒有同步。
- 1
- 2
ArrayList與Collection關系如下圖,實線代表繼承,虛線代表實現接口:?
三、ArrayList的實現
對于ArrayList而言,它實現List接口、底層使用數組保存所有元素。其操作基本上是對數組的操作。下面進行具體的介紹:
1. 私有屬性
// 保存ArrayList中數據的數組 private transient Object[] elementData; // ArrayList中實際數據的數量 private int size;- 1
- 2
- 3
- 4
很容易理解,elementData存儲ArrayList內的元素,size表示它包含的元素的數量。?
有個關鍵字需要解釋:transient。?
Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。為了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。
2.構造函數
ArrayList提供了三種方式的構造器,可以構造一個指定初始容量的空列表、構造一個默認初始容量為10的空列表以及構造一個包含指定collection的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列的。
// ArrayList帶容量大小的構造函數。public ArrayList(int initialCapacity) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);// 新建一個數組this.elementData = new Object[initialCapacity];}// ArrayList構造函數。默認容量是10。public ArrayList() {this(10);}// 創建一個包含collection的ArrayListpublic ArrayList(Collection<? extends E> c) {elementData = c.toArray();size = elementData.length;// c.toArray might (incorrectly) not return Object[] (see 6260652)if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
3.元素存儲?
ArrayList是基于數組實現的,當添加元素的時候,如果數組大,則在將某個位置的值設置為指定元素即可,如果數組容量不夠了,以add(E e)為例,可以看到add(E e)中先調用了ensureCapacity(size+1)方法,之后將元素的索引賦給elementData[size],而后size自增。例如初次添加時,size為0,add將elementData[0]賦值為e,然后size設置為1(類似執行以下兩條語句elementData[0]=e;size=1)。將元素的索引賦給elementData[size]不是會出現數組越界的情況嗎?這里關鍵就在ensureCapacity(size+1)中了。?
具體實現如下:?
(1)?當調用下面這兩個方法向數組中添加元素時,默認是添加到數組中最后一個元素的后面。內存結構變化如下:?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
(2)當調用下面這兩個方法向數組中添加元素或集合時,會先查找索引位置,然后將元素添加到索引處,最后把添加前索引后面的元素追加到新元素的后面。?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
(3)調用該方法會將index位置的元素用新元素替代?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
4.元素讀取
// 返回此列表中指定位置上的元素。public E get(int index) {RangeCheck(index);return (E) elementData[index];}- 1
- 2
- 3
- 4
- 5
5.元素刪除?
ArrayList提供了根據下標或者指定對象兩種方式的刪除功能。如下:?
romove(int index),首先是檢查范圍,修改modCount,保留將要被移除的元素,將移除位置之后的元素向前挪動一個位置,將list末尾元素置空(null),返回被移除的元素。?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
6. 調整數組容量ensureCapacity?
(1)從上面介紹的向ArrayList中存儲元素的代碼中,我們看到,每當向數組中添加元素時,都要去檢查添加后元素的個數是否會超出當前數組的長度,如果超出,數組將會進行擴容,以滿足添加數據的需求。數組擴容通過一個公開的方法ensureCapacity(int minCapacity)來實現。在實際添加大量元素前,我也可以使用ensureCapacity來手動增加ArrayList實例的容量,以減少遞增式再分配的數量。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
從上述代碼中可以看出,數組進行擴容時,會將老數組中的元素重新拷貝一份到新的數組中,每次數組容量的增長大約是其原容量的1.5倍。這種操作的代價是很高的,因此在實際使用時,我們應該盡量避免數組容量的擴張。當我們可預知要保存的元素的多少時,要在構造ArrayList實例時,就指定其容量,以避免數組擴容的發生。或者根據實際需求,通過調用ensureCapacity方法來手動增加ArrayList實例的容量。?
(2)?ArrayList還給我們提供了將底層數組的容量調整為當前列表保存的實際元素的大小的功能。它可以通過trimToSize方法來實現。代碼如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
由于elementData的長度會被拓展,size標記的是其中包含的元素的個數。所以會出現size很小但elementData.length很大的情況,將出現空間的浪費。trimToSize將返回一個新的數組給elementData,元素內容保持不變,length和size相同,節省空間。?
7.轉為靜態數組toArray的兩種方法?
(1)調用Arrays.copyOf將返回一個數組,數組內容是size個elementData的元素,即拷貝elementData從0至size-1位置的元素到新數組并返回。
- 1
- 2
- 3
- 4
(2)如果傳入數組的長度小于size,返回一個新的數組,大小為size,類型與傳入數組相同。所傳入數組長度與size相等,則將elementData復制到傳入數組中并返回傳入的數組。若傳入數組長度大于size,除了復制elementData外,還將把返回數組的第size個元素置為空。
// 返回ArrayList的模板數組。所謂模板數組,即可以將T設為任意的數據類型public <T> T[] toArray(T[] a) {// 若數組a的大小 < ArrayList的元素個數;// 則新建一個T[]數組,數組大小是“ArrayList的元素個數”,并將“ArrayList”全部拷貝到新數組中if (a.length < size)return (T[]) Arrays.copyOf(elementData, size, a.getClass());// 若數組a的大小 >= ArrayList的元素個數;// 則將ArrayList的全部元素都拷貝到數組a中。System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
8.實現了Cloneable接口,進行數據淺拷貝
// 克隆函數public Object clone() {try {ArrayList<E> v = (ArrayList<E>) super.clone();// 將當前ArrayList的全部元素拷貝到v中v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError();}}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
9.實現Serializable 接口,啟用其序列化功能
// java.io.Serializable的寫入函數// 將ArrayList的“容量,所有的元素值”都寫入到輸出流中private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject();// 寫入“數組的容量”s.writeInt(elementData.length);// 寫入“數組的每一個元素”for (int i = 0; i < size; i++)s.writeObject(elementData[i]);if (modCount != expectedModCount) {throw new ConcurrentModificationException();}}// java.io.Serializable的讀取函數:根據寫入方式讀出// 先將ArrayList的“容量”讀出,然后將“所有的元素值”讀出private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {// Read in size, and any hidden stuffs.defaultReadObject();// 從輸入流中讀取ArrayList的“容量”int arrayLength = s.readInt();Object[] a = elementData = new Object[arrayLength];// 從輸入流中將“所有的元素值”讀出for (int i = 0; i < size; i++)a[i] = s.readObject();}總結
以上是生活随笔為你收集整理的【数据结构】ArrayList原理及实现学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL Server物化视图学习笔记
- 下一篇: Mac下Git安装及配置