程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 集合類學習之Arraylist 源碼分析

集合類學習之Arraylist 源碼分析

編輯:C#入門知識

集合類學習之Arraylist 源碼分析


ArrayList是List接口的可變數組的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。

每個ArrayList實例都有一個容量,該容量是指用來存儲列表元素的數組的大小。它總是至少等於列表的大小(如果不指定capacity,默認是10)。

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    }
隨著向ArrayList中不斷添加元素,其容量也自動增長。自動增長會帶來數據向新數組的重新拷貝(影響性能),因此,如果可預知數據量的多少,可在構造ArrayList時指定其容量。在添加大量元素前,應用程序也可以使用ensureCapacity操作來增加ArrayList實例的容量,這可以減少遞增式再分配的數量。

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }
/**
     * Increases the capacity of this ArrayList instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity
     */
    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);
    }
    }
注意: 在ensureCapacity中,
Object oldData[] = elementData;
是要在復制新數組前,保存原來數組的引用,因為後面這個引用會指向新的數組。但是保存後其實沒有用處。
elementData = Arrays.copyOf(elementData, newCapacity);
注意,此實現不是同步的。如果多個線程同時訪問一個ArrayList實例,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。

Arraylist的實現

底層利用object數組實現

private transient Object[] elementData;

構造方法

public ArrayList() {
    this(10);
}
 
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    this.elementData = new Object[initialCapacity];
}
 
public ArrayList(Collection c) {
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

此處也是用Arrays.copyOf實現數組的復制,重點研究一下。與生成新數組的時候一樣。

存儲

第一判斷ensureSize,如果夠直接插入,否則按照policy擴展,復制,重建數組。

第二步插入元素。

ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection c)、addAll(int index, Collection c)這些添加元素的方法。下面我們一一講解

1. set(int index, E element),取代,而非插入,返回被取代的元素

 /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
    RangeCheck(index);
 
    E oldValue = (E) elementData[index];
    elementData[index] = element;
    return oldValue;
    }
2.add(E e) 增加元素到末尾,如果size不溢出,自動增長
 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }
3.add(int index, E element) 增加元素到某個位置,該索引之後的元素都後移一位
/**
     * 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) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(
        "Index: "+index+", Size: "+size);
 
    ensureCapacity(size+1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
             size - index);
    elementData[index] = element;
    size++;
    }
3.後面兩個方法都是把集合轉換為數組利用c.toArray,然後利用Arrays.copyOF 方法,重點研究一下
 /**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return true if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection c) {
    Object[] a = c.toArray();
        int numNew = a.length;
    ensureCapacity(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
    return numNew != 0;
    }
Collection 接口定義了toArray方法,如下
 /**
     * Returns an array containing all of the elements in this collection.
     * If this collection makes any guarantees as to what order its elements
     * are returned by its iterator, this method must return the elements in
     * the same order.
     *
     * 

The returned array will be "safe" in that no references to it are * maintained by this collection. (In other words, this method must * allocate a new array even if this collection is backed by an array). * The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this collection */ Object[] toArray();

下面是arraylist對其的一種實現:
 /**
     * Returns an array containing all of the elements in this list
     * in proper sequence (from first to last element).
     *
     * 

The returned array will be "safe" in that no references to it are * maintained by this list. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * *

This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this list in * proper sequence */ public Object[] toArray() { return Arrays.copyOf(elementData, size); }


刪除

一種是按索引刪除,不用查詢,索引之後的element順序左移一位,並將最後一個element設為null,由gc負責回收。

 /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
    RangeCheck(index);
 
    modCount++;
    E oldValue = (E) elementData[index];
 
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                 numMoved);
    elementData[--size] = null; // Let gc do its work
 
    return oldValue;
    }

Arrays.copyOf

  /**
     * Copies the specified array, truncating or padding with nulls (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain null.
     * Such indices will exist if and only if the specified length
     * is greater than that of the original array.
     * The resulting array is of the class newType.
     *
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @param newType the class of the copy to be returned
     * @return a copy of the original array, truncated or padded with nulls
     *     to obtain the specified length
     * @throws NegativeArraySizeException if newLength is negative
     * @throws NullPointerException if original is null
     * @throws ArrayStoreException if an element copied from
     *     original is not of a runtime type that can be stored in
     *     an array of class newType
     * @since 1.6
     */
    public static  T[] copyOf(U[] original, int newLength, Class 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;
    }
此處jdk有所優化,如果是object類型,直接new object數組,如果不是通過Array.newInstance調用native方法產生響應的數組類型,創建數組後,通過System.arrayCopy實現數組復制,System是個final類,copyof是個native方法。(如果實現,為何用native方法???)源碼如下
* @param      src      the source array.
     * @param      srcPos   starting position in the source array.
     * @param      dest     the destination array.
     * @param      destPos  starting position in the destination data.
     * @param      length   the number of array elements to be copied.
     * @exception  IndexOutOfBoundsException  if copying would cause
     *               access of data outside array bounds.
     * @exception  ArrayStoreException  if an element in the src
     *               array could not be stored into the dest array
     *               because of a type mismatch.
     * @exception  NullPointerException if either src or
     *               dest is null.
     */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

備注:

  1. Native修飾符標示該方法的實現體非java,而是c++或者其他語言

    2. 有助於提升性能

    關於native

    Java不是完美的,Java的不足除了體現在運行速度上要比傳統的C++慢許多之外,Java無法直接訪問到操作系統底層(如系統硬件等),為此Java使用native方法來擴展Java程序的功能。

      可以將native方法比作Java程序同C程序的接口,其實現步驟:
      1、在Java中聲明native()方法,然後編譯;
      2、用javah產生一個.h文件;
      3、寫一個.cpp文件實現native導出方法,其中需要包含第二步產生的.h文件(注意其中又包含了JDK帶的jni.h文件);
      4、將第三步的.cpp文件編譯成動態鏈接庫文件;
      5、在Java中用System.loadLibrary()方法加載第四步產生的動態鏈接庫文件,這個native()方法就可以在Java中被訪問了。

    上述例子中,System.arrayCopy()和Array.newInsance(compnentType, length)均采用native方法,補充Array.newInstance()源碼如下:

    * @param componentType the Class object representing the
         * component type of the new array
         * @param length the length of the new array
         * @return the new array
         * @exception NullPointerException if the specified
         * componentType parameter is null
         * @exception IllegalArgumentException if componentType is {@link Void#TYPE}
         * @exception NegativeArraySizeException if the specified length 
         * is negative
         */
        public static Object newInstance(Class componentType, int length)
        throws NegativeArraySizeException {
        return newArray(componentType, length);
        }

    private static native Object newArray(Class componentType, int length)
        throws NegativeArraySizeException;

    Array.newInstance()的意義

    利用Array.newInstance()創建數組的意義是什麼?

    Java反射技術除了可以在運行時動態地決定要創建什麼類型的對象,訪問哪些成員變量,方法,還可以動態地創建各種不同類型,不同維度的數組。

    動態創建數組的步驟如下:
    1.創建Class對象,通過forName(String)方法指定數組元素的類型
    2.調用Array.newInstance(Class, length_of_array)動態創建數組

    訪問動態數組元素的方法和通常有所不同,它的格式如下所示,注意該方法返回的是一個Object對象
    Array.get(arrayObject, index)

    為動態數組元素賦值的方法也和通常的不同,它的格式如下所示, 注意最後的一個參數必須是Object類型
    Array.set(arrayObject, index, object)

    動態數組Array不單可以創建一維數組,還可以創建多維數組。步驟如下:
    1.定義一個整形數組:例如int[] dims= new int{5, 10, 15};指定一個三維數組
    2.調用Array.newInstance(Class, dims);創建指定維數的數組

    訪問多維動態數組的方法和訪問一維數組的方式沒有什麼大的不同,只不過要分多次來獲取,每次取出的都是一個Object,直至最後一次,賦值也一樣。

    動態數組Array可以轉化為普通的數組,例如:
    Array arry = Array.newInstance(Integer.TYPE,5);
    int arrayCast[] = (int[])array;

    public static void main(String args[]) throws Exception {
    Class classType = Class.forName("java.lang.String");
    // 創建一個長度為10的字符串數組
    Object array = Array.newInstance(classType, 10);
    // 把索引位置為5的元素設為"hello"
    Array.set(array, 5, "hello");
    // 獲得索引位置為5的元素的值
    String s = (String) Array.get(array, 5);
    System.out.println(s);
    }

    public static void main(String args[]) {
    int[] dims = new int[] { 5, 10, 15 };
    // 創建一個具有指定的組件類型和維度的新數組。
    Object array = Array.newInstance(Integer.TYPE, dims);
     
    // 取出三維數組的第3行,為一個數組
    Object arrayObj = Array.get(array, 3);
    Class cls = arrayObj.getClass().getComponentType();
    System.out.println(cls);
     
    // 取出第3行的第5列,為一個數組
    arrayObj = Array.get(arrayObj, 5);
    // 訪問第3行第5列的第10個元素,為其賦值37
    Array.setInt(arrayObj, 10, 37);
     
    // 動態數組和普通數組的轉換:強行轉換成對等的數組
    int arrayCast[][][] = (int[][][]) array;
    System.out.println(arrayCast[3][5][10]);
    }

    為什麼elementData 要聲明為transient變量

    public class ArrayList extends AbstractList
            implements List, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;
     
        /**
         * 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;

    序列化有2種方式:

    A、只是實現了Serializable接口。

    序列化時,調用java.io.ObjectOutputStream的defaultWriteObject方法,將對象序列化。

    注意:此時transient修飾的字段,不會被序列化。

    B、實現了Serializable接口,同時提供了writeObject方法。

    序列化時,會調用該類的writeObject方法。而不是java.io.ObjectOutputStream的defaultWriteObject方法。

    注意:此時transient修飾的字段,是否會被序列化,取決於writeObject
     /**
         * Save the state of the ArrayList instance to a stream (that
         * is, serialize it).
         *
         * @serialData The length of the array backing the ArrayList
         *             instance is emitted (int), followed by all of its elements
         *             (each an Object) in the proper order.
         */
        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();
     
            // Write out array length
            s.writeInt(elementData.length);
     
        // Write out all elements in the proper order.
        for (int i=0; i

    ArrayList是會開辟多余空間來保存數據的,而系列化和反序列化這些沒有存放數據的空間是要消耗更多資源的,所以ArrayList的數組就聲明為transient,自己實現write/readObject方法,僅僅系列化已經存放的數據


    import java.io.*;
    
    public class  Box  implements  Serializable  //要保存的對象類必須實現序列化接口serializable
    { 
        private int width; 
        private int height; 
        public void setWidth(int width){ 
            this.width  = width; 
        } 
        public void setHeight(int height){ 
            this.height = height; 
        } 
        public static void main(String[] args){ 
            Box myBox = new Box(); 
            myBox.setWidth(50); 
            myBox.setHeight(30); 
            try{  //序列化。
                FileOutputStream fs = new FileOutputStream("foo.ser"); 
                ObjectOutputStream os =  new ObjectOutputStream(fs); 
                os.writeObject(myBox); 
                os.close(); 
            }catch(Exception ex){ 
                ex.printStackTrace(); 
            } 
        }     
    } 
     //發序列化方法
    Public static void seserialize(string filename) throws Exception{
        // 反序列化(讀出保存的對象文件)
           ObjectInputStream in = new ObjectInputStream (new FileInputStream(filename));
           Box box = (Box) (in.readbject());
           System.out.println(box.toString());
           In.Closer();
    }
    為何要序列化,不是在內存中嗎

    ArrayList 實現了java.io.Serializable接口,在需要序列化的情況下,復寫writeObjcet和readObject方法提供適合自己的序列化方法。

    1、序列化是干什麼的?

    簡單說就是為了保存在內存中的各種對象的狀態(也就是實例變量,不是方法),並且可以把保存的對象狀態再讀出來。雖然你可以用你自己的各種各樣的方法來保存object states,但是Java給你提供一種應該比你自己好的保存對象狀態的機制,那就是序列化。

    2、什麼情況下需要序列化

    a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;

    b)當你想用套接字在網絡上傳送對象的時候;

    c)當你想通過RMI傳輸對象的時候;

    總結

    1.Arraylist基於數組實現,是自增長的

    2,非線程安全的

    3.插入時可能要擴容,刪除時size不會減少,如果需要,可以使用trimToSize方法,在查詢時,遍歷查詢,為null,判斷是否是null, 返回; 如果不是null,用equals判斷,返回

     /**
         * Returns true if this list contains the specified element.
         * More formally, returns true if and only if this list contains
         * at least one element e such that
         * (o==null ? e==null : o.equals(e)).
         *
         * @param o element whose presence in this list is to be tested
         * @return true if this list contains the specified element
         */
        public boolean contains(Object o) {
        return indexOf(o) >= 0;
        }
     
        /**
         * Returns the index of the first occurrence of the specified element
         * in this list, or -1 if this list does not contain the element.
         * More formally, returns the lowest index i such that
         * (o==null ? get(i)==null : o.equals(get(i))),
         * or -1 if there is no such index.
         */
        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;
        }

    4. 允許重復和 null 元素
















  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved