1. 程式人生 > >ArrayList原始碼解析(二)

ArrayList原始碼解析(二)

歡迎轉載,轉載煩請註明出處,謝謝。

https://www.cnblogs.com/sx-wuyj/p/11177257.html

自己學習ArrayList原始碼的一些心得記錄.

繼續上一篇,ArrayList原始碼解析(一)

  • addll(Collection<? extends E> c) :新增目標集合到原有集合中.

     //引數需要是Collection的子類
        public boolean addAll(Collection<? extends E> c) {
            //將C集合通過toArray()方法轉為陣列
            Object[] a = c.toArray();
            //拿到傳入陣列的長度
            int numNew = a.length;
            //對底層陣列進行擴容
            ensureCapacityInternal(size + numNew);  // Increments modCount
            //複製陣列
            System.arraycopy(a, 0, elementData, size, numNew);
            //將size更新為size+numNew
            size += numNew;
            //返回boolean
            return numNew != 0;
        }

    ensureCapacityInternal(size + numNew);

    關於這個方法,在上一篇部落格當中有詳細解釋,可以去看看.

  • add(int index, Collection<? extends E> c):新增目標集合到原有集合指定位置

    public boolean addAll(int index, Collection<? extends E> c) {
            //檢驗index
            rangeCheckForAdd(index);
            //將C集合通過toArray()方法轉為陣列
            Object[] a = c.toArray();
            //拿到傳入陣列的長度
            int numNew = a.length;
            //對底層陣列進行擴容
            ensureCapacityInternal(size + numNew);  
            //計算需要複製的長度
            int numMoved = size - index;
            //需要複製的長度如果大於0,通過arraycopy方法複製
            if (numMoved > 0)
                System.arraycopy(elementData, index, elementData, index + numNew,
                                 numMoved);
            //將引數陣列複製到底層陣列index位置.
            System.arraycopy(a, 0, elementData, index, numNew);
            //修改size的值  為size+numNew
            size += numNew;
            //返回boolean
            return numNew != 0;
        }

    其實addAll()方法只要理解擴容機制,其他的內容很簡單.但是這個方法需要注意的就是根據index去插入的時候.

    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
    
    System.arraycopy(a, 0, elementData, index, numNew);

    看見原始碼,可能會想為什麼要複製兩次呢?第一個判斷的時候,其實還有另一層含義:判斷插入位置是否在底層陣列中間,如果是則需要將插入的位置之後的元素向後移動,也就是複製一個新的陣列

    第二次的複製才是將兩個數組合為一個數組,也就是底層陣列.

  • get(int index):獲取指定位置的元素

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

    get方法大家都不陌生,原始碼也很簡單,或許對rangeCheck()方法比較陌生.

     private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    僅此而已,我們傳入的index最大值不能超過size,這個很好理解.

  • set(int index, E element):修改指定位置的元素

    public E set(int index, E element) {
            rangeCheck(index);
    
            E oldValue = elementData(index);
            elementData[index] = element;
            return oldValue;
        }

    set方法的原理其實就是將底層陣列indxe的元素修改傳入的元素,至於傳入的引數為什麼用泛型也很好理解,我們可以想一下我們工作學習中集合中元素的型別,各種各樣,所以在這個地方使用泛型的好處不言而喻.

    還有一點就是set方法是有返回值的,我之前是沒有注意到的,返回值就是被修改的那個元素.

  • size():集合的大小

    public int size() {
            return size;
        }
  • indexOf(object o):集合中某個元素第一次出現的位置

     //集合中某個元素第一次出現的位置
        public int indexOf(Object o) {
            //判斷傳入o是否為null
            if (o == null) {
                //如果為null,迴圈遍歷尋找為null的索引位置
                for (int i = 0; i < size; i++)
                    //因為傳入的是一個物件,為null使用==
                    if (elementData[i]==null)
                        //返回出現的位置
                        return i;
            } else {
                //如果不為null,使用equals尋找傳輸物件位置
                for (int i = 0; i < size; i++)
                    if (o.equals(elementData[i]))
                        //找到返回物件位置
                        return i;
            }
            //找不到物件情況下返回-1
            return -1;
        }

    這個方法需要注意就是,如果傳入null的時候是需要用==去尋找的.

  • lastIndexOf(Object o):返回元素最後一次出現的位置

    public int lastIndexOf(Object o) {
          //判斷傳入o是否為null
            if (o == null) {
                //如果為null,迴圈遍歷尋找為null的索引位置
                //這裡是倒序遍歷
                for (int i = size-1; i >= 0; i--)
                    if (elementData[i]==null)
                        return i;
            } else {
                //如果不為null,使用equals尋找傳輸物件位置
                //同樣是倒序遍歷
                for (int i = size-1; i >= 0; i--)
                    if (o.equals(elementData[i]))
                        return i;
            }
            return -1;
        }

    其實indeOf()和lastIndexOf()兩個方法的思路是一樣的,區別就在於一個為正序遍歷,一個倒序遍歷.

  • clone():克隆

    public Object clone() {
            try {
                //呼叫了父類也就是Object的clone.
                ArrayList<?> v = (ArrayList<?>) super.clone();
                //集合V的底層陣列複製一個原集合底層陣列
                v.elementData = Arrays.copyOf(elementData, size);
                v.modCount = 0;
                //返回V集合
                return v;
            } catch (CloneNotSupportedException e) {
               // this shouldn't happen, since we are Cloneable
                throw new InternalError(e);
            }
        }

    這個方法我個人認為是不是返回值可以為ArrayList,從原始碼可以看出來返回去v是ArrayList.但是ArrayList的頂級父類也是Object.所以這個也不算錯.

    try-catch中的註釋意思是 這是不可能發生的,因為我們是克隆的

  • toArray():將集合裝換為Object型別陣列

    public Object[] toArray() {
            return Arrays.copyOf(elementData, size);
        }

    其實就是將底層陣列複製一個返回去,底層呼叫的依然還是呼叫了System.arraycopy()這個方法,這裡不展開說.

  • toArray(T[] a):將集合裝換為指定型別陣列

     public <T> T[] toArray(T[] a) {
            //如果引數陣列的長度小於size,那麼直接轉換為引數陣列型別
            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);
            //如果引數陣列長度大於size
            //那麼返回去的陣列[size]為null
            if (a.length > size)
                a[size] = null;
            return a;
        }

    這兩個過載方法的區別就是,第一個會返回一個Object型別的陣列,第二個會返回一個指定型別的陣列.

  • remove(int index):刪除指定位置的元素

     public E remove(int index) {
            //校驗
            rangeCheck(index);
            modCount++;
            //被刪除的元素
            E oldValue = elementData(index);
            //需要被複制的元素個數
            int numMoved = size - index - 1;
            //如果被複制的元素個數大於0,那麼就複製一個數組
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            //將底層陣列最後一個元素設定為null
            //讓GC開始工作
            elementData[--size] = null;
            //返回被刪除的元素
            return oldValue;
        }

    如果被複制的元素葛素小於或者等於0,那麼意味著刪除的是末尾的元素,不需要變更元素位置.

  • remove(Object o):刪除指定元素

     public boolean remove(Object o) {
            //判斷需要刪除的物件是否為null
            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;
                    }
            }
            //如果沒有找到返回false
            return false;
        }

    同樣是過載,其中fastRemove()方法和remove()邏輯基本一致,只是沒有返回值.並且被private修飾,是一個私有方法.

      private void fastRemove(int index) {
            modCount++;
            // //需要被複制的元素個數
            int numMoved = size - index - 1;
            //如果被複制的元素個數大於0,那麼就複製一個數組
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            //將底層陣列最後一個元素設定為null
            //讓GC開始工作
            elementData[--size] = null; // clear to let GC do its work
        }
  • clean():清楚集合內部元素,成為一個空集合

     public void clear() {
            modCount++;
    
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
                elementData[i] = null;
    
            size = 0;
        }

    這個應該一目瞭然了吧,就是迴圈遍歷底層陣列,將底層陣列所有的元素設定為null,並將size修改為0;

個人的一些簡單理解.還有一部分方法沒有寫.時間和篇幅有限,先寫到這裡,之後再繼續更新.

如果在閱讀的過程中由.於本人水平有限帶來的誤導,希望大佬可以批評指正.共同進