ArrayList——源碼探究

来源:http://www.cnblogs.com/yilixia/archive/2017/07/29/7257585.html
-Advertisement-
Play Games

ArrayList是java中使用頻率非常高的一個集合類,它的底層採用數組來實現,本文主要來探究一下其源碼,幫助記憶和理解。 ...


摘要

ArrayList UML

ArrayList 是Java中常用的一個集合類,其繼承自AbstractList並實現了List

構造器

ArrayList 提供了三個構造器,分別是
含參構造器-1

// 含參構造器-1
// 參數含義: 初始化線性表容量
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

// 含參構造器-2
// 參數含義:其他的集合類型轉為ArrayList
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;
        }
}

無參構造器

// 無參構造器
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

成員變數

// 預設的表容量
private static final int DEFAULT_CAPACITY = 10;
// 構造參數為 0 的時候,預設都指向該數組
private static final Object[] EMPTY_ELEMENTDATA = {};
// 無參構造時預設指向該數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 實際存放數據的數組
transient Object[] elementData;
// 表大小
private int size;
// 修改次數 Modify Count (定義在AbstractList中) 【劃重點】
protected transient int modCount = 0;

關於這兩個空數和構造器,參考如下代碼

ArrayList a1 = new ArrayList(0);
ArrayList a2 = new ArrayList();
ArrayList a3 = new ArrayList();
ArrayList a4 = new ArrayList(0);
Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
System.out.println(field.get(a1) == field.get(a2));
System.out.println(field.get(a2) == field.get(a3));
System.out.println(field.get(a1) == field.get(a4));

以上代碼將會輸出如下結果

false   # a1.elementData ≠ a2.elementData
true    # a2.elementData = a3.elementData
true    # a1.elementData = a4.elementData

內部類

// 迭代器實現類,實現了Iterator介面,可以使用增強的for迴圈
private class Itr implements Iterator<E>{}
// 需要註意的是:ListIterator介面繼承於Iterator介面
// ListIterator 和 Iterator最大的區別在於:Iterator單向移動,而ListIterator可以雙向移動並且可以增加、設置元素
// forEachRemaining 方法都是向後迭代的
private class ListItr extends Itr implements ListIterator<E>{}
// 為sublist()方法準備的內部類
private class SubList extends AbstractList<E> implements RandomAccess]{}
// 為多核應用開發所準備的Spliterator
static final class ArrayListSpliterator<E> implements Spliterator<E>{}

常用方法

擴容機制

ArrayList的擴容由以下幾個方法來實現。要明確的是,ArrayList的容量實際上就是其存放數據的elementData數組的長度,每一次自動的擴容都是由當前表的大小和elementData的長度做對比後而決定的。

    // 供使用者調用的容量初始化方法,在大量增加新數據的情況下,預先確定足夠的容量可以減少時間開支(因為會因為表不夠大而頻繁的擴容)
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;
        // 最小能保證表容量為DEFAULT_CAPACITY(10)
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    
    // 這個方法供內部調用,也就是在add和addAll里使用的。
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 按最大的算,此處也可以看出來,容量最小也是DEFAULT_CAPACITY(10)
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

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

        // overflow-conscious code
        // 不夠大就擴容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * 實際的擴容方法
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);     // 擴容實際最小擴的是1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);            // 最大也只能能是MAX_ARRAY_SIZE(有可能是Integer.MAX_VALUE)
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

add

  • 預設增加到最後一個元素後面
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  • 在指定位置後面插入

    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++;
    }

addAll

  • 預設增加到最後一個元素後面
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
  • 在指定位置後面插入
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount,同時確認是否擴容

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

remove

  • 按下標

    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;
    }
  • 按元素

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    
    private void fastRemove(int index) {
        modCount++;
        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,防止記憶體泄露
    }

set

  • 指定位置
    // 插入新值,返回舊值
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

get

  • 指定位置
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

clear

    public void clear() {
        modCount++;
        // 這裡不能簡單的把size置為0就完事,要確定本表對每一個元素不再有引用關係,避免記憶體泄漏
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

trimToSize

一般來說,表的大小要小於等於表的容量,而在記憶體緊張的時候,可能會用到該方法來使表的容量縮小至表的大小一樣大

    public void trimToSize() {
        modCount++;
        // size 只會 ≤ elementData.length,而等於的時候該方法的目的已經達到就不用做任何事了
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

indexOf

public int indexOf(Object o) {
    // 註意這裡區分了 o 是否為 null
    // 為null的情況很好理解,因為 null 的含義是確認的
    // 但是不為空即是一個對象的話,就需要用到equals方法,所以一般的類會重寫equals方法
    // Object的equals只是簡單的對引用做個比較
    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;
}

lastIndexOf

這個方法是與indexOf對應的一個方法,indexOf的實現是正序查找,而本方法是逆序查找

    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;
    }

contains

這個方法的內部實現是依靠indexOf方法來實現的

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

sublist

    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        // 這裡就用到了前面列出的SubList內部類
        // 需要註意的是:由sublist方法返回的List,對其操作時會在原List上生效,即它們是同一個元素
        return new SubList(this, 0, fromIndex, toIndex);
    }

補充方法

此外有四個補充的方法需要註意,這四個方法的參數都是函數式介面的實現類,即可以使用λ表達式

forEach

顧名思義,作用於每一個元素的方法

    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        // 正向遍歷
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            // accept的原型為: void accept(T t); 即一個無返回值的函數,也是需要在λ表達式里表示的內容
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

removeIf

    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            // test的原型為: boolean test(T t); 返回布爾值,在這裡也能看出來,當返回的值為true時,才進行代碼塊的操作
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0;
        if (anyToRemove) {
            final int newSize = size - removeCount;
            for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }
            for (int k=newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }

        return anyToRemove;
    }

replaceAll

    public void replaceAll(UnaryOperator<E> operator) {
        // UnaryOperator 一元運算符
        Objects.requireNonNull(operator);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
          // UnaryOperator<T> 繼承自 Function<T,T(R)>
          // 其apply 的原型為:  R apply(T t);
          
          // BinaryOperator<T> 繼承自 BiFunction<T,T(U),T(R)> 
          // 其apply方法原型為: R apply(T t, U u);
          elementData[i] = operator.apply((E) elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

sort

這裡的排序是藉助於Arrays這個工具類的sort方法來實現的,這裡僅作參考

    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++;
    }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 15套java架構師、集群、高可用、高可擴 展、高性能、高併發、性能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分佈 式項目實戰視頻教程 視頻課程包含: 高級Java架構師包含:Spring boot、Spring cloud、Dubbo ...
  • ReentrantLock是Java併發包中提供的一個可重入的互斥鎖。ReentrantLock和synchronized在基本用法,行為語義上都是類似的,同樣都具有可重入性。只不過相比原生的Synchronized,ReentrantLock增加了一些高級的擴展功能,比如它可以實現公平鎖,同時也可 ...
  • 通過python 來實現這樣一個簡單的爬蟲功能,把我們想要的圖片爬取到本地。(Python版本為3.6.0) 一.獲取整個頁面數據 說明: 向getHtml()函數傳遞一個網址,就可以把整個頁面下載下來. urllib.request 模塊提供了讀取web頁面數據的介面,我們可以像讀取本地文件一樣讀 ...
  • mybatis 是apache下的一個面向sql編程的半自動化的ORM持久層的框架。特點:面向sql編程,達到高性能的使用目的。 下麵是簡單使用 現導入jar包,只有mybatis和資料庫驅動包(這裡用的是mysql的驅動包)是必須的,其餘是日誌需要的包 db.properties配置連接池的配置文 ...
  • 靜態方法(@staticmethod) 通過@staticmethod裝飾器即可把其裝飾的方法變為一個靜態方法,什麼是靜態方法呢?其實不難理解,普通的方法,可以在實例化後直接調用,並且在方法里可以通過self.調用實例變數或類變數,但靜態方法是不可以訪問實例變數或類變數的,一個不能訪問實例變數和類變 ...
  • 最近的一次面試中,被問到橋接模式,以前呢並沒有很仔細的研究過這個設計模式,藉此機會剖析一下。 先給出自己對這個模式理解後的源碼: 那麼設個設計模式的應用場景呢? 從參考資料1中的解釋是,數據機Modem,舊版OldModem採用的的dial和answer的模式,新版採用send和receive的 ...
  • 首先,這是我對自己的需求而使用的邏輯,若有可以完美的地方方便告訴下小白。 MAVEN 1、前端頁面,偽非同步(頁面不刷新) 為什麼不用ajax呢? JQuery的ajax函數的返回類型只有xml、text、json、html等類型,沒有“流”類型。所以就用js做個form表單請求 上代碼 2、在工具包 ...
  • 爬取前的準備: BeautifulSoup的導入:pip install BeautifulSoup4 requests的導入:pip install requests 下載jupyter notebook:pip install jupyter notebook 下載python,配置環境(可使用 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...