欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中的ArrayList底層源碼分析

 更新時(shí)間:2023年12月15日 09:35:58   作者:愛(ài)喝咖啡的程序員  
這篇文章主要介紹了Java中的ArrayList底層源碼分析,通過(guò)下標(biāo)讀取元素的速度很快,這是因?yàn)锳rrayList底層基于數(shù)組實(shí)現(xiàn),可以根據(jù)下標(biāo)快速的找到內(nèi)存地址,接著讀取內(nèi)存地址中存放的數(shù)據(jù),需要的朋友可以參考下

一. 基本原理和優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

1.通過(guò)下標(biāo)讀取元素的速度很快,這是因?yàn)锳rrayList底層基于數(shù)組實(shí)現(xiàn),可以根據(jù)下標(biāo)快速的找到內(nèi)存地址,接著讀取內(nèi)存地址中存放的數(shù)據(jù)。

2.隨機(jī)讀的性能很高,仍然是因?yàn)锳rrayList底層基于數(shù)組實(shí)現(xiàn)。(隨機(jī)讀: 一會(huì)兒list.get(2),一會(huì)兒list.get(10))

缺點(diǎn):

1.數(shù)組的長(zhǎng)度是固定的,如果ArrayList的初始長(zhǎng)度太小,后續(xù)又不斷的向list寫(xiě)入數(shù)據(jù),會(huì)導(dǎo)致數(shù)組頻繁的擴(kuò)容和復(fù)制數(shù)據(jù),而這些過(guò)程非常影響系統(tǒng)的運(yùn)行。

2.由于是數(shù)組實(shí)現(xiàn)的,如果想往中間插入一個(gè)元素,會(huì)導(dǎo)致這個(gè)元素后面所有的元素都要往后挪動(dòng)一個(gè)位置。

二. 源碼分析

1.1 默認(rèn)的構(gòu)造函數(shù)

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

如果使用默認(rèn)的構(gòu)造函數(shù),初始化時(shí)是一個(gè)空數(shù)組,數(shù)組的類(lèi)型是Object[ ],數(shù)組的長(zhǎng)度為0。

當(dāng)執(zhí)行add操作后,才會(huì)為ArrayList進(jìn)行一次擴(kuò)容,這里使用的是ArrayList的初始長(zhǎng)度,10。

1.2 add(E e)

list.add("張三");
list.add("李四");
public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}

每次執(zhí)行ArrayList的add()方法,先會(huì)判斷一下,看看當(dāng)前數(shù)組是否已滿(mǎn),能不能放得下本次待加進(jìn)去的元素,如果數(shù)組已經(jīng)被塞滿(mǎn)了,那就進(jìn)行擴(kuò)容,創(chuàng)建一個(gè)新數(shù)組,把老數(shù)組中的數(shù)據(jù)拷貝到新數(shù)組中,確保新數(shù)組能裝得下足夠多的元素。

剛剛我們說(shuō)了,使用ArrayList的無(wú)參構(gòu)造函數(shù),初始化時(shí)數(shù)組的長(zhǎng)度為0。

list.add(“張三”);

elementData[size++]=e;此時(shí)會(huì)把元素插入到下標(biāo)為0的位置,接著把size設(shè)置成0+1=1。(顯然,size就是ArrayList實(shí)際存儲(chǔ)元素的個(gè)數(shù))

list.add(“李四”)l;

elementData[size++]=e;此時(shí)會(huì)把"李四"插入到下標(biāo)為1的位置,接著把size設(shè)置成1+1=2。

1.3 add(int index, E element)

list.add("張三");
list.add("李四");
list.add("王五");
list.add(1, "趙六");
public void add(int index, E element) {
	rangeCheckForAdd(index);
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	System.arraycopy(elementData, index, elementData, index + 1,
					 size - index);
	elementData[index] = element;
	size++;
}

首先,執(zhí)行rangeCheckForAdd(index); 檢查說(shuō),待插入的元素下標(biāo)不能大于當(dāng)前元素的個(gè)數(shù),也不能小于0。

問(wèn)題: 不能小于0倒是好理解,為什么待插入的元素下標(biāo)可以等于當(dāng)前元素的個(gè)數(shù)呢?

答: 若相等,則代表插入這個(gè)元素,正好不需要挪動(dòng)舊數(shù)組中任何的元素,不會(huì)造成任何的壞影響。但是當(dāng)待插入元素的下標(biāo)大于當(dāng)前元素,會(huì)導(dǎo)致數(shù)組跳躍式的插入元素,這對(duì)于數(shù)組來(lái)說(shuō),是絕對(duì)不允許的。

就拿當(dāng)前的例子來(lái)說(shuō),[“張三”, “李四”, “王五”],現(xiàn)在想要執(zhí)行l(wèi)ist.add(4, “趙六”);如果真的讓你得逞了,豈不是會(huì)出現(xiàn) [“張三”, “李四”, “王五”, , “趙六”] 導(dǎo)致數(shù)組中存放的數(shù)據(jù)不連續(xù)的恐怖后果?

private void rangeCheckForAdd(int index) {
	if (index > size || index < 0)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

接著,執(zhí)行ensureCapacityInternal(size + 1),數(shù)組擴(kuò)容,確保數(shù)組有足夠的空間能容納待插入的元素,別搞得插入元素后,把數(shù)組撐爆了。

然后,執(zhí)行System.arraycopy(elementData, index, elementData, index + 1,size - index); 挪動(dòng)舊元素,為待插入的元素騰位置。案例中,System.arraycopy(elementData, 1, elementData, 2,2); 就是從elementData的第二個(gè)元素開(kāi)始,拷貝2個(gè)元素到elementData的第三個(gè)元素的位置上。

我們可以看看elementData,原本[“張三”, “李四”, “王五”] ,現(xiàn)在是[“張三”, “李四”, “李四”, “王五”]

最后,elementData[index] = element; 把趙六插入到index=1的位置上,現(xiàn)在是[“張三”, “趙六”, “李四”, “王五”]

1.4 ensureCapacityInternal

這個(gè)方法的作用是擴(kuò)容,它可以說(shuō)是ArrayList中最為核心的代碼了,所以單獨(dú)拿出來(lái)說(shuō)一下。

private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

執(zhí)行add操作時(shí),總是會(huì)先執(zhí)行ensureCapacityInternal(),計(jì)算出加上了本次待新增的元素后,總共需要多大的空間來(lái)存儲(chǔ)。

假設(shè)初始化數(shù)組的大小是10,陸陸續(xù)續(xù)的已經(jīng)有10個(gè)元素填入了數(shù)組,此時(shí)想要加入第11個(gè)元素。

首先,執(zhí)行calculateCapacity(elementData, minCapacity); 這個(gè)方法主要是用來(lái)初始化空數(shù)組,有些時(shí)候,我們使用new ArrayList()初始化了數(shù)組,沒(méi)有指定數(shù)組的初始大小,那么當(dāng)往數(shù)組中插入元素時(shí),ArrayList就會(huì)為我們初始化數(shù)組,默認(rèn)的長(zhǎng)度是10,如果你一次性加的元素太多(比如你使用的是addAll( ) ),則按照加入的元素個(gè)數(shù)來(lái)定。

private static int calculateCapacity(Object[] elementData, int minCapacity) {
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	return minCapacity;
}

接著執(zhí)行ensureExplicitCapacity(),modCount指的是數(shù)組被修改的次數(shù),如果數(shù)組沒(méi)有足夠的空間放下新增的元素,就會(huì)觸發(fā)擴(kuò)容。

這個(gè)很好理解,比如你的數(shù)組長(zhǎng)度是10,已經(jīng)放滿(mǎn)了,現(xiàn)在想要插入第11個(gè)元素,肯定是插不進(jìn)去的,所以自然就要擴(kuò)容。

private void ensureExplicitCapacity(int minCapacity) {
	modCount++;

	// overflow-conscious code
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}

那么怎么擴(kuò)容呢?很簡(jiǎn)單,增加原有數(shù)組容量的一半。比如原來(lái)的數(shù)組長(zhǎng)度為10,擴(kuò)容一次后,數(shù)組長(zhǎng)度=10 + 10/2 =15。

如果原數(shù)組經(jīng)過(guò)1.5倍的擴(kuò)容后,仍然放不下待插入的元素,怎么辦?那么新數(shù)組的長(zhǎng)度就以添加新元素后的最小長(zhǎng)度為準(zhǔn)。

比如說(shuō),原來(lái)的數(shù)組長(zhǎng)度為10,默認(rèn)情況下,擴(kuò)容一次長(zhǎng)度就是15,現(xiàn)在我一次想要插入100個(gè)元素(通過(guò)addAll()方法),此時(shí)數(shù)組肯定是裝不下,這個(gè)時(shí)候,新數(shù)組的長(zhǎng)度就等于10+100=110了。

private void grow(int minCapacity) {
	// overflow-conscious code
	int oldCapacity = elementData.length;
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity = hugeCapacity(minCapacity);
	// minCapacity is usually close to size, so this is a win:
	elementData = Arrays.copyOf(elementData, newCapacity);
}

最后一步,從當(dāng)前數(shù)組中最后一個(gè)元素的后一個(gè)位置開(kāi)始,加入新的元素。

elementData[size++] = e;

1.5 set(int index, E element)

list.add("張三");
list.add("李四");
list.add("王五");
list.set(1, "趙六");
public E set(int index, E element) {
 	rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

首先,執(zhí)行rangeCheck(index),這一步就是在檢查,你想替換的下標(biāo)是否存在元素。從1.2節(jié)我們知道了,size代表著ArrayList內(nèi)實(shí)際存放的元素個(gè)數(shù),別忘了ArrayList的底層是數(shù)組實(shí)現(xiàn)的,所以不可能跳躍的存儲(chǔ)元素,因此,下標(biāo)從0到size-1,一定會(huì)有元素。如果你現(xiàn)在想要替換的下標(biāo)大于等于size,那么在ArrayList中,連這個(gè)元素都不存在,那你還怎么替換元素?替換空氣嗎?

private void rangeCheck(int index) {
	if (index >= size)
		throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

接著,執(zhí)行E oldValue = elementData(index); 看看,這里直接把要替換的元素給取出來(lái)了。在上述案例中,list.set(1, “趙六”)會(huì)把下標(biāo)為1的元素給取出來(lái),也就是李四,所以oldValue=李四。

然后,執(zhí)行elementData[index] = element; 在我們的案例中,也就是elementData[1] = “趙六”,把下標(biāo)為1的位置上的元素替換成了"趙六"。

最后,返回被替換的舊數(shù)據(jù),也就是李四。

1.6 get(E element)

list.get(1);
public E get(int index) {
  rangeCheck(index);
  return elementData(index);
}
E elementData(int index) {
	return (E) elementData[index];
}

就是這么簡(jiǎn)單,根據(jù)下標(biāo),直接從數(shù)組中獲取數(shù)據(jù)。沒(méi)什么好說(shuō)的,這個(gè)方法的性能是ArrayList所有方法中最高的。

1.7 remove(int index)

list.remove(1);
list.remove(2);
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;
}

執(zhí)行rangeCheck(index),看看你想刪除的元素的下標(biāo)是否存在,有沒(méi)有越界。比如數(shù)組中只有3個(gè)元素,你偏要?jiǎng)h第10個(gè)元素,那ArrayList上哪去幫你刪?

執(zhí)行int numMoved = size - index - 1; 所謂刪除元素,說(shuō)白了就是把待刪除元素后面的元素,全部前進(jìn)一格,相當(dāng)于是add(index, E)的逆向操作。

問(wèn)題: 不就是挪動(dòng)待刪除元素右邊的元素么,干脆寫(xiě)成int numMoved = size + 1;這樣可以不?

答: 不可以。numMoved 代表的是需要移動(dòng)的元素的個(gè)數(shù)。

執(zhí)行elementData[–size] = null; 現(xiàn)在已經(jīng)把元素往前挪了一格,比如舊數(shù)組為[“張三”, “李四”, “王五”, “趙六”],現(xiàn)在想刪除index=1的元素,當(dāng)執(zhí)行完System.arraycopy,也就是移動(dòng)(實(shí)際上是復(fù)制)元素的過(guò)程后,數(shù)組為 [“張三”, “王五”, “趙六”, “趙六”],最后,我們只需要?jiǎng)h除末尾元素即可。

所謂的刪除,就是置為null,由于之前的對(duì)象沒(méi)有了引用,接著讓JVM來(lái)回收即可。

三. 結(jié)論

1.在對(duì)ArrayList做隨機(jī)位置的插入和刪除操作時(shí),會(huì)涉及到數(shù)組大量元素的移動(dòng)(其實(shí)就是拷貝和刪除),所以性能都不高。(具體可以參考add(int index, E element) 和 remove(int index)這兩個(gè)方法)

2.執(zhí)行add或者add(int index, E element)時(shí),如果插入的比較頻繁,偶爾會(huì)由于舊數(shù)組容量不夠,導(dǎo)致擴(kuò)容,擴(kuò)容就會(huì)導(dǎo)致創(chuàng)建新數(shù)組,復(fù)制數(shù)據(jù),所以性能不高。

3.set()或者get(),這兩個(gè)方法都是靠數(shù)組下標(biāo)來(lái)定位待操作的元素,接著替換或者讀取元素,這個(gè)性能還是不錯(cuò)的。

4.一旦經(jīng)歷了擴(kuò)容后,就算后期刪除了元素,ArrayList也不會(huì)主動(dòng)縮容。

到此這篇關(guān)于Java中的ArrayList底層源碼分析的文章就介紹到這了,更多相關(guān)ArrayList底層源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring中Feign的調(diào)用流程詳解

    Spring中Feign的調(diào)用流程詳解

    這篇文章主要介紹了Spring中Feign的調(diào)用流程詳解,分析過(guò)了創(chuàng)建的代理是FeignInvocationHandler,那我們就打斷點(diǎn),停在它的反射方法上,看看到底做了什么,需要的朋友可以參考下
    2023-11-11
  • 通過(guò)實(shí)例解析Java不可變對(duì)象原理

    通過(guò)實(shí)例解析Java不可變對(duì)象原理

    這篇文章主要介紹了通過(guò)實(shí)例解析Java不可變對(duì)象原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Java中IO流使用FileWriter寫(xiě)數(shù)據(jù)基本操作詳解

    Java中IO流使用FileWriter寫(xiě)數(shù)據(jù)基本操作詳解

    這篇文章主要介紹了Java中IO流FileWriter寫(xiě)數(shù)據(jù)操作,FileWriter類(lèi)提供了多種寫(xiě)入字符的方法,包括寫(xiě)入單個(gè)字符、寫(xiě)入字符數(shù)組和寫(xiě)入字符串等,它還提供了一些其他的方法,如刷新緩沖區(qū)、關(guān)閉文件等,需要的朋友可以參考下
    2023-10-10
  • SpringBoot自動(dòng)重啟、熱啟動(dòng)方式

    SpringBoot自動(dòng)重啟、熱啟動(dòng)方式

    這篇文章主要介紹了SpringBoot自動(dòng)重啟、熱啟動(dòng)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 簡(jiǎn)單談?wù)凧ava垃圾回收

    簡(jiǎn)單談?wù)凧ava垃圾回收

    本文是看了James Gosling的<<Java程序設(shè)計(jì)語(yǔ)言>>后結(jié)合自己的一些項(xiàng)目經(jīng)驗(yàn),簡(jiǎn)單總結(jié)下關(guān)于java的垃圾回收問(wèn)題的看法,有需要的小伙伴可以參考下
    2016-05-05
  • Java中JUC?的?Exchange?交換器詳情

    Java中JUC?的?Exchange?交換器詳情

    這篇文章主要介紹了Java中JUC?的?Exchange?交換器詳情,文章基于Java的相關(guān)資料展開(kāi)詳細(xì)的內(nèi)容介紹,需要的小伙伴可以參考一下
    2022-05-05
  • Java中的FutureTask實(shí)現(xiàn)異步任務(wù)代碼實(shí)例

    Java中的FutureTask實(shí)現(xiàn)異步任務(wù)代碼實(shí)例

    這篇文章主要介紹了Java中的FutureTask實(shí)現(xiàn)異步任務(wù)代碼實(shí)例,普通的線程執(zhí)行是無(wú)法獲取到執(zhí)行結(jié)果的,FutureTask?間接實(shí)現(xiàn)了?Runnable?和?Future?接口,可以得到子線程耗時(shí)操作的執(zhí)行結(jié)果,AsyncTask?異步任務(wù)就是使用了該機(jī)制,需要的朋友可以參考下
    2024-01-01
  • JMeter導(dǎo)入自定義的Jar包的詳解教程

    JMeter導(dǎo)入自定義的Jar包的詳解教程

    這篇文章主要介紹了JMeter導(dǎo)入自定義的Jar包的詳解教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 簡(jiǎn)單聊一聊Java線程池ThreadPoolExecutor

    簡(jiǎn)單聊一聊Java線程池ThreadPoolExecutor

    在使用線程池之后,開(kāi)啟線程就變成了在線程池當(dāng)中找到一個(gè)空閑的線程,銷(xiāo)毀線程變成了歸還線程到線程池的過(guò)程,下面這篇文章主要給大家介紹了關(guān)于Java線程池ThreadPoolExecutor的相關(guān)資料,需要的朋友可以參考下
    2022-06-06
  • mybatisplus 的SQL攔截器實(shí)現(xiàn)關(guān)聯(lián)查詢(xún)功能

    mybatisplus 的SQL攔截器實(shí)現(xiàn)關(guān)聯(lián)查詢(xún)功能

    大家都知道m(xù)ybatisplus不支持關(guān)聯(lián)查詢(xún),后來(lái)學(xué)習(xí)研究發(fā)現(xiàn)mybatisplus的SQL攔截器可以實(shí)現(xiàn)這一操作,下面小編給大家分享我的demo實(shí)現(xiàn)基本的關(guān)聯(lián)查詢(xún)功能沒(méi)有問(wèn)題,對(duì)mybatisplus關(guān)聯(lián)查詢(xún)相關(guān)知識(shí)感興趣的朋友一起看看吧
    2021-06-06

最新評(píng)論