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