ArrayLIst

Base:JDK1.8

一、ArrayList:

首先ArrayList是最多見的一種數據結構,數組。咱們知道在Java中,咱們若是想要使用數組,能夠某種數組(int爲例子)html

int arr=new int[10];java

這種形式進行去進行聲明,不過這有必定的缺憾,就是若是我想存儲的數據超過10個,就沒法存儲了,也就是說這個長度是不能改變的。c++

而ArrayList就很好的解決了這樣一個問題,可以動態擴容,可以存儲任何對象(基本類型會自動變成包裝了類)。算法

二、繼承的類and實現的接口:

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

 

這個是源代碼,首先繼承了AbstractList類,這個類是一個抽象的類,裏面定義的方法有:數組

2.一、AbstractList:

因爲LinkList也實現了這個類,因此裏面定義的方法都是一些共有的方法。(下篇就是LinkList)。安全

2.二、List接口:

接口中主要定義了不少經常使用的方法。數據結構

ps:我看到 AbstractList中有的方法跟List中的方法是同名方法,做爲重載,爲何不放在一個裏面呢?多線程

-----------------------------------------------app

若是有誰能幫忙回答一下,感激涕零dom

------------------------------------------------

2.三、RandomAccess

/* * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * */

package java.util;

/** * Marker interface used by <tt>List</tt> implementations to indicate that * they support fast (generally constant time) random access. The primary * purpose of this interface is to allow generic algorithms to alter their * behavior to provide good performance when applied to either random or * sequential access lists. * * <p>The best algorithms for manipulating random access lists (such as * <tt>ArrayList</tt>) can produce quadratic behavior when applied to * sequential access lists (such as <tt>LinkedList</tt>). Generic list * algorithms are encouraged to check whether the given list is an * <tt>instanceof</tt> this interface before applying an algorithm that would * provide poor performance if it were applied to a sequential access list, * and to alter their behavior if necessary to guarantee acceptable * performance. * * <p>It is recognized that the distinction between random and sequential * access is often fuzzy. For example, some <tt>List</tt> implementations * provide asymptotically linear access times if they get huge, but constant * access times in practice. Such a <tt>List</tt> implementation * should generally implement this interface. As a rule of thumb, a * <tt>List</tt> implementation should implement this interface if, * for typical instances of the class, this loop: * <pre> * for (int i=0, n=list.size(); i &lt; n; i++) * list.get(i); * </pre> * runs faster than this loop: * <pre> * for (Iterator i=list.iterator(); i.hasNext(); ) * i.next(); * </pre> * * <p>This interface is a member of the * <a href="{@docRoot}/../technotes/guides/collections/index.html"> * Java Collections Framework</a>. * * @since 1.4 */
public interface RandomAccess {
}

 

由於裏面沒有任何方法,因此我也不知道具體這個類是幹什麼用的,只能使用度娘去查:

jdk中有個RandomAccess接口,這是一個標記接口(Marker),它沒有任何方法,這個接口被List的實現類(子類)使用。若是List子類實現了RandomAccess接口,那就表示它可以快速隨機訪問存儲的元素。RandomAccess接口的意義在於:在對列表進行隨機或順序訪問的時候,訪問算法可以選擇性能最佳方式。

通常的列表訪問算法在訪問列表元素以前,都被建議先使用instanceof關鍵字檢查一下列表是不是一個RandomAccess子類,而後再決定採用隨機仍是順序方式訪問列表中的元素,這樣能夠保證訪問算法擁有最佳的性能。

http://blog.csdn.net/zht666/article/details/51597361  博客連接。

------------------------------------------------------------------------------------------------------

若是想要知道幹什麼,推薦去翻譯一下英語,寫的仍是挺明白的。

-------------------------------------------------------------------------------------------------------

2.四、接口: Cloneable, java.io.Serializable

實現了這兩個接口,也就說明ArrayList是支持  克隆和序列化的。

這兩個接口貌似也是 標記性接口。沒有具體的方法實現。

三、ArrayList中得數據常量、成員變量、數據結構、經常使用方法

3.一、常量

初始化的容量。

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

共享空數組?不知道做用

//Shared empty array instance used for empty instances.
 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 = {};

數組最大長度。具體爲何是這個數。看英語應該這個大小是vm 的極限?

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

3.二、成員變量

/**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

表示數組大小,無需解釋。

3.三、數據結構

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

真正用來存儲數據的數組。

咱們發現是:

transient的 即不可被序列化。(既然實現了Serializable接口,爲何又不讓數據進行可序列化呢?這裏就涉及到了人家設計的巧妙之處。涉及到後面的兩個方法:

writeObject(java.io.ObjectOutputStream s) 和 readObject(java.io.ObjectInputStream s))

底層是Object數組,所以它能存儲各類數據。(最好使用泛型,這樣更加安全高效)

因爲是數組,一定的數據結構是一個順序的結構。

這個結構是一個比較典型的:在邏輯內存在(腦海中)是連續的一片內存,在物理內存(實際電腦內存)中也是連續的一片內存。

這樣勢必會對內存有必定的要求,若是個人數組特別特別特別大,在內存中也得尋找能連續的起來的內存來存儲它,這樣就要求仍是比較大的,這個也算是一個弊端吧。

3.二、經常使用方法重要方法

初始化方法,這個最經常使用了吧,想用就得先初始化:

3.2.一、構造方法:

3.2.1.一、無參構造

這是讓數組指向 默認的空的數組。

(這裏其實我很不明白,elementData 引用了DEFAUL...這個是一個常量,爲何初始化要這麼指定呢?但願有人能給解釋一下

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

3.2.1.二、帶有數組長度的構造方法

這個構造方法能夠指定底層數組初始化時候的長度。

/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //若是指定長度>0,就使用這個長度
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //等於0 就使用共享的默認數組
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
           // 不然拋出初始化異常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

3.2.1.三、將某Collection中數據放入ArrayList的構造方法

 /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    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;
        }
    }

這個構造方法我用的不是不少,具體的解釋就看英文吧。。。

3.2.二、add

添加元素的方法,一樣也是有重載。

3.2.2.一、直接放入數組尾部的add

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        //確保再放入一個後,數組仍是未滿狀態,這個方法裏面會調用擴容方法。
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //注意size++ 與 ++size,這裏面直接先放入尾部,而後size+1
        elementData[size++] = e;
        return true;
    }

3.2.2.二、放入指定位置的add

/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //越界檢測
        rangeCheckForAdd(index);
        //確保數組放入一個後,仍是未滿狀態
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //這個使用的是System中的arraycopy方法,這個方法是native的,即本地的c/c++寫好的, 所以效率仍是能夠的
        //方法參數arraycopy(Object src(源數組),  int  srcPos(源數組開始的位置),
        //                              Object dest(目標數組), int destPos(目標數組開始的位置),
        //                              int length(複製的數組長度))
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //目標位置上的數據被新傳來的數據覆蓋
        elementData[index] = element;
        //size+1
        size++;
    }

3.2.3 ensureCapacityInternal

private void ensureCapacityInternal(int minCapacity) {
        //判斷數組是否是剛剛初始化,第一次添加
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //若是是第一次初始化,取10 和 minCapacity 中大數 做爲初始化長度
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //確認數組長度
        ensureExplicitCapacity(minCapacity);
    }

其實我仍是有點不太明白,爲何初始化的時候要去指定數組引用一個final數組,然後來又初始化?爲何不直接初始化呢?

3.2.4 ensureExplicitCapacity

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//每一次的操做都要modCount++,這個是基於fail-fast機制的。

        // overflow-conscious code
        //判斷是否容量是否裝滿
        if (minCapacity - elementData.length > 0)
             //若是滿,就進行擴容
             grow(minCapacity);
    }

這個函數的做用就是要去判斷是否須要去進行擴容。

3.2.5 grow

private void grow(int minCapacity) {
        // overflow-conscious code
        //由於數組存滿,纔會進行擴容,所以老的數組長度,就是等於數組.length
        int oldCapacity = elementData.length;
        //這個 =  oldCapacity+oldCapacity/2  所以這個是1.5倍(新的是老的1.5倍,而不是增長的長度是1.5倍)
        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:
        //上面的if 判斷是爲了保證新的長度是大於舊的長度,畢竟int也是有範圍的。
        //接下來是數組的總體複製。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

小結

以上是對 初始化,添加,擴容的一系列方法的源碼展現。

基本這個就是一個 完整的 操做流程。

首先初始化(默認長度爲10,也能夠指定長度)--->底層實現是一個Object數組,所以能存各類對象,由於Object是全部類的超類--->根據傳入的參數個數,進行add添加(分2種狀況,一個是直接加到數組末尾,一個加到指定位置,根據傳入的參數個數,可是這個時候並無添加到數組中)--->參數1個,先判斷是否須要進行擴容,若是須要就1.5倍擴容(這個1.5倍仍是有好處的,好處baidu)--->參數2個的話,就先判斷index是否越界,若是越界就拋出異常,不然就判斷是否須要擴容,最後使用System.Arraycopy方法進行數組從指定位置開始,總體後移--->最後將數據放入應該的位置(末尾 or 指定位置)。

3.2.6 get

public E get(int index) {
        //下標檢測
        rangeCheck(index);

        return elementData(index);
    }

get方法比較簡單,先檢查index 是否越界,而後 返回對應位置的數據。

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

這兩個方法,合起來就是 get方法。 第二段代碼的@SuppressWarnings("unchecked") ,該批註的做用是給編譯器一條指令,告訴它對被批註的代碼元素內部的某些警告保持靜默。

3.2.7 remove

remove是有兩個方法,一個直接是根據下標值,另外一個是根據對象的

3.2.7.1 remove(int)

public E remove(int index) {
        //越界檢測
        rangeCheck(index);
       //基於fail-fast 機制
        modCount++;
        //先得到
        E oldValue = elementData(index);
        //求出須要移動元素的個數
        int numMoved = size - index - 1;
        if (numMoved > 0)
         //若是index不是最後一個元素,則須要將後面的全部元素進行前移,
         //這個方法在 add(int,E)中也使用過就是使用的本地方法進行數組拷貝
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //將數組最後一個制空,讓gc回收
        elementData[--size] = null; // clear to let GC do its work
        //返回remove的值
        return oldValue;
    }

remove的方法代碼不是不少,邏輯也很明瞭,中間也是使用了一個arraycoyp方法。

3.2.7.2 remove(Object)

public boolean remove(Object o) {
        if (o == null) {
            //首先判斷是否是爲null
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            //若是不是null,就一次去使用equals去判斷每一,是否同樣
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

注意:這個方法也很簡單,可是須要主意的一點是,當你使用add(1) 的時候,jdk會自動識別,將你的int 類型自動裝箱轉換爲Integer,add(1) ==add(new Interger(1))

可是當使用remove()方法的時候,就不會幫你自動裝箱,轉換爲Interger類型了。

演示:測試代碼

public static void main(String[] args) {
		ArrayList<Integer> arr=new ArrayList<Integer>();
		for(int i=9;i>-1;i--){
			arr.add(i);
		}
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		System.out.println();
		arr.remove(2);
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		
	}

結果:

這個很明顯的是刪除了index爲2的 值。

再次測試:

public static void main(String[] args) {
		ArrayList<Integer> arr=new ArrayList<Integer>();
		for(int i=9;i>-1;i--){
			arr.add(i);
		}
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		System.out.println();
                //此處進行了修改
		arr.remove(new Integer(2));
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		
	}

結果:

這個結果很明顯的說明了刪除的是 值是2的數據。

 

所以,當儲存的是整形的時候,須要去主意,是remove的index 仍是remove的 O

 

3.2.8 set

public E set(int index, E element) {
        rangeCheck(index);
         
        E oldValue = elementData(index); 
        //直接放入指定位置,覆蓋原來數據就行。
        elementData[index] = element;
        return oldValue;
    }

set 方法就是覆蓋原來位置上的數據。很是簡單。

3.3 其餘經常使用方法或者簡單方法

這裏面就不貼方法了,由於比較簡單,簡單的列舉一下其餘方法

public boolean isEmpty() 判斷數組是否是空
public int size() 返回數組的長度
 public boolean contains(Object o) 返回是否包含指定對象
 public int indexOf(Object o) contains調用的具體的業務邏輯去判斷是否包含的方法
public int lastIndexOf(Object o) 從後往前判斷是指定對象的下標
public Object[] toArray() 返回一個數組
private void rangeCheckForAdd(int index) 邊界檢測,越界直接拋出異常
public Iterator<E> iterator() 返回一個迭代器(這有一個內部類),迭代器模式
   
   

3.4 補充方法

前面說到這個類是實現了一個 java.io.Serializable 接口,可是 數組Object 卻又被 transient 修飾,爲何要這麼作呢?

假設有這麼一個場景:假設如今數據量大,ArrayList的 length=20W,而後 size=11W。

因爲數組會被自動的填充,Object 都會是null 若是這樣 直接去 序列化,就會很浪費,畢竟後面的null不須要去序列化。所以他就去重寫了方法:

3.4.1 writeObject

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        //記錄序列化前的 fail-fast 
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        //記錄數組的大小
        s.writeInt(size);
        //具體的序列化的方法,只序列化有數據的部分,其餘部分略掉
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
        //fail-fast機制,若是序列化後發現數組被修改了(多線程中),就拋出異常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

 

3.4.2 readObject

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            //反序列化的核心
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

這兩個方法都是很好的防止了序列化和反序列化哪些沒必要要的數據,所以數組Object纔會被聲明爲:transient 。

-----------------------------------------------------------------------------

不保證代碼徹底正確,也不保證代碼是最優。

僅僅是根據本身的理解,寫出來直觀的代碼,方便理解。

錯誤請指出,感激涕零!

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息