淺析JDK8-ArrayList

基于jdk1.8源碼

UML類關(guān)系圖

? 我們先來看一下ArrayList的類關(guān)系圖诫咱。

ArrayList.png

? 從圖中可以看出件已,ArrayList繼承了AbstractList抽象類恋博,實現(xiàn)了4個接口雹仿。

1.實現(xiàn)了List接口:
2.實現(xiàn)了RandomAccess接口:
3.實現(xiàn)了Cloneable接口:
4.實現(xiàn)了Serializable接口:

構(gòu)造函數(shù)

<u>ArrayList的構(gòu)造方法就做一件事情重付,就是初始化一下儲存數(shù)據(jù)的容器勺像,其實本質(zhì)上就是一個數(shù)組障贸,在其中就叫elementData。</u>

1.ArrayList()

構(gòu)造一個初始容量為 10 的空列表吟宦。如果我們創(chuàng)建ArrayList對象的時候不傳入?yún)?shù)篮洁,則使用此無參構(gòu)造方法創(chuàng)建ArrayList對象。

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

初始化一個空的list殃姓,在add第一個元素的時候袁波,設(shè)置初始容量為10

 private static int calculateCapacity(Object[] elementData, int minCapacity) {
          if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //data一開始是空,則返回10的初始容量
              return Math.max(DEFAULT_CAPACITY, minCapacity);
          }
          return minCapacity;
      }

2.ArrayLIst(int)

構(gòu)造一個指定容量為capacity的空ArrayList蜗侈。這是一個帶初始容量大小的有參構(gòu)造函數(shù)篷牌。

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);
    }
}
  • 初始容量大于0,實例化數(shù)組,將自定義的容量大小當(dāng)成初始化elementData的大小
  • 初始容量等于0踏幻,將空數(shù)組賦給elementData
  • 初始容量小于0枷颊,拋異常

3.ArrayList(Collection<? extends E>)

構(gòu)造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。第二個有參構(gòu)造方法構(gòu)造時賦的值是它的父類Collection對象夭苗。

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)
          //每個集合的toarray()的實現(xiàn)方法不一樣信卡,所以需要判斷一下,如果不是Object[].class類型题造,那么就需要使用ArrayList中的方法去改造一下傍菇。
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
  • 將集合對象轉(zhuǎn)成數(shù)據(jù),地址賦值給elementData
  • 如果數(shù)組的實際大小等于0(c中沒有元素)界赔,將空數(shù)組EMPTY_ELEMENTDATA賦值給elementData
  • 如果size的值大于0桥嗤,則執(zhí)行Arrays.copy方法,把collection對象的內(nèi)容(可以理解為深拷貝)copy到elementData中()

重要方法

1.get方法

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

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

@SuppressWarnings("unchecked")
E elementData(int index) {
  return (E) elementData[index];
}

可以看到由于Arraylist底層就是個obj的數(shù)組仔蝌,所以獲取元素是比較簡單的。直接先對index進(jìn)行范圍判斷荒吏,然后返回數(shù)組下標(biāo)對應(yīng)的值敛惊。

2.set方法

public E set(int index, E element) {
    rangeCheck(index);

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

set方法是替換數(shù)組某個位置的值。先檢查下標(biāo)是否越界绰更,然后取出舊值瞧挤,把新值替換進(jìn)去,最后返回舊值給用戶儡湾。

3.add方法

(1)一個參的方法

在list末尾加上一個元素


public boolean add(E e) {
  //確保內(nèi)部容量特恬,傳的是size+1的最小容量值,避免浪費(fèi)
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

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

private static int calculateCapacity(Object[] elementData, int minCapacity) {
  //實際數(shù)組大小如果為空的話徐钠,初始化max(10癌刽,minCapacity)
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    return Math.max(DEFAULT_CAPACITY, minCapacity);
  }
  return minCapacity;
}

//確保明確的容量
private void ensureExplicitCapacity(int minCapacity) {
  modCount++;

  // overflow-conscious code
  //如果最終所需的最小容量>目前數(shù)組的長度,則進(jìn)行擴(kuò)容
  if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}

private void grow(int minCapacity) {
  // overflow-conscious code
  int oldCapacity = elementData.length;
  //新容量=老容量+老容量/2
  int newCapacity = oldCapacity + (oldCapacity >> 1);
  //擴(kuò)容后還比最小需求容量小的話尝丐,直接使用最小需求容量作為新的容量大小
  if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
  if (newCapacity - MAX_ARRAY_SIZE > 0)
    //如果新的容量超過了最大的數(shù)組容量显拜,則進(jìn)行巨大化處理
    newCapacity = hugeCapacity(minCapacity);
  // minCapacity is usually close to size, so this is a win:
  //講數(shù)組拷貝進(jìn)新擴(kuò)容的大數(shù)組里面
  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;
}

(2)兩個參的方法

在list指定的位置插入一個元素

public void add(int index, E element) {
  //越界檢查
    rangeCheckForAdd(index);
//確保內(nèi)部容量,不夠會調(diào)用grow進(jìn)行擴(kuò)容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
  //復(fù)制數(shù)組爹袁,arraycopy(原數(shù)組远荠,源數(shù)組中的起始位置,目標(biāo)數(shù)組失息,目標(biāo)數(shù)據(jù)中的起始位置譬淳,要復(fù)制的數(shù)組元素的數(shù)量)
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

4.remove方法

(1)刪除指定位置的方法

刪除指定位置元素,返回該元素

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);
  //將索引指向null,讓GC回收多余末尾的舊值
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

(2)刪除第一個匹配到的元素

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;
  //說是刪除盹兢,其實就是移動后面的元素覆蓋掉前面的元素邻梆,再將最后的元素索引設(shè)置為空,以此達(dá)到刪除的目的绎秒。
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

(3)刪除一段區(qū)間的元素

fromIndex<=刪除區(qū)間<toIndex

protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

對比jdk11的寫法

protected void removeRange(int fromIndex, int toIndex) {
  if (fromIndex > toIndex) {
    throw new IndexOutOfBoundsException(
      outOfBoundsMsg(fromIndex, toIndex));
  }
  modCount++;
  shiftTailOverGap(elementData, fromIndex, toIndex);
}

private void shiftTailOverGap(Object[] es, int lo, int hi) {
  System.arraycopy(es, hi, es, lo, size - hi);
  for (int to = size, i = (size -= hi - lo); i < to; i++)
    es[i] = null;
}

(4)removeAll和retainAll

//刪除源數(shù)組中包含目標(biāo)數(shù)組的元素
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);//false表示不保留相同的元素确虱,即刪除
}
//交集,保留源數(shù)組中包含目標(biāo)數(shù)組的元素
public boolean retainAll(Collection<?> c) {
  Objects.requireNonNull(c);
  return batchRemove(c, true);//true表示保留相同的元素
}

private boolean batchRemove(Collection<?> c, boolean complement) {
  final Object[] elementData = this.elementData; //拿原集合出來
  int r = 0, w = 0;
  boolean modified = false; //有沒有改變標(biāo)志
  try {
    for (; r < size; r++)//循環(huán)每一個原集合的元素
      if (c.contains(elementData[r]) == complement)//判斷每一個源元素是否存在于對比的list中。complement為true則把元素保存下來(retainAll);為false則不保留(removeAll)
        elementData[w++] = elementData[r];
  } finally {
    // Preserve behavioral compatibility with AbstractCollection,
    // even if c.contains() throws.
    if (r != size) {//如果contains過程出錯校辩,則把剩下的源數(shù)據(jù)的元素復(fù)制到目標(biāo)數(shù)組里面去
      System.arraycopy(elementData, r,//源數(shù)組窘问,源起始位置
                       elementData, w,//目標(biāo)數(shù)組,目標(biāo)起始位置
                       size - r);//復(fù)制的長度
      w += size - r;//更新目標(biāo)數(shù)組的最新長度
    }
    if (w != size) {//目標(biāo)數(shù)組剩下的元素為源數(shù)組的元素宜咒,可以==null惠赫,讓gc回收掉
      // clear to let GC do its work
      for (int i = w; i < size; i++)
        elementData[i] = null;
      modCount += size - w;
      size = w;
      modified = true;//數(shù)組的大小有變化則返回true;
    }
  }
  return modified;
}

(5)indexOf和lastIndexOf

//返回源數(shù)組中第一個命中的下標(biāo),如沒有則返回-1故黑。
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;
}
//倒序?qū)ふ叶郏祷氐谝粋€命中的下標(biāo)
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;
}

(6)clear()

public void clear() {
    modCount++;

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

    size = 0;
}

問題

1.默認(rèn)初始化大小

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

2.怎么擴(kuò)容

當(dāng)判斷需要的最小容量大于list中數(shù)組的長度時,則通過grow擴(kuò)容场晶,首先擴(kuò)大為原數(shù)組的1.5倍混埠,還不夠就擴(kuò)充為需求的容量大小。

3.最大容量

最大容量為MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

4.怎么找到下一個的

因為lsit的底層存儲還是數(shù)組诗轻,所以直接通過下標(biāo)定位就行了钳宪。

5.線程安全嗎

不安全的,主要問題出現(xiàn)在獲取size大小操作完后扳炬,size++這里不是原子性的吏颖。導(dǎo)致會出出現(xiàn)臟讀的情況。舉例:A和B兩個線程同時調(diào)用add方法

  1. 初始列表size大小為0恨樟。此時兩個線程同時拿到size為0半醉。
  2. 線程A執(zhí)行插入操作,將elementData[0]=A
  3. 此時線程B也執(zhí)行插入操作劝术,將elementData[0]=B
  4. 線程A將size++
  5. 線程B將size++

此時我們會發(fā)現(xiàn)ArrayList的size變成了2缩多,[0]=B;[1]=null。這就出現(xiàn)線程不安全的問題了

6.和hashmap的區(qū)別

額外知識點(diǎn)

1.transient關(guān)鍵字

/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer. Any
 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * will be expanded to DEFAULT_CAPACITY when the first element is added.
 */
transient Object[] elementData; // non-private to simplify nested class acces Object[] elementData; // non-private to simplify nested class access

transient的字面意思是短暫的养晋,在java中被此關(guān)鍵字修飾的屬性值將不會被序列化瞧壮。

具體可查看:https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc

2.MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

 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

有一些虛擬機(jī)在數(shù)組中保留了一些頭信息,為了避免內(nèi)存溢出而已匙握。

3.modCount的作用

在 AbstractList 中咆槽,有一個全局變量 madCount,記錄了結(jié)構(gòu)性改變的次數(shù)圈纺。結(jié)構(gòu)性改變指的是那些修改了列表大小的操作秦忿,在迭代過程中可能會造成錯誤的結(jié)果。

madCount 交由迭代器(Iterator)和列表迭代器(ListIterator)使用蛾娶,當(dāng)進(jìn)行 next()灯谣、remove()、previous()蛔琅、set()胎许、add() 等操作時,如果 modCount 的值意外改變,那么迭代器或者列表迭代器就會拋出 ConcurrentModificationException 異常辜窑。

如果希望提供快速失敼呈觥(fail-fast)機(jī)制,那么其子類就應(yīng)該在 add(int, E)穆碎、remove(int) 等結(jié)構(gòu)性改變的方法中將 madCount 變量自增牙勘,否則可以忽略該變量。

參考博文

  1. https://blog.csdn.net/u010250240/article/details/89762912
  2. https://baijiahao.baidu.com/s?id=1637926321175819771&wfr=spider&for=pc
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末所禀,一起剝皮案震驚了整個濱河市方面,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌色徘,老刑警劉巖恭金,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異褂策,居然都是意外死亡横腿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門辙培,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人邢锯,你說我怎么就攤上這事扬蕊。” “怎么了丹擎?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵尾抑,是天一觀的道長。 經(jīng)常有香客問我蒂培,道長再愈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任护戳,我火速辦了婚禮翎冲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘媳荒。我一直安慰自己抗悍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布钳枕。 她就那樣靜靜地躺著缴渊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鱼炒。 梳的紋絲不亂的頭發(fā)上衔沼,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼指蚁。 笑死菩佑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的欣舵。 我是一名探鬼主播擎鸠,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缘圈!你這毒婦竟也來了劣光?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤糟把,失蹤者是張志新(化名)和其女友劉穎绢涡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遣疯,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雄可,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缠犀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片数苫。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖辨液,靈堂內(nèi)的尸體忽然破棺而出虐急,到底是詐尸還是另有隱情,我是刑警寧澤滔迈,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布止吁,位于F島的核電站,受9級特大地震影響燎悍,放射性物質(zhì)發(fā)生泄漏敬惦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一谈山、第九天 我趴在偏房一處隱蔽的房頂上張望俄删。 院中可真熱鬧,春花似錦奏路、人聲如沸抗蠢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迅矛。三九已至,卻和暖如春潜叛,著一層夾襖步出監(jiān)牢的瞬間秽褒,已是汗流浹背壶硅。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留销斟,地道東北人庐椒。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像蚂踊,于是被迫代替她去往敵國和親约谈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359