庖丁解牛之ArrayList 源代碼分析和設計

背景

最近也看了一篇ArrayList的文章壹店,講了一些ArrayList的源碼相關的知識骄瓣,一直也打算寫一篇ArrayList源碼的文件械馆,今天正好有時間嫉柴,搞起厌杜!

類圖結構

ArrayList.png

先看一下List接口定義



package java.util;

import java.util.function.UnaryOperator;

public interface List<E> extends Collection<E> {
    int size();

    boolean isEmpty();
  
    boolean contains(Object o);

    Iterator<E> iterator();
   
    Object[] toArray();

    <T> T[] toArray(T[] a);

    boolean add(E e);

    boolean remove(Object o);

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean addAll(int index, Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);
 
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
   
    @SuppressWarnings({"unchecked", "rawtypes"})
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

    void clear();

    boolean equals(Object o);

    int hashCode();

    E get(int index);
    
    E set(int index, E element);

    E remove(int index);

    int indexOf(Object o);

    int lastIndexOf(Object o);
    
    ListIterator<E> listIterator();

    ListIterator<E> listIterator(int index);

    List<E> subList(int fromIndex, int toIndex);

    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }
}

源碼分析

我們先有這樣一個概念,ArrayList的本身就是一個數組結構差凹,存儲的對象都是Object類型期奔,我們先看一下構造方法

構造方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // non-private to simplify nested class access
    
    private int size;

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

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

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

ArrayList 構造方法有三個

  • 默認的構造方法侧馅,創(chuàng)建了一個空的Object數組
  • 指定容量的構造方法危尿,是根據initialCapacity的值大小創(chuàng)建一個指定的Object數據
  • 指定集合的構造方法,copy原來的集合創(chuàng)建新的集合

add方法馁痴,添加元素

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
      }
      ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
      modCount++;
      // overflow-conscious code
     if (minCapacity - elementData.length > 0)
        grow(minCapacity);
 }

通過add的源碼我們可以了解到谊娇,ensureCapacityInternal主要是計算數組長度是否已經滿了,滿了就需要對ArrayList的對象數組進行擴容罗晕, elementData[size++] = e; 這個比較好理解济欢,就是對這個數據進行賦值操作,關鍵的核心代碼還是ensureCapacityInternal這個方法小渊,當第一次給ArrayList添加元素的時候就會走過這個方法中

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

首先創(chuàng)建一個長多未10的Object數組法褥,接著就調用了ensureExplicitCapacity這個方法

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

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

modCount這個變量很關鍵,不管是對ArrayList執(zhí)行add酬屉、remove操作這個值都會增加半等,modCount這個變量主要的作用是在集合遍歷的時候來判斷集合中的數據有沒有修改揍愁,如果有修改了就跑出異常了ConcurrentModificationException,當minCapacity大于已經分配數據的大小時候就需要對原數組進行擴容,

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

擴容的數組大小是多少了呢杀饵?新擴容的數組大小=原來的數組+原來的數組的/2莽囤,這個地方有注意事項,就是最大的數組長度=Integer.MAX_VALUE - 8切距,為什么是這樣呢朽缎?因為要保留8個字節(jié)的空間給JVM,否則可能出現Oom異常

/**
 * 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;

一般情況下我們也不會關注這個谜悟,這個地方了解一下就OK了

remove 方法

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 remove method that skips bounds checking and does not
 * return the value removed.
 */
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
}

首先根據o是否為空來進行判斷话肖,如果o==null,先判斷集合中是否有空元素,如果過有則執(zhí)行fastRemove(index);

如果o不是空的赌躺,就遍歷集合對集合中的元素進行equals的判斷

fastRemove我們分析一下狼牺,建設ArrayList 中數據包含如何1、2礼患、3是钥、4


ArrayList remove.png

要想達到這樣的效果,我們看ArrayList源碼是怎么處理的

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
}

numMoved=size(5)-2-1=2,numMoved計算出了修改后的數據的最后一位的位置缅叠,

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

arraycopy方法是從指定的索引位置拷貝數據到des對象中悄泥,desPos是目標對象的啟示位置,length是拷貝的數量

clear 方法

public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

這個比較簡單肤粱,就是對數組中的元素賦予null

總結

1.難度并不是很復雜

2.通過源碼學習了一下ArrayList的實現弹囚,有很高的借鑒意義,比如對擴容的處理和和對異常情況擴容的處理

3.看了ArrayList的源代碼领曼,我們可以自己思考一下如何去實現這個數據結構鸥鹉?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庶骄,隨后出現的幾起案子毁渗,更是在濱河造成了極大的恐慌,老刑警劉巖单刁,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灸异,死亡現場離奇詭異,居然都是意外死亡羔飞,警方通過查閱死者的電腦和手機肺樟,發(fā)現死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門税肪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乘综,“玉大人,你說我怎么就攤上這事紊搪】ㄈ澹” “怎么了田柔?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵誓篱,是天一觀的道長。 經常有香客問我凯楔,道長窜骄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任摆屯,我火速辦了婚禮邻遏,結果婚禮上,老公的妹妹穿的比我還像新娘虐骑。我一直安慰自己准验,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布廷没。 她就那樣靜靜地躺著糊饱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颠黎。 梳的紋絲不亂的頭發(fā)上另锋,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音狭归,去河邊找鬼夭坪。 笑死,一個胖子當著我的面吹牛过椎,可吹牛的內容都是我干的室梅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疚宇,長吁一口氣:“原來是場噩夢啊……” “哼亡鼠!你這毒婦竟也來了?” 一聲冷哼從身側響起敷待,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤间涵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后讼撒,有當地人在樹林里發(fā)現了一具尸體浑厚,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡股耽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年根盒,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片物蝙。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡炎滞,死狀恐怖,靈堂內的尸體忽然破棺而出诬乞,到底是詐尸還是另有隱情册赛,我是刑警寧澤钠导,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站森瘪,受9級特大地震影響牡属,放射性物質發(fā)生泄漏。R本人自食惡果不足惜扼睬,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一逮栅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窗宇,春花似錦措伐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粪躬,卻和暖如春担败,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背镰官。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工氢架, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朋魔。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓岖研,卻偏偏與公主長得像,于是被迫代替她去往敵國和親警检。 傳聞我的和親對象是個殘疾皇子孙援,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354