ArrayList 源碼分析

ArrayList是在Java中最常用的集合之一,其本質(zhì)上可以當(dāng)做是一個可擴容的數(shù)組,可以添加重復(fù)的數(shù)據(jù),也支持隨機訪問

ArrayList的類定義

    public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

其中,RandomAccess、Cloneable、Serializable三個接口都屬于標(biāo)記接口,沒有任何需要手動實現(xiàn)的方法

有人以為clone()方法來自于Cloneable接口,實際上clone()方法來自于Object類

  • RandomAccess接口表示ArrayList支持隨機訪問,即可以直接訪問集合內(nèi)任意位置的元素
  • Cloneable接口表示ArrayList支持克隆,并重寫了克隆方法
public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

從上方代碼第四行出可以看出,調(diào)用clone()方法時,也重新創(chuàng)建了一個新的數(shù)組作為集合數(shù)據(jù)的容器,新舊兩個集合擁有各自的數(shù)據(jù)存儲的內(nèi)容空間,互不影響

  • Serializable接口表示ArrayList可序列化

除此之外, 父類AbstractList也提供了部分通用方法的實現(xiàn)。但是這些方法有很多都進行了重寫,所以本文不會過多關(guān)注AbstractList類。但是它提供了一個非常重要的成員變量:modCount,表示集合長度被修改的次數(shù),在遍歷集合段落中會詳細(xì)講解它的作用

ArrayList 的成員變量分析

ArrayList的成員變量包含

// 序列化ID
private static final long serialVersionUID = 8683452581122892189L;

// 集合的默認(rèn)容量
private static final int DEFAULT_CAPACITY = 10;

// 用于初始化elementData的空數(shù)組 
private static final Object[] EMPTY_ELEMENTDATA = {};

// 默認(rèn)容量下用于初始化elementData的空數(shù)組 
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存放實際元素的數(shù)組
transient Object[] elementData;

// 集合的大小
private int size;

// 集合的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

根據(jù)源碼,我們可以得到兩個比較直觀的信息:

  1. 集合的默認(rèn)容量是10,即調(diào)用無參構(gòu)造器生成的ArrayList,在不擴容的情況下,最多能夠存儲10個數(shù)據(jù)
  2. 集合的本質(zhì)是一個類型為Object的數(shù)組

要有一個值得注意的地方,成員變量中包含兩個用于初始化elementData的空數(shù)組:EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA

在實例化ArrayList的時候,如果指定capacity(容量)為0,則使用EMPTY_ELEMENTDATA來初始化elementData;沒有指定capacity,也就是使用默認(rèn)容量capacity = 10的情況下,則使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA來初始化elementData

為什么要根據(jù)不同的情況使用不同的空接口。因為ArrayList要根據(jù)不同的情況進行擴容。通過new ArrayList(0)構(gòu)造的集合,其容量就是0;但是通過new ArrayList()構(gòu)造的集合,雖然初始化后elementData也是一個空數(shù)組,但是在添加第一個元素后,其會立即擴容為一個容量為10(默認(rèn)capacity)的數(shù)組。這是兩種不同的處理方式,所以要區(qū)分不同的空數(shù)組對象。在后續(xù)的構(gòu)造函數(shù)添加元素段落中還會繼續(xù)講到

最后,為什么集合的最大容量是Integer.MAX_VALUE — 8?為什么要節(jié)省這8個容量?因為有一些虛擬機會在數(shù)組中保留一些頭信息或是描述信息,嘗試分配更大的數(shù)組可能會導(dǎo)致OutOfMemoryError

構(gòu)造器分析

ArrayList擁有三個構(gòu)造器,它們本質(zhì)上都是在對內(nèi)部的elementData進行初始化操作

  • 無參構(gòu)造器
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

這是最簡單的構(gòu)造器,僅僅只是將elementData指向常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA,以便于在添加第一個元素時,擴容至默認(rèn)容量capacity = 10的大小


  • 顯式設(shè)置初始化容量的構(gòu)造器
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);
    }
}

這個構(gòu)造器首先需要保證手動指定的容量大于0,否則會拋出IllegalArgumentException異常;其次,判斷手動指定的容量是否等于0,如果是,則直接將elementData指向靜態(tài)常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果不是,則創(chuàng)建一個跟指定容量相同大小的Object類型的數(shù)組作為elementData即可


  • 基于另一個集合創(chuàng)建ArrayList的構(gòu)造器
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

這個構(gòu)造器相對比較復(fù)雜。首先,將參數(shù)集合轉(zhuǎn)換為數(shù)組對象。接下來,判斷新數(shù)組對象是否包含元素,對應(yīng)源碼的第3行。如果沒有元素,則直接將elementData指向靜態(tài)常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA。如果數(shù)組對象包含元素,則要判定數(shù)組對象的類型是否屬于Object數(shù)組類型(由于不同集合有不同的toArray方法的實現(xiàn),所以toArray方法不一定返回的是Object類型的數(shù)組)。在類型不屬于Object數(shù)組類型時,根據(jù)當(dāng)前的數(shù)組對象的大小和實際元素,復(fù)制一個新的Object類型的數(shù)組作為elementData

重要方法

添加元素

在開始分析添加元素的方法之前,我們需要先來分析幾個工具方法

  • 確保集合內(nèi)容容量充足的前置方法
/**
 * 確保集合內(nèi)容容量充足的前置方法
 * @Param minCapacity 調(diào)用者指定的最小容量
 */
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

在添加元素時,ArrayList會判斷當(dāng)前容量是否充足,在不充足的情況下進行擴容。ensureCapacityInternal(int)方法只是用來獲取到當(dāng)前所需要的最小容量。正如源碼所展示的,當(dāng)前所需要的容量并不一定是方法調(diào)用者傳入的minCapacity的值,因為當(dāng)集合通過無參構(gòu)造器創(chuàng)建時,即elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA時,它的容量至少要達到10(默認(rèn)容量的大小:DEFAULT_CAPACITY)。

拿到最小需要的容量后,判斷當(dāng)前集合是否滿足條件,到底需不需要擴容,則交給了ensureExplicitCapacity(int)方法來處理


  • 判斷集合是否需要擴容的方法
/**
 * 判斷當(dāng)前集合是否需要擴容的方法
 * @Param minCapacity 實際需要的最小容量
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
  if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

之前提到了ArrayList從AbstractList父類中繼承了一個非常重要的字段:modCount,這個字段表示的是集合內(nèi)部數(shù)組對象長度可能被修改的次數(shù),在所有有可能改變數(shù)組對象長度的方法中,modCount都會進行自增操作,用于保證集合迭代時不會因為集合的長度發(fā)生改變而出現(xiàn)奇怪的錯誤,其具體的用法會在后續(xù)遍歷元素段落中詳細(xì)分析

只要實際需要的最小容量大于當(dāng)前集合的容量,就需要擴容。具體擴容的操作交給grow(int)來處理


  • 對集合進行擴容的方法
/**
 * 實際需要的最小容量
 * @Param minCapacity 實際需要的最小容量
 */
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

對集合進行擴容,我們需要知道擴容后的容量是多少?雖然當(dāng)前需要的最小容量是minCapacity,但是如果minCapacity小于原始容量的1.5倍,則ArrayList認(rèn)為這個minCapacity的容量也是不安全的,很有可能會進行第二次擴容。為了減少擴容帶來的消耗,此時ArrayList認(rèn)為擴容后的容量應(yīng)該為原始容量的1.5倍

不管是選擇minCapacity還是原始容量的1.5倍作為擴容后的容量,都會存在一種特殊情況:擴容后的容量大于了集合允許的最大容量(MAX_ARRAY_SIZE),這種情況應(yīng)該怎么處理?

前文說到,集合的最大容量為Integer.MAX_VALUE — 8,這節(jié)省的8個容量是用于某些虛擬機在數(shù)組中保存一些頭信息或描述信息。如果集合的容量實在不夠,那只能把原本節(jié)省下來的8個容量拿來使用,這也意味著,在某些虛擬機環(huán)境下,這樣的操作會導(dǎo)致OutOfMemoryError錯誤。

這個操作是由hugeCapacity(int)方法來完成的,由于這個方法非常簡單,在這里就不對其做單獨的分析了

確定擴容后的容量后,根據(jù)這個容量創(chuàng)建一個新的數(shù)組,并把原始數(shù)據(jù)的數(shù)據(jù)拷貝過來,集合的擴容也就完成了

到這里可以看出,擴容本身就是一種低效率的操作(除了要開辟新的內(nèi)存空間外,還得把數(shù)據(jù)一個一個的復(fù)制到新的空間中),并且隨著原始數(shù)據(jù)增加,操作的速度也會越來越慢(copy10個數(shù)據(jù)絕對比copy1個數(shù)據(jù)慢),所以最好能在構(gòu)造ArrayList時就預(yù)估好容量的大小,避免擴容帶來的開銷


現(xiàn)在我們再來分析添加元素的方法就比較簡單了

// 在集合的末尾添加一個元素
public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

// 在集合的指定位置,添加一個元素
public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

不管使用的是哪個方法,都需要判斷集合是否能夠容納size + 1容量的數(shù)據(jù)。如果達不到要求就開始擴容。其次,我們發(fā)現(xiàn)在指定位置添加元素是一件非常麻煩的事情,因為從指定位置開始到集合末尾的數(shù)據(jù),都得往后移動一位。所以在使用ArrayList的時候,盡量不要在集合中的某一個位置添加元素。如果確實存在這種需求,那么可以考慮使用LinkedList而不是ArrayList(后續(xù)我們也會對LinkedList的源碼進行分析)

這里有個很簡單的方法rangeCheckForAdd(int),其用來判斷參數(shù)index是否合法,不對其做單獨的分析

獲取元素

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index];
}

判斷指定坐標(biāo)是否越界(依賴于非常簡單的rangeCheck(int)方法),然后直接從內(nèi)部數(shù)據(jù)中獲取數(shù)據(jù)

刪除元素

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

刪除方法遇到了跟add(int, E)方法同樣的問題,即要把指定位置開始至整個集合末尾的所有元素向前移動一位

到此可以看出,對于ArrayList來說,添加元素、刪除元素都不是那么便捷的事情,ArrayList的優(yōu)勢還是在于能夠快速的訪問元素

由于現(xiàn)在較操作前少了一個元素,所以在移動元素之后需要把當(dāng)前集合的最后一位元素的值設(shè)置為null


還有一個刪除元素的方法

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

這個方法本質(zhì)上也是查找到指定元素在集合中的位置,再根據(jù)這個位置使用fastRemove(int)方法來執(zhí)行刪除操作

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

fastRemove(int)方法的原理跟remove(int)方法是一致的

注意,刪除操作也會改變集合的長度,所以每個刪除方法中都有modCount++操作

遍歷元素

在JDK1.5之后,通常使用foreach循環(huán)(也叫增強for循環(huán))來對集合進行遍歷


List<E> list = new ArrayList<>();
for(E e: list) {
    System.out.println(e);
}

foreach循環(huán)本質(zhì)上是在調(diào)用迭代器

List<E> list = new ArrayList<>();
Iterator<E> iter = list.iterator();
while(iter.hasNext()) {
    System.out.println(iter.next());
}

ArrayList調(diào)用iterator()方法返還的是一個類型為ArrayList$Itr的迭代器。這個類是ArrayList中的一個私有內(nèi)部類

private class Itr implements Iterator<E> {
    int cursor; // 下一個返回的元素的索引
    int lastRet = -1; // 上一次返還的元素的索引,如果沒有則為-1
    int expectedModCount = modCount; // 預(yù)期的集合長度被修改的次數(shù)
}

終于要到modCount的用法了

當(dāng)一個modCount為0(長度沒有被修改過)的ArrayList調(diào)用Iterator()方法后,我們會得到這樣的一個Itr對象

// 偽代碼
Itr: {
    cursor: 0,
    lastRet: -1,
    expectedModCount: 0
}

此時我們可以調(diào)用hasNext()方法來判斷是否還有元素未訪問,即是否能夠繼續(xù)迭代

public boolean hasNext() {
    return cursor != size;
}

如果上一次訪問的元素是集合的最后一個元素,即上一次訪問的元素索引為size - 1,那么就不能再繼續(xù)迭代了。這種情況下,因為cursor屬性代表下一次訪問元素的索引,所以可以說,cursor的值等于上一次訪問元素的索引 + 1,也就是說當(dāng)cursor = (size - 1) + 1時,就可以表示集合已經(jīng)迭代完畢。反過來,當(dāng)cursor != (size -1) + 1時,就表示集合還可以繼續(xù)迭代

當(dāng)我們通過hasNext()方法確定集合還能夠迭代時,可以通過next()方法取出迭代的元素的值

public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

除去驗證的代碼,重要的是,我們能夠看到,cursor始終指向下一次返回元素的索引,同時,lastRet指向當(dāng)前返還元素的索引

假設(shè),此時有另一個程序?qū)Ξ?dāng)前集合中的某一處進行了添加操作(調(diào)用了add(int, e)方法),如果對這樣的情況放任不管,那么迭代器迭代的數(shù)據(jù)肯定會有錯誤。所以,有了一個方法:checkForComodification()來確保迭代時,集合的長度沒有發(fā)生改變

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

此時,因為集合調(diào)用了add(int, e)方法,所以集合的屬性modCount = modCount + 1,而迭代器的屬性是expectedModCount是在迭代器實例化的時候就已經(jīng)創(chuàng)建好的,expectedModCount = 0,因此,modCount != expectedModCount,表示迭代器生成之后,集合發(fā)生了長度方面的改變,會影響集合的遍歷操作,拋出ConcurrentModificationException異常。從AbstractList處誕生的modCount屬性,一直到了這里才有了自己的用武之地

但是,有些時候,我們確實需要在遍歷的過程中操作數(shù)據(jù),比如說:遍歷某個集合,把符合某種條件的數(shù)據(jù)刪除掉。對于這樣的情況,迭代器提供了一個通過它自己來修改集合元素的方法:remove()

注意,迭代器只提供了對集合元素進行刪除操作的方法,沒有添加元素的方法

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    // 確保集合本身沒有被其他方式修改過
    checkForComodification();
    try {
        // 刪除上一次返回的元素
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        // modCount被修改了,expectedModCount也要跟著修改
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

值得注意的是,執(zhí)行刪除操作后,lastRet的值變?yōu)榱?1,所以,不能通過迭代器進行連續(xù)的刪除操作

List<E> list = new ArrayList<>();
Iterator<E> iter = list.iterator();

// 這是錯誤的做法
iter.next();
iter.remove();
// 連續(xù)刪除是不支持的
iter.remove();

// 這是正確的做法
iter.next();
iter.remove();
iter.next();
iter.remove();

ArrayList還有兩個獲取迭代器的方法:listIterator()和listIterator(int),這兩個方法返還的都是ArrayList$ListItr類型的迭代器

public ListIterator<E> listIterator() {
    return new ListItr(0);
}

public ListIterator<E> listIterator(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

而ArrayList$ListItr類同樣也是ArrayList的私有內(nèi)部類

private class ListItr extends Itr implements ListIterator<E> {
    // 省略了屬性和方法……
}

ListItr繼承自Itr,是Itr的擴展,實現(xiàn)了從后往前遍歷、添加元素、修改元素、返還索引等方法,其思路大致與Itr相同,因此不在此詳細(xì)分析了

其它方法

ArrayList還有幾個常用且有效的方法值得分析

  • 修改指定位置元素的值得方法
public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

該方法修改指定位置元素的值,并把修改前的值返回。注意,修改方法并沒有modCount++的操作,因為修改集合內(nèi)某一元素的值不會對集合的長度發(fā)生影響,也就影響不到迭代器的操作了

  • 清理集合內(nèi)剩余空間的方法
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
    }
}

有時候,我們確定集合內(nèi)的元素不會再發(fā)生變化,而集合還有剩余容量時可以調(diào)用這個方法來清理剩余容量。分為兩種情況:

  1. 當(dāng)集合的尺寸為0的時候,elementData指向常量** EMPTY_ELEMENTDATA**
  2. 當(dāng)集合的尺寸不為0的時候,根據(jù)當(dāng)前尺寸創(chuàng)建一個新的數(shù)組,復(fù)制原數(shù)組數(shù)據(jù),將新的數(shù)組賦值給elementData

注意,這也是一個可能會影響集合長度的方法 ,所以也有modCount++操作

  • 手動擴容的方法
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

有些時候我們會在集合實例化之后才確認(rèn)到具體的容量,而按照默認(rèn)的擴容方式(每次擴容50%),可能需要多次擴容才能達到預(yù)期的容量。前文說過,擴容是一個低效率的操作,為了避免多次執(zhí)行這樣的操作,所以我們可以主動調(diào)用ensureCapacity(int)方法來進行擴容

參數(shù)minCapacity代表預(yù)期的容量。但是對于通過無參構(gòu)造器創(chuàng)建的ArrayList(也就是說其容量為10),如果minCapacity小于10,則ArrayList依然認(rèn)為自己的容量應(yīng)該是10,不會進行任何操作

小結(jié)

本文較為詳細(xì)的分析了ArrayList的源碼,也提出了一些使用ArrayList的技巧,希望能夠?qū)Ω魑婚_發(fā)者帶來幫助。下一步我會繼續(xù)分享LinkedList的源碼分析,請大家多多支持

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內(nèi)容

  • ArrayList 原文見:Java 容器源碼分析之 ArrayList 概述 ArrayList是使用頻率最高的...
    Leocat閱讀 237評論 0 0
  • ArrayList 認(rèn)識 ArrayList是最常見以及每個Java開發(fā)者最熟悉的集合類了 elementData...
    zlb閱讀 156評論 0 0
  • 定義 除了實現(xiàn)了List接口,還實現(xiàn)了RandomAccess,Cloneable, java.io.Serial...
    zhanglbjames閱讀 434評論 0 0
  • ArrayList 概述 ArrayList 是實現(xiàn)List接口的動態(tài)數(shù)組,每個ArrayList實例都有一個默認(rèn)...
    ZcEDiaos閱讀 240評論 0 0
  • 果然想念一件事或者一個人,就很難集中精力去做其他事情。 看看自己好久沒有做oj ,沒有上coursera,沒有靜下...
    robotcator閱讀 183評論 0 1