数据结构特性解析 (二) ArrayList
前言
ArrayList可能是Java中使用次數(shù)最多的數(shù)據(jù)結(jié)構(gòu)了,因此了解其特性比較重要
描述
ArrayList是一個(gè)數(shù)組隊(duì)列,相當(dāng)于動(dòng)態(tài)數(shù)組.與Java中的數(shù)組相比,它的容量能動(dòng)態(tài)增長.
并且ArrayList還有一些添加,遍歷和移除的操作
特點(diǎn)
1.ArrayList內(nèi)部實(shí)現(xiàn)是利用Java的數(shù)組
這是內(nèi)部存儲(chǔ)數(shù)據(jù)的Object數(shù)組
add方法的實(shí)現(xiàn)方式,根據(jù)源碼可以看到,先判斷了一下容量,然后在當(dāng)前已存放數(shù)據(jù)的size的位置設(shè)置為傳過來的數(shù)據(jù),并且size+1
public boolean add(E e) {ensureCapacityInternal(size + 1); // 檢查容量(擴(kuò)充機(jī)制)下面再說elementData[size++] = e;return true;}2.ArrayList的空參構(gòu)造默認(rèn)長度不是10,而是0
ArrayList一共有三個(gè)構(gòu)造方法
//1設(shè)置初始化容量 public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}//2默認(rèn)的無參構(gòu)造 public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}//3傳入一個(gè)Collection對(duì)象public ArrayList(Collection<? extends E> c) {elementData = c.toArray();if ((size = elementData.length) != 0) {// c.toArray might (incorrectly) not return Object[] (see 6260652)if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {// replace with empty array.this.elementData = EMPTY_ELEMENTDATA;}}首先第三個(gè)構(gòu)造不多說了,是把別的集合轉(zhuǎn)換為ArrayList內(nèi)部的數(shù)組,然后如果有長度在設(shè)置一下size,跟本條關(guān)系不大
然后第一個(gè)構(gòu)造,是指定默認(rèn)的數(shù)組長度,小于0拋異常,等于零則設(shè)置內(nèi)部數(shù)組為靜態(tài)的length為0的空數(shù)組
重點(diǎn)是第二個(gè)構(gòu)造,直接賦值成如下的數(shù)組,可以看到是一個(gè)空容量的數(shù)組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};而在第一次add的時(shí)候會(huì)進(jìn)行擴(kuò)容,代碼如下所示
private static final int DEFAULT_CAPACITY = 10;//默認(rèn)第一次擴(kuò)容的容量private void ensureCapacityInternal(int minCapacity) {//這里會(huì)傳入size+1,也就是1if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果是默認(rèn)的空數(shù)組minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);//檢查容量并擴(kuò)容}3.ArrayList的擴(kuò)容方式,是先創(chuàng)建一個(gè)更大的數(shù)組,然后把舊數(shù)組內(nèi)容拷貝過去,在把內(nèi)部數(shù)組設(shè)置為新的大數(shù)組
我們看其add方法實(shí)現(xiàn)
public boolean add(E e) {ensureCapacityInternal(size + 1); // 檢查自身的可用容量elementData[size++] = e;return true;}如果ensureCapacityInternal檢查到容量不夠,最終會(huì)調(diào)用如下代碼
private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;//記錄當(dāng)前的長度int newCapacity = oldCapacity + (oldCapacity >> 1);// >> 符號(hào)(右位移),相當(dāng)于除2,所以擴(kuò)容是1+0.5=1.5倍if (newCapacity - minCapacity < 0)//如果擴(kuò)容后,容量還小于指定容量,就直接設(shè)置為指定容量,比如使用空參構(gòu)造new,size為0,乘1.5還是0,就會(huì)在第一次擴(kuò)容的時(shí)候設(shè)置為10newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)//長度過長檢查,如果長度溢出了int的值,就會(huì)變成負(fù)數(shù)導(dǎo)致拋異常(但說實(shí)話,它這個(gè)判斷還是有可能會(huì)int溢出)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);//進(jìn)行數(shù)組拷貝工作,容量為新擴(kuò)容的大小}所以,如果事先知道元素的大小或大概的大小,可以在構(gòu)造中傳入,就能減少擴(kuò)容次數(shù),能有效節(jié)省時(shí)間和減少內(nèi)存消耗
4.ArrayList的indexOf實(shí)現(xiàn)是遍歷
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;}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;}可以看到,indexOf是從前向后遍歷,而lastIndexOf是從后向前遍歷,所以如果你大概知道想查找的元素在前或者后,使用對(duì)應(yīng)的方法,可以更節(jié)省時(shí)間
5.因?yàn)閮?nèi)部使用的是數(shù)組,所以其特性也和數(shù)組特性類似
ArrayList根據(jù)索引的方式去查找數(shù)據(jù),會(huì)比較快,不用進(jìn)行循環(huán)或多次尋址
而遍歷查詢則需要遍歷索引來循環(huán)從數(shù)組中取出
6.ArrayList是線程不安全的
內(nèi)部沒有使用鎖或同步機(jī)制,如果想要使用線程安全的ArrayList(類似特性),可以使用Collections.synchronizedList
7.ArrayList不支持forEach時(shí)修改數(shù)據(jù)
forEach其實(shí)是java的語法糖,內(nèi)部使用的是迭代器Iterator,通過hasNext()和next()方法,來遍歷數(shù)據(jù)
private class Itr implements Iterator<E> {ArrayList的內(nèi)部類迭代器protected int limit = ArrayList.this.size;int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public boolean hasNext() {return cursor < limit;}@SuppressWarnings("unchecked")public E next() {if (modCount != expectedModCount)throw new ConcurrentModificationException();int i = cursor;if (i >= limit)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}}而內(nèi)部有一個(gè)modCount變量,標(biāo)識(shí)的是自身修改的次數(shù),會(huì)在擴(kuò)容時(shí),remove時(shí)和clear時(shí)進(jìn)行+1操作,而在迭代器內(nèi)next()方法取數(shù)據(jù)的時(shí)候,會(huì)判斷modCount變量,看看到底修沒修改過原數(shù)據(jù),如果修改過就會(huì)拋異常(這是為了數(shù)據(jù)的準(zhǔn)確性)
如果想要避免上述問題,可以使用CopyOnWriteArrayList(線程安全,支持forEach時(shí)修改數(shù)據(jù))
8.ArrayList支持隨機(jī)讀取,并且隨機(jī)添加和刪除效率不高
因?yàn)锳rrayList的實(shí)現(xiàn)基于數(shù)組,所以根據(jù)索引來獲取是很快的
而添加到的時(shí)候,只有在添加在尾部時(shí)效率較好(且不觸發(fā)擴(kuò)容)
而隨機(jī)插入,則會(huì)引起插入索引后面的位置都會(huì)被復(fù)制并向后位移,所以效率不高
刪除也是,只有刪除尾部時(shí)效率較好
隨機(jī)刪除會(huì)引起刪除的索引后面的位置都會(huì)被復(fù)制并向前位移,所以效率也不高
下一篇:?數(shù)據(jù)結(jié)構(gòu)特性解析 (三) 鏈表
總結(jié)
以上是生活随笔為你收集整理的数据结构特性解析 (二) ArrayList的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手动实现kt(java)同步工作流和异步
- 下一篇: 模仿Retrofit封装一个使用更简单的