最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 的ArrayList集合底層實現(xiàn)與最佳實踐

 更新時間:2025年11月12日 11:44:49   作者:李少兄  
本文主要介紹了Java的ArrayList集合類的核心概念、底層實現(xiàn)、關(guān)鍵成員變量、初始化機制、容量演變、擴容機制、性能分析、核心方法源碼解析、特殊場景與常見問題、性能優(yōu)化與最佳實踐以及與LinkedList的對比,感興趣的朋友跟隨小編一起看看吧

1. 核心概念與底層實現(xiàn)

1.1 ArrayList 的本質(zhì)

ArrayList 是基于 動態(tài)數(shù)組List 實現(xiàn)類,其底層數(shù)據(jù)結(jié)構(gòu)是 Object[] elementData。它通過動態(tài)擴容機制(自動擴展數(shù)組長度)實現(xiàn)靈活的存儲需求,但犧牲了線程安全性。

  • 定義
    ArrayList是Java集合框架中的一個動態(tài)數(shù)組實現(xiàn)類,繼承自AbstractList,實現(xiàn)了List接口。它允許存儲重復(fù)元素null值,并支持通過索引快速訪問元素
  • 核心特性
    • 動態(tài)數(shù)組:容量可自動擴展,無需手動管理。
    • 線程不安全:多線程環(huán)境下需手動同步或使用Vector/CopyOnWriteArrayList
    • 隨機訪問高效:通過索引訪問元素的時間復(fù)雜度為O(1)。
    • 增刪操作較慢:中間位置的插入/刪除需要移動元素,時間復(fù)雜度為O(n)。

1.1.1 底層數(shù)據(jù)結(jié)構(gòu)

ArrayList的底層基于對象數(shù)組Object[] elementData)實現(xiàn):

  • 初始容量:默認為10(通過DEFAULT_CAPACITY定義)。
  • 動態(tài)擴容:當數(shù)組空間不足時,自動擴容為原容量的1.5倍oldCapacity + (oldCapacity >> 1))。
  • 關(guān)鍵成員變量
    // 底層數(shù)組,存儲所有元素
    transient Object[] elementData;
    // 當前元素個數(shù)
    private int size;
    

JDK 1.7 vs JDK 1.8的初始化差異

特性JDK 1.7JDK 1.8+
無參構(gòu)造的elementData.length100(DEFAULTCAPACITY_EMPTY_ELEMENTDATA
首次添加元素時的擴容不觸發(fā)(已預(yù)分配)觸發(fā)擴容到10
內(nèi)存占用立即分配10個元素的內(nèi)存空間延遲分配,節(jié)省初始內(nèi)存

1.1.2 動態(tài)擴容機制

擴容觸發(fā)條件

當調(diào)用add()方法時,若當前數(shù)組容量(elementData.length)小于size + 1,則觸發(fā)擴容。

擴容規(guī)則

  • 新容量計算newCapacity = oldCapacity + (oldCapacity >> 1)(即原容量的1.5倍)。
  • 特殊情況處理
    • 若計算后的容量仍小于所需最小容量(minCapacity),則直接使用minCapacity
    • 若擴容過程中出現(xiàn)整數(shù)溢出(極端大容量),拋出OutOfMemoryError

1.2 關(guān)鍵成員變量

// 核心成員變量
transient Object[] elementData; // 底層數(shù)組,存儲元素
private int size; // 當前元素個數(shù)
// 靜態(tài)常量
private static final Object[] EMPTY_ELEMENTDATA = new Object[0]; // 空數(shù)組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0]; // 無參構(gòu)造時使用
private static final int DEFAULT_CAPACITY = 10; // 默認初始容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 最大數(shù)組長度

2. 初始化機制:從0到10的蛻變

2.1 無參構(gòu)造的陷阱

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 初始化為空數(shù)組(長度0)
}
  • 關(guān)鍵點
    • elementData.length 初始為0,但 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一個靜態(tài)空數(shù)組(長度0)。
    • 首次添加元素時,會觸發(fā) 強制擴容到10,而非直接使用 elementData 的原始長度。

2.2 首次擴容的觸發(fā)過程

當調(diào)用 add() 方法時:

public boolean add(E e) {
    ensureCapacityInternal(size + 1); // 此處觸發(fā)擴容
    elementData[size++] = e;
    return true;
}

2.2.1ensureCapacityInternal的核心邏輯

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 強制設(shè)置為10
    }
    ensureExplicitCapacity(minCapacity);
}
  • 關(guān)鍵步驟
    1. 檢查 elementData 是否為 DEFAULTCAPACITY_EMPTY_ELEMENTDATA(即無參構(gòu)造的空數(shù)組)。
    2. 若是,則將 minCapacity 設(shè)為 max(10, minCapacity),確保首次擴容至少到10。
    3. 調(diào)用 ensureExplicitCapacity 繼續(xù)檢查。

2.2.2ensureExplicitCapacity的邏輯

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果當前容量不足
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity);
    }
}
  • 觸發(fā)擴容條件minCapacity > elementData.length。

2.2.3grow方法的擴容計算

private void grow(int minCapacity) {
    int oldCapacity = elementData.length; // 當前容量
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量 = 原容量的1.5倍
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity; // 若新容量仍不足,則直接使用minCapacity
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity); // 處理溢出
    }
    elementData = Arrays.copyOf(elementData, newCapacity); // 復(fù)制數(shù)據(jù)到新數(shù)組
}
  • 首次擴容時
    • oldCapacity = 0newCapacity = 0 + 0 = 0,但經(jīng)過 ensureCapacityInternal 的修正后,minCapacity = 10。
    • 最終 newCapacity = 10,觸發(fā) Arrays.copyOf 創(chuàng)建新數(shù)組。

3. 容量演變的詳細過程

初始容量的定義

  • 默認初始容量ArrayList的默認初始容量是10,但這僅在首次添加元素時生效。
  • 底層數(shù)組的初始狀態(tài)
    • JDK 1.7:無參構(gòu)造時直接創(chuàng)建長度為10的數(shù)組(預(yù)分配策略)。
    • JDK 1.8+:無參構(gòu)造時底層數(shù)組初始化為空數(shù)組(長度為0),采用懶漢式初始化,直到首次調(diào)用add()方法時才會分配容量為10的數(shù)組。

關(guān)鍵成員變量

// 源碼片段(JDK 1.8+)
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 默認初始容量為10
    private static final int DEFAULT_CAPACITY = 10;
    // 空數(shù)組,用于區(qū)分不同狀態(tài)的空ArrayList
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 無參構(gòu)造時使用
    // 底層數(shù)組,存儲元素
    transient Object[] elementData;
    // 當前元素個數(shù)
    private int size;
}

無參構(gòu)造方法的初始化

// 無參構(gòu)造方法(JDK 1.8+)
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 初始化為空數(shù)組
}
  • elementData的初始狀態(tài):
    • elementData被初始化為DEFAULTCAPACITY_EMPTY_ELEMENTDATA,其長度為0。
    • 此時,ArrayList的容量(elementData.length)為0,但默認容量(DEFAULT_CAPACITY)是10。

首次添加元素時的初始化

當調(diào)用add()方法添加第一個元素時,會觸發(fā)以下流程:

// add(E e)方法(JDK 1.8+)
public boolean add(E e) {
    // 確保容量足夠
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    // 如果當前elementData是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(即無參構(gòu)造的情況)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 將minCapacity設(shè)為max(DEFAULT_CAPACITY, minCapacity)
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果當前容量不足
    if (minCapacity - elementData.length > 0) 
        grow(minCapacity);
}
  • 關(guān)鍵步驟
    1. elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA時(即無參構(gòu)造的空列表),minCapacity被強制設(shè)為Math.max(DEFAULT_CAPACITY, minCapacity)。
    2. 此時,minCapacity為10(假設(shè)首次添加一個元素,minCapacity = 1,但會被替換為10)。
    3. 調(diào)用grow(minCapacity)擴容到10。

初始容量的動態(tài)變化

  • JDK 1.8+的初始容量流程
    • 無參構(gòu)造時:elementData.length = 0,size = 0。
    • 首次添加元素時:擴容到10,elementData.length = 10,size = 1。
    • 后續(xù)添加元素時:當size達到10時,觸發(fā)下一次擴容(10 → 15)。

3.1 不同場景的容量變化

3.1.1 無參構(gòu)造的初始狀態(tài)

ArrayList<String> list = new ArrayList<>();
System.out.println(list.size()); // 0
System.out.println(list.elementData.length); // 0(JDK 1.8+)

3.1.2 首次添加元素

list.add("Hello");
// 此時:
System.out.println(list.size()); // 1
System.out.println(list.elementData.length); // 10(擴容到10)

3.1.3 添加第11個元素

for (int i = 1; i < 10; i++) {
    list.add("World");
}
list.add("World"); // 第11個元素
// 此時:
System.out.println(list.size()); // 11
System.out.println(list.elementData.length); // 15(10 → 15)

3.1.4 添加第16個元素

for (int i = 0; i < 5; i++) {
    list.add("Java");
}
// 此時:
System.out.println(list.elementData.length); // 22(15 → 22)

4. 擴容的數(shù)學模型與性能分析

4.1 擴容的數(shù)學公式

  • 擴容公式
    newCapacity = oldCapacity + (oldCapacity >> 1)
    等價于 newCapacity = oldCapacity * 1.5(向下取整)。

4.2 擴容的漸進特性

當前容量新容量計算新容量實際值
00 + 010(強制修正)
1010 + 515
1515 + 722
2222 + 1133

4.3 擴容的性能代價

  • 時間復(fù)雜度
    每次擴容需復(fù)制所有元素,時間復(fù)雜度為 O(n),但通過 指數(shù)增長策略,總擴容時間復(fù)雜度為 O(n)(攤還分析)。
  • 空間復(fù)雜度
    最終容量可能超過實際需求,但通過 trimToSize()(JDK 11+)可回收冗余空間。

5. 核心方法的源碼解析

5.1 尾部添加add(E e)

public boolean add(E e) {
    ensureCapacityInternal(size + 1); // 確保容量足夠
    elementData[size++] = e; // 直接寫入數(shù)組末尾
    return true;
}
  • 時間復(fù)雜度O(1)(不考慮擴容開銷)。

5.2 中間插入add(int index, E element)

public void add(int index, E element) {
    rangeCheckForAdd(index); // 索引檢查
    ensureCapacityInternal(size + 1);
    // 將[index, size) 的元素后移一位
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}
  • 時間復(fù)雜度O(n)(需移動元素)。

5.3 刪除元素remove(int index)

public E remove(int index) {
    rangeCheck(index); // 索引檢查
    modCount++;
    E oldValue = (E) elementData[index];
    int numMoved = size - index - 1;
    if (numMoved > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    }
    elementData[--size] = null; // 釋放引用
    return oldValue;
}
  • 時間復(fù)雜度O(n)(需移動元素)。

6. 特殊場景與常見問題

6.1 初始容量為0的誤解

  • 常見誤區(qū):認為 ArrayList 的初始容量是0,但實際:
    • JDK 1.8+:無參構(gòu)造時 elementData.length = 0,但首次添加元素時強制擴容到10。
    • size 的初始值始終為0,直到元素被添加。

6.2 擴容溢出的處理

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) { // 無法處理負數(shù)
        throw new OutOfMemoryError();
    }
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
  • 作用:當擴容請求超過 MAX_ARRAY_SIZE(2^31-9)時,使用 Integer.MAX_VALUE。

6.3 并發(fā)修改異常(ConcurrentModificationException)

  • 觸發(fā)條件:在迭代過程中修改集合(非通過迭代器)。
  • 解決方案
List<String> list = new ArrayList<>();
// 錯誤示例:
for (String s : list) {
    if (s.equals("remove")) {
        list.remove(s); // 拋出異常
    }
}
// 正確示例:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("remove")) {
        it.remove(); // 安全刪除
    }
}

7. 性能優(yōu)化與最佳實踐

7.1 預(yù)分配容量

// 優(yōu)化示例:已知數(shù)據(jù)量時預(yù)分配容量
ArrayList<String> list = new ArrayList<>(1000000); // 初始容量100萬
for (int i = 0; i < 1000000; i++) {
    list.add("Data" + i);
}

7.2 避免頻繁擴容

  • 場景:添加大量元素時,預(yù)分配容量可減少擴容次數(shù)。
  • 數(shù)學證明
    • 假設(shè)初始容量為 C,每次擴容增長1.5倍,添加 N 個元素時,擴容次數(shù)為:
      log_1.5(N / C)
      
    • 預(yù)分配 C = N 可完全避免擴容。

7.3 使用trimToSize()

// JDK 11+:回收冗余容量
list.trimToSize(); // 將數(shù)組長度調(diào)整為當前size

8. 與 LinkedList 的深度對比

8.1 底層結(jié)構(gòu)對比

特性ArrayListLinkedList
存儲結(jié)構(gòu)連續(xù)內(nèi)存數(shù)組雙向鏈表(每個節(jié)點存儲前后指針)
訪問速度快(O(1))慢(O(n)需遍歷)
插入/刪除速度慢(中間操作需移動元素,O(n))快(修改指針,O(1))
內(nèi)存占用低(連續(xù)內(nèi)存)高(每個節(jié)點存儲額外指針)

8.2 適用場景

  • ArrayList
    • 頻繁查詢、少量增刪。
    • 數(shù)據(jù)量較大但訪問模式固定。
  • LinkedList
    • 頻繁增刪、少量查詢。
    • 需要雙向遍歷或鏈表特性(如隊列、棧)。

9. 源碼級優(yōu)化技巧

9.1 避免toArray()的性能陷阱

// 錯誤示例:頻繁調(diào)用toArray()導(dǎo)致額外開銷
for (Object obj : list.toArray()) {
    // ...
}
// 優(yōu)化示例:直接使用elementData(需謹慎)
Object[] arr = list.toArray();
for (Object obj : arr) {
    // ...
}

9.2 使用subList()的注意事項

// 避免直接修改子列表的引用
List<String> sublist = list.subList(0, 10);
sublist.clear(); // 會修改原列表

10. 總結(jié):ArrayList 的設(shè)計哲學

  • 核心思想:通過 動態(tài)數(shù)組 實現(xiàn)高效隨機訪問,以 1.5倍擴容 平衡內(nèi)存與性能。
  • 適用場景:優(yōu)先選擇 ArrayList,除非需要頻繁的中間增刪操作。
  • 最佳實踐:預(yù)分配容量、避免并發(fā)修改、合理使用 trimToSize()。

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

相關(guān)文章

  • Spring根據(jù)XML配置文件 p名稱空間注入屬性的實例

    Spring根據(jù)XML配置文件 p名稱空間注入屬性的實例

    下面小編就為大家分享一篇Spring根據(jù)XML配置文件 p名稱空間注入屬性的實例,具有很好的參考價值。希望對大家有所幫助
    2017-11-11
  • java基礎(chǔ)之方法詳解

    java基礎(chǔ)之方法詳解

    這篇文章主要介紹了java基礎(chǔ)之方法詳解,文中有非常詳細的代碼示例,對正在學習java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04
  • 一種類似JAVA線程池的C++線程池實現(xiàn)方法

    一種類似JAVA線程池的C++線程池實現(xiàn)方法

    線程池(thread pool)是一種線程使用模式。線程過多或者頻繁創(chuàng)建和銷毀線程會帶來調(diào)度開銷,進而影響緩存局部性和整體性能。這篇文章主要介紹了一種類似JAVA線程池的C++線程池實現(xiàn)方法,需要的朋友可以參考下
    2019-07-07
  • 一文秒懂?kafka?HA(高可用)

    一文秒懂?kafka?HA(高可用)

    這篇文章主要介紹了秒懂?kafka?HA(高可用)的相關(guān)知識,本文我們來說一說和?kafka?高可用相關(guān)的一些策略,對kafka?HA相關(guān)知識感興趣的朋友一起看看吧
    2021-11-11
  • Springboot Maven打包跳過測試的五種方式小結(jié)

    Springboot Maven打包跳過測試的五種方式小結(jié)

    本文主要介紹了Springboot Maven打包跳過測試的五種方式小結(jié),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • MyBatis3源碼解析之如何獲取數(shù)據(jù)源詳解

    MyBatis3源碼解析之如何獲取數(shù)據(jù)源詳解

    用myBatis3與spring整合的時候,我們可以通過多種方式獲取數(shù)據(jù)源,下面這篇文章主要給大家介紹了關(guān)于MyBatis3源碼解析之如何獲取數(shù)據(jù)源的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-06-06
  • 詳解Java設(shè)計模式——迭代器模式

    詳解Java設(shè)計模式——迭代器模式

    這篇文章主要介紹了Java設(shè)計模式——迭代器模式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-03-03
  • java httpclient設(shè)置超時時間和代理的方法

    java httpclient設(shè)置超時時間和代理的方法

    這篇文章主要介紹了java httpclient設(shè)置超時時間和代理的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-02-02
  • Springcould多模塊搭建Eureka服務(wù)器端口過程詳解

    Springcould多模塊搭建Eureka服務(wù)器端口過程詳解

    這篇文章主要介紹了Springcould多模塊搭建Eureka服務(wù)器端口過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • SpringBoot2.6.x升級后循環(huán)依賴及Swagger無法使用問題

    SpringBoot2.6.x升級后循環(huán)依賴及Swagger無法使用問題

    這篇文章主要為大家介紹了SpringBoot2.6.x升級后循環(huán)依賴及Swagger無法使用問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06

最新評論

张家口市| 竹山县| 桓仁| 民权县| 泾阳县| 夏邑县| 修文县| 澄江县| 金塔县| 嘉善县| 福建省| 新巴尔虎左旗| 嘉禾县| 宁强县| 长乐市| 松江区| 台北县| 麻城市| 株洲市| 莎车县| 龙南县| 阿坝| 鄢陵县| 唐河县| 旌德县| 太白县| 保山市| 沛县| 曲沃县| 和平县| 黄山市| 仁怀市| 大城县| 潞西市| 吴堡县| 苏尼特左旗| 依兰县| 宜都市| 杭锦后旗| 从化市| 虞城县|