今天回頭研究一下 java 中的列表 ArrayList
,它表示用數(shù)組來存儲(chǔ)一系列的對(duì)象,并且支持 ?List
的特性.
我們今天研究一下它的基礎(chǔ)屬性, 添加方法, 以及與 lambda 相關(guān)的部分.
屬性
默認(rèn)容量
表示新建一個(gè) ArrayList
的初始化空間.
private static final int DEFAULT_CAPACITY = 10;
空列表
提供兩個(gè)空的數(shù)組列表,供代碼中使用以便于給參數(shù)賦值.
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
存儲(chǔ)數(shù)組
在數(shù)組泛型中存儲(chǔ)的數(shù)據(jù),用數(shù)組來實(shí)現(xiàn).
transient Object[] elementData; // non-private to simplify nested class access
列表大小
size
表示列表所包含的元素個(gè)數(shù),默認(rèn)是0
private int size;
擴(kuò)容
其實(shí)如果提到了 ArrayList
我們很多時(shí)候都會(huì)最先提到 ArrayList
的擴(kuò)容方法,那么我們這邊就先研究一下有關(guān)擴(kuò)容方法的一串方法鏈
ArrayList
有一個(gè)名為 grow
的匿名方法,顧名思義,就是擴(kuò)容,讓我們來一起看一下(核心):
private void grow(int minCapacity) {
//我們?nèi)〉搅嗽械娜萘? int oldCapacity = elementData.length;
//新的容量暫時(shí)等于原有的容量的 大約 一點(diǎn)五 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//取 新容量 和 傳入的最小容量 中的較小者(為了更小的內(nèi)存占用)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//MAX_ARRAY_SIZE的值是最大的 Integer - 8 (2147483639)
if (newCapacity - MAX_ARRAY_SIZE > 0)
/*
綜合起來我們可以知道 minCapacity 是大于 2147483639 的
下面方法會(huì)返回一個(gè) 合適 的較大數(shù)
*/
newCapacity = hugeCapacity(minCapacity);
//方法獲取了一個(gè)新的數(shù)組,長(zhǎng)度為新的長(zhǎng)度,前面的元素不變, 延長(zhǎng)的部分使用默認(rèn)值
elementData = Arrays.copyOf(elementData, newCapacity);
}
在這其中我們調(diào)用了 hugeCapacity
方法:
private static int hugeCapacity(int minCapacity) {
//負(fù)數(shù)拋出內(nèi)存溢出異常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//返回 MAX_VALUE 或者 MAX_VALUE-8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
?所以我們得出了,在列表長(zhǎng)度沒有變的特別巨大的情況下,新的數(shù)組長(zhǎng)度一般情況下會(huì)變成原有長(zhǎng)度的1.5倍
所以當(dāng)我們?cè)谡{(diào)用 add
方法的時(shí)候,會(huì)發(fā)生如下事情:
public boolean add(E e) {
//調(diào)用了增加長(zhǎng)度的方法,具體增加到多少其實(shí)不重要
ensureCapacityInternal(size + 1);
//給 size 的下一個(gè)標(biāo)記位賦值
elementData[size++] = e;?
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//空集合的時(shí)候的處理,其實(shí)不怎么重要
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//核心就是接著往下走了
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//長(zhǎng)度不對(duì)的時(shí)候有用,但是在 add 環(huán)境下只是傳值給 grow
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
所以我們會(huì)發(fā)現(xiàn)其實(shí)核心代碼還是 grow
方法,其它的都是處理一些特殊情況的.
查找
在 ArrayList
中有幾個(gè)類似的方法,他們都有一個(gè)特性,就是能夠從列表中找到一個(gè)特定的元素并且執(zhí)行某些操作, 例如:
public boolean contains(Object o);
public int indexOf(Object o);
public int lastIndexOf(Object o);
public boolean remove(Object o);
這些方法都非常類似,所以我們現(xiàn)在研究一下 indexOf
方法:
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;
}
小結(jié)
很顯然我們能夠得到結(jié)論:
- 我們?cè)趯ふ覍?duì)象的時(shí)候會(huì)用 for 循環(huán)遍歷數(shù)組中的每一個(gè)元素
- 尋找對(duì)象的時(shí)候我們使用的是
equals
方法
函數(shù)方法
在 ArrayList
中有幾個(gè)方法,它以函數(shù)接口作為參數(shù), 分別是如下方法:
public void forEach(Consumer<? super E> action);
public boolean removeIf(Predicate<? super E> filter);
public void replaceAll(UnaryOperator<E> operator);
public void sort(Comparator<? super E> c);
以下我們會(huì)對(duì)上面幾個(gè)方法舉例使用或者進(jìn)行代碼分析:
forEach
forEach
方法要求傳入一個(gè) consumer, 然后依據(jù)我們傳入的 lambda 去對(duì)列表的每一個(gè)元素進(jìn)行一次消費(fèi)行為.
源碼
public void forEach(Consumer<? super E> action) {
//函數(shù)的非空校驗(yàn)
Objects.requireNonNull(action);
//記錄當(dāng)前操作次數(shù),獲取當(dāng)前的數(shù)據(jù)和大小
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
//對(duì)每一個(gè)元素進(jìn)行循環(huán),并且執(zhí)行對(duì)應(yīng)的消費(fèi)函數(shù)
//modCount 是用來防止多線程的時(shí)候出現(xiàn)問題的, 這個(gè)是一個(gè)?確保相對(duì)線程安全的方案
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
//一旦在遍歷結(jié)束之后發(fā)現(xiàn)中途列表發(fā)生了變化,那么拋出異常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
栗子
@Test
public void base05(){
List<Integer> arrayList = Arrays.asList(1,2,3,4,5);
arrayList.forEach(i -> {
System.out.println(i);
});
}
removeIf
removeIf
方法要求傳入一個(gè)? predicate 并且將返回值為 true 的移除方法.和 stream 的 filter 有些類似.
栗子
@Test
public void base06(){
List<Integer> arrayList = Arrays.asList(1,2,3,4,5);
ArrayList<Integer> newList = new ArrayList<Integer>(arrayList);
newList.removeIf(i -> {
return i % 2 == 0;
});
newList.forEach(i -> {
System.out.println(i);
});
}
replaceAll
replaceAll
方法接受一個(gè) UnaryOperator 函數(shù),返回相同類型的數(shù)據(jù)來替換原有數(shù)據(jù)
源碼
public void replaceAll(UnaryOperator<E> operator) {
//非空校驗(yàn)
Objects.requireNonNull(operator);
//記錄當(dāng)前操作次數(shù),獲取當(dāng)前的數(shù)據(jù)和大小
final int expectedModCount = modCount;
final int size = this.size;
//對(duì)每一個(gè)元素進(jìn)行循環(huán),并且執(zhí)行對(duì)應(yīng)的函數(shù)
//modCount 是用來防止多線程的時(shí)候出現(xiàn)問題的, 這個(gè)是一個(gè)?確保相對(duì)線程安全的方案
for (int i=0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
//一旦在遍歷結(jié)束之后發(fā)現(xiàn)中途列表發(fā)生了變化,那么拋出異常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
栗子
@Test
public void base07(){
List<Integer> arrayList = Arrays.asList(1,2,3,4,5);
ArrayList<Integer> newList = new ArrayList<Integer>(arrayList);
newList.replaceAll(i -> {
return 10 - i;
});
newList.forEach(i -> {
System.out.println(i);
});
}
sort
sort
方法需要傳入一個(gè) comparator 接口對(duì)象的函數(shù),表示用來比較兩個(gè)數(shù)的大小的函數(shù)
源碼
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
栗子
@Test
public void base08(){
List<Integer> arrayList = Arrays.asList(934,2,13,43,5);
ArrayList<Integer> newList = new ArrayList<Integer>(arrayList);
newList.sort((a,b) -> {
if(a > b){ return 1;}
else {return -1;}
});
newList.forEach(i -> {
System.out.println(i);
});
有關(guān) ArrayList
的學(xué)習(xí)就到這里.
之后我們會(huì)繼續(xù)學(xué)習(xí) LinkedList
的相關(guān)內(nèi)容.
歡迎關(guān)注我的博客: 既然來了就坐坐吧
小站剛開始起步,歡迎您的駕到.