ArrayList詳解

ArrayList是我們使用的最常用的集合,下面我會從ArrayList特徵和結構、源碼的分析,以及自我實現ArrayList三個方面剖析ArrayList.

ArrayList特徵和結構

    ArrayList是java中的動態數組。它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。
ArrayList 繼承了AbstractList,實現了List。它是一個數組隊列,提供了相關的添加、刪除、修改、遍歷等功能。
ArrayList 實現了RandmoAccess接口,即提供了隨機訪問功能。RandmoAccess是java中用來被List實現,爲List提供快速訪問功能的。在ArrayList中,我們即可以通過元素的序號快速獲取元素對象;這就是快速隨機訪問。稍後,我們會比較List的“快速隨機訪問”和“通過Iterator迭代器訪問”的效率。
ArrayList 實現了Cloneable接口,即覆蓋了函數clone(),能被克隆。
ArrayList 實現java.io.Serializable接口,這意味着ArrayList支持序列化,能通過序列化去傳輸。
和Vector不同,ArrayList中的操作不是線程安全的。所以,建議在單線程中才使用ArrayList,而在多線程中可以選擇Vector或者CopyOnWriteArrayList。

ArrayList源碼分析

我們看看ArrayList有哪些屬性:
/**
 * The array buffer into which the elements of the ArrayList are stored.
 * The capacity of the ArrayList is the length of this array buffer.
 */
private transient Object[] elementData;

/**
 * The size of the ArrayList (the number of elements it contains).
 *
 * @serial
 */
private int size;
ArrayList中一切操作都是對elementData數組的操作,而size屬性代表ArrayList中元素的個數。
我們看看ArrayList的基本的增、刪、改、查操作:
public boolean add(E e);
public void add(int index, E element);
public E set(int index, E element);
public E remove(int index);
public boolean remove(Object o);
public int indexOf(Object o);
具體操作我就不寫在上面了,我主要討論一下里面涉及到的問題和原理.在add()操作過程中,會先判斷當前數組容量是否足夠插入數據.
擴充容量方法如下:
    public void ensureCapacity(int minCapacity) {
	modCount++;
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object oldData[] = elementData;
	    int newCapacity = (oldCapacity * 3)/2 + 1;
    	    if (newCapacity < minCapacity)
		newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
    通過源碼,我們可以看到,List集合的容量擴展是按以前容量的1.5倍增長的。add(index,e)的具體過程是怎麼樣的,它是數組的一種插入數據的操作.set(index,e)是將數組index下標的值設爲e,它的返回值是原來index的舊值.這些基本的知識點,都需要我們通過查看ArrayList的源碼去看的。別人說的再多作用也不大,當我們自己去查看ArrayList的源碼時,就會有很多意想不到的收穫的!

數組的複製操作

    對ArrayList的操作就是對數組的操作,所以在ArrayList中有很多對數組的複製內容。接下來我們就一起來學習數組的複製.
數組的複製有兩種方法:
(1)使用System.arraycopy()複製
eg:
	int a[]={1,2,3,4,5,6,7};
	int b[]=new int[5];
	System.arraycopy(a,2,b,1,3);
	//參數的意義:a爲原數組,2爲a中開始複製的位置,b爲目標數組,1爲b中開始的位置,3爲複製的長度
	//所以b中的值爲: 0,3,4,5,0
	int a[]=new int[]{1,2,3,4,5,6,7};
	int b[]=new int[2];
	System.arraycopy(a,3,b,0,Math.min(a.length-3,b.length-0));  
	System.out.println(Arrays.toString(b)); 
src - 源數組。
srcPos - 源數組中的起始位置。
dest - 目標數組。
destPos - 目標數據中的起始位置。
length - 要複製的數組元素的數量。
(2)使用Arrays.copyOf()或Arrays.copyOfRange()方法
常用API如下:
 
   public static boolean[] copyOf(boolean[] original, int newLength);
   public static byte[] copyOf(byte[] original, int newLength);
   public static int[] copyOf(int[] original, int newLength);
   public static short[] copyOf(short[] original, int newLength);
   public static long[] copyOf(long[] original, int newLength);
   public static float[] copyOf(flot[] original, int newLength);
   public static double[] copyOf(double[] original, int newLength);
   public static  T[] copyOf(T[] original, int newLength);
   public static  T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType);
  
可以看到對基本數據類型都有對應的copyof方法,同時也有引用類型的複製 T[] copyOf,其內部都是調用System.arrayCopy()方法實現的.如果需要數組的複製範圍選擇,可以選擇Arrays.copyOfRange()方法。
我們着重來看看 public static T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType); 方法。T,U肯定是兩個不同的類型,那麼又能實現從U數組的內容複製到T數組,那麼能說明什麼呢?
我們查看源碼:
 
    public static  T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
  
裏面先通過 Array.newInstance(),反射創建一個與newType類型相同的數組,然後調用 system.arraycopy方法,實現數組的複製,那麼能夠從U類型數組複製元素到T類型元素,那麼可以確定的是,T類型一定能兼容U類型,比如說向上轉型,或者 Object o=new Integer(12) 我們通過例子說明:
	Integer[] intArray = new Integer[] { 1, 2, 3, 4, 5 };
	Object obj2[] = copyOf(intArray, 4, Object[].class);
	for (Object obj : obj2) {
		System.out.println(obj);
	}
  

ArrayList數組應用再探

上面一部分都是數組工具類Arrays中常用的一些功能,其複製功能在ArrayList中得到了廣泛的應用.接下來,我們再來看看ArrayList中的另外兩個高級應用:
(1)ArrayList轉換成數組
相應API如下:
  
	public Object[] toArray();
	public <t> T[] toArray(T[] a);
	</t>
第一個方法沒什麼好說的,我們看第二個方法。傳入T[]a,然後返回 T[]類型數組,這是個什麼鬼!
還是看它的源代碼吧:
        public <t> T[] toArray(T[] a) {
          if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
	        System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
            a[size] = null;
            return a;
        }
	</t>
分析結論如下:
    當數組a的長度小於size,則創建新的數組,複製ArrayList中數組的內容並返回新數組;當數組a的長度等於size時,複製ArrayList中數組的內容到數組a中,並返回a;當數組a的長度大於size時,複製ArrayList中數組的內容到數組a中,設置a[size]=null,並返回a 大家有沒有感覺一臉懵逼的感覺啊,爲啥要這樣設計咧!反正我感覺,直接傳入T.class參數蠻好的:
 
    @SuppressWarnings("unchecked")
	public <t> T[] toArray(Class<? extends T> cls) {
		T newType[] = (T[]) Array.newInstance(cls, size);
		System.arraycopy(elementData, 0, newType, 0, size);
		return newType;
	}
	</t>
(2)ArrayList的迭代器操作
在AbstractList中通過內部類實現Iterator接口,實現迭代,這裏應用到了迭代器模式,大家可以通過查看源碼來體會這種設計.以及實現ListIterator接口,實現List的前後雙向迭代過程。

自己實現ArrayList

研究ArrayList源碼,我們才能更清楚透徹的去了解ArrayList結構和實現。以前總是聽別人說集合裏面的代碼特別的優秀,很值得我們閱讀和借鑑。總是沒時間,也不敢去嘗試,終於花了一天時間來分析和了解ArrayList,並自己模仿寫出自己的ArrayList,雖然有很多東西還是不知道爲啥這樣做,但是還是收穫滿滿的,所以鼓勵大家多看看源碼,在這裏我也無恥的粘上自己的源碼咧!
自定義ArrayList下載
最後歡迎大家和我一起討論學習,一起提高!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章