1. 程式人生 > >jdk1.7 ArrayList源碼淺析

jdk1.7 ArrayList源碼淺析

copy ole efault out except ora lose 運算符 pac

參考:http://www.cnblogs.com/xrq730/p/4989451.html(借鑒的有點多,哈哈)

  首先介紹ArrayList的特性:

1、允許元素為空、允許重復元素

2、有序,即插入順序和訪問順序一致

3、非同步

ArrayList實現原理為動態數組

首先看構造方法:

  public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: 
"+ initialCapacity); this.elementData = new Object[initialCapacity]; } public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; }

第一個構造方法為指定大小進行初始化,第二個private static final Object[] EMPTY_ELEMENTDATA = {}; 相當於構造一個空的數組。

主要分析add()方法,其他應該可以類比。

常用的方法為add(E e)和add(int index , E element)

add()的方法簡單來講就是:

1、確保數組容量,其中就包括是否擴容然後將數組整個復制。同時modCount(用來記錄集合被修改的次數)值加一

2、將元素添加到數組中

  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal從字面上理解就是確保內部容量

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

        ensureExplicitCapacity(minCapacity);
    }
DEFAULT_CAPACITY等於10。AarryList在用無參的構造方法創建時,是沒有為裏面的數組成員分配空間的,
只有在進行add操作後才會分配空間,並會和10比較。再調用ensureExplicitCapacity來分配具體的空間,
並且在第一次add時會直接分配10個空間。
  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

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

在add操作時,若集合所需的最小容量大於當前數組長度(數組長度計算包括了為null的元素),就需要擴容。

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

關於擴容:

1、如果一次性擴容擴得太大,必然造成內存空間的浪費。

2、如果一次性擴容擴得不夠,那麽下一次擴容的操作必然比較快地會到來,這會降低程序運行效率,要知道擴容還是比價耗費性能的一個操作

newCapacity代表具體擴容成多大。第4行就相當於 int newCapacity=oldCapacity+ordCapacity/2(大約1.5倍)。用移位操作可以在數組容量大時節省點時間。

第一個if只在初次分配10個空間時執行。第二個if,hugeCapacity方法主要就是個三元運算符,MAX_ARRAY_SIZE=整型最大值-8(這個設計不懂)

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

疑問:為什麽不直接賦值為Integer.MAX_VALUE。

關於Arrays.copyOf方法其實就是調用的System.arraycopy,而後者又是加了native關鍵字,底層應該是c++之類的語言去實現整個數組的復制。

設計上面,Arrays.copyOf分了一個泛型方法和一個專門處理基本類型如int、float的方法。

在明確數組大小時可以手動為其擴容,來減少反復的擴容。

 public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != EMPTY_ELEMENTDATA)
            // any size if real element table
            ? 0
            // larger than default for empty table. It‘s already supposed to be
            // at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

add(int index, E element)方法:

1、判斷插入位置是否合法

2、確保數組容量,其中就包括是否擴容然後將數組整個復制。同時modCount(用來記錄集合被修改的次數)值加一。

3、將插入位置之後的元素復制到其加1之後的位置去(說的有點繞啊o(╯□╰)o),也就是從插入位置整體向後移一個單位。

4、將元素放到指定位置

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

ArrayList的優缺點

從上面的幾個過程總結一下ArrayList的優缺點。ArrayList的優點如下:

1、ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了RandomAccess接口,因此查找也就是get的時候非常快

2、ArrayList在順序添加一個元素的時候非常方便,只是往數組裏面添加了一個元素而已。

不過ArrayList的缺點也十分明顯:

1、刪除元素的時候,涉及到一次元素復制,如果要復制的元素很多,那麽就會比較耗費性能

2、插入元素的時候,涉及到一次元素復制,如果要復制的元素很多,那麽就會比較耗費性能

因此,ArrayList比較適合順序添加、隨機訪問的場景

ArrayList和Vector的區別

ArrayList是線程非安全的,這很明顯,因為ArrayList中所有的方法都不是同步的,在並發下一定會出現線程安全問題。那麽我們想要使用ArrayList並且讓它線程安全怎麽辦?一個方法是用Collections.synchronizedList方法把你的ArrayList變成一個線程安全的List,比如:

List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++)
{
    System.out.println(synchronizedList.get(i));
}

另一個方法就是Vector,它是ArrayList的線程安全版本,其實現90%和ArrayList都完全一樣,區別在於:

1、Vector是線程安全的,ArrayList是線程非安全的

2、Vector可以指定增長因子,如果該增長因子指定了,那麽擴容的時候會每次新的數組大小會在原數組的大小基礎上加上增長因子;如果不指定增長因子,那麽就給原數組大小*2,源代碼是這樣的:

int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                 capacityIncrement : oldCapacity);

jdk1.7 ArrayList源碼淺析