[java學習]java容器源碼初探(1)

來源:互聯網
上載者:User

[java學習]java容器源碼初探(1)
一、動態數組ArrayList

在我們開發人員眼中,這就是一個“動態數組”,可以“動態”地調整數組的大小,雖然說數組從定義了長度後,就不能改變大小。
實現“動態”調整的基本原理就是:按照某個調整策略,重新建立一個調整後一樣大小的數組,然後將原來的數組賦值回去。
下面我們來解析一下幾個與數組不一樣的方法。

看看ArrayList中主要的幾個欄位(源碼剖析):

    // 預設的初始數組大小    private static final int DEFAULT_CAPACITY = 10;    // 空數組的表示    private static final Object[] EMPTY_ELEMENTDATA = {};    // 資料儲存的數組,主體就是它    private transient Object[] elementData;    // 數組元素個數(不一定等於數組的length)    private int size;

添加元素(源碼剖析):

public boolean add(E e) {    // 判斷數組需不需要擴容,需要就擴容(動態調整數組大小的關鍵方法)    ensureCapacityInternal(size + 1);    // 數組元素賦值,數組元素個數+1    elementData[size++] = e;    return true;}public void add(int index, E element) {    // 監測是否是數組越界    rangeCheckForAdd(index);    // 判斷數組需不需要擴容,需要就擴容(動態調整數組大小的關鍵方法)    ensureCapacityInternal(size + 1);    // index和index的元素向後移動     System.arraycopy(elementData, index, elementData, index + 1, size - index);    // index位置上賦值    elementData[index] = element;    // 數組元素+1    size++;}

刪除元素,這裡注意,並沒有真正的縮小數組的大小,只是修改size的值(源碼剖析):

public E remove(int index) {    // 數組越界檢查,會拋出異常哦    rangeCheck(index);    // 記錄修改次數,在迭代器那裡會有用    modCount++;    // 找到要刪除的資料    E oldValue = elementData(index);    // 需要移動的元素個數    int numMoved = size - index - 1;    if (numMoved > 0)    // 複製數組,移動元素    System.arraycopy(elementData, index+1, elementData, index,numMoved);    // 移除引用,方便GC回收    elementData[--size] = null;    return oldValue;}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 {        // 同樣是遍曆數組,尋找        for (int index = 0; index < size; index++)            if (o.equals(elementData[index])) {                // 找到,刪除                fastRemove(index);                return true;            }    }    // 找不到    return false;}// 與E remove(int index)的差不多,我就不多說了。// 這裡我就不明白,remove(int index)和fastRemove(int index)明明可以代碼複用,為何不複用代碼呢?private void fastRemove(int index) {    modCount++;    int numMoved = size - index - 1;    if (numMoved > 0)        System.arraycopy(elementData, index+1, elementData, index, numMoved);    // 置null,讓GC可以回收它    elementData[--size] = null;}

看看最重要的數組擴容方法(源碼剖析):

    private void ensureCapacityInternal(int minCapacity) {        // 判斷數組是否為空白數組        if (elementData == EMPTY_ELEMENTDATA) {            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);        }        ensureExplicitCapacity(minCapacity);    }    private void ensureExplicitCapacity(int minCapacity) {        // 修改次數+1        modCount++;        // 判斷需不需要擴容:比較數組大小和需要擴容的大小        if (minCapacity - elementData.length > 0)            grow(minCapacity);    }    // 增加容量開始    private void grow(int minCapacity) {        int oldCapacity = elementData.length;        // x1.5倍        int newCapacity = oldCapacity + (oldCapacity >> 1);        if (newCapacity - minCapacity < 0)            // 擴容的大小太小了            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            // 擴容的大小不會超過最大容量Integer.MAX_VALUE-8            newCapacity = hugeCapacity(minCapacity);        // 數組複製        elementData = Arrays.copyOf(elementData, newCapacity);    }

ArrayList被稱為“動態數組的原理”,大致就如源碼解析中注釋。裡面還有其他數組操作的方法,這裡就不多講,其實思路也是差不多的,一切以源碼為準。

二、鏈表隊列LinkedList

(雙向)鏈表+隊列(Deque)。以前我一直以為LinkedList只是個鏈表而已,沒想到它可以當隊列用。

看看結點代碼,一看就知道是雙向的結點(源碼):

    private static class Node {        E item;        Node next; // 後繼節點        Node prev; // 前置節點        Node(Node prev, E element, Node next) {            this.item = element;            this.next = next;            this.prev = prev;        }    }

幾個重要欄位(源碼解析):

    // transient:不被序列化    // 結點個數    transient int size = 0;    // 第一個結點    transient Node first;    // 最後一個結點    transient Node last;

簡單的看看鏈表的代碼(源碼解析):

    // 添加結點    public void add(int index, E element) {        // 檢查index是否越界        checkPositionIndex(index);        if (index == size)            // 添加最後一個            linkLast(element);        else            // 添加到原index結點前面            linkBefore(element, node(index));    }    // 刪除結點    public E remove(int index) {        // 檢查index是否越界        checkElementIndex(index);        // 解除結點連結        return unlink(node(index));    }    // 尋找結點    public E get(int index) {        // 檢查越界了沒        checkElementIndex(index);        // 開始尋找        return node(index).item;    }    // “真正”是使用這個方法來尋找    Node node(int index) {        // 折半尋找~,果然是會利用雙向鏈表的優點        if (index < (size >> 1)) { // 從前面找找前半邊            Node x = first;            for (int i = 0; i < index; i++)                x = x.next;            return x;        } else { // 從後面找找後半邊            Node x = last;            for (int i = size - 1; i > index; i--)                x = x.prev;            return x;        }    }

簡單的看看隊列的代碼(源碼解析):

    // 入隊    public boolean offer(E e) {        return add(e);    }    // 也是入隊    public boolean add(E e) {        // 接到最後一個結點        linkLast(e);        // 個人覺得這個return true沒必要,完全可以無傳回值void,以為如果入隊失敗linkLast會拋異常。既然是拋異常了,就不會return false了,那就沒必要return true。        return true;    }    // “溫柔地”取隊頭元素,隊為空白不會拋出異常,只會返回null    public E peek() {        final Node f = first;        return (f == null) ? null : f.item;    }    // “粗暴地”取隊頭元素,隊為空白拋出NoSuchElementException異常    public E element() {        return getFirst();    }    // “溫柔地”出隊,隊為空白不會拋出異常,只會返回null    public E poll() {        final Node f = first;        return (f == null) ? null : unlinkFirst(f);    }    // “粗暴地”出隊,隊為空白拋出NoSuchElementException異常    public E remove() {        return removeFirst();    }
三、安全的動態數組Vector

加鎖版的ArrayList(安全執行緒)。方法實現與ArrayList差不多,不同的是線程不安全的方法都加上鎖(synchronized)了,這裡就不講啦。
一般不使用Vector而使用ArrayList,除非在多線程環境下(萬不得已)才使用Vector。

四、棧Stack

棧,父類是Vector,所以你懂的(加鎖版動態數組)。既然父類“安全”了,作為子類的棧也要“安全”了(加鎖!),這麼說棧也不是很快嘛~。
Stack規定數組最後一個元素為棧頂元素。

入棧(源碼解析):

    // 不加鎖?父類Vector的addElement已經加鎖了,所以不加多餘的鎖。    public E push(E item) {        // 添加元素到數組最後一個元素的後面        addElement(item);        // 返回壓入棧的元素        return item;    }

出棧(源碼解析):

    // 出棧    public synchronized E pop() {        E obj;        // 棧中元素個數        int len = size();        // 取棧頂元素        obj = peek();        // 移除最後的一個元素(就是移除棧頂元素啦)        removeElementAt(len - 1);        // 返回移除的棧頂元素        return obj;    }    // 取棧頂元素    public synchronized E peek() {        int len = size();        if (len == 0)            // 空棧異常            throw new EmptyStackException();            // 取數組最後一個元素        return elementAt(len - 1);    }

棧中元素尋找,從棧頂(最後一個數組元素)到棧底(第一個數組元素)(源碼解析):

    public synchronized int search(Object o) {        // 找到最後一個符合要求的元素        int i = lastIndexOf(o);        if (i >= 0) {            // 找到了            return size() - i;        }        // 沒找到        return -1;    }
五、優先隊列PriorityQueue

PriorityQueue實現了優先隊列(預設最小堆)。
實現優先隊列的痛點:需要調整堆。
調整堆的方法有:上濾和下濾。

幾個重要欄位(源碼解析):

    // 預設的初始化容器大小    private static final int DEFAULT_INITIAL_CAPACITY = 11;    // 元素數組    private transient Object[] queue;    // 元素個數    private int size = 0;    // 比較子(可拓展),可以為空白,為空白的話E就必須實現Comparable介面。    private final Comparator comparator;    // 修改次數    private transient int modCount = 0;

添加元素(入隊)(源碼解析):

    public boolean add(E e) {        return offer(e);    }    public boolean offer(E e) {        if (e == null)            throw new NullPointerException();        // 修改次數+1        modCount++;        int i = size;        // 判斷是否應該擴充數組        if (i >= queue.length)        // 數組擴容            grow(i + 1);        // 數組元素增加1        size = i + 1;        if (i == 0)        // 第一個入隊的元素            queue[0] = e;        else        // 因為有元素加入,需要調整堆(從下到上):上濾            siftUp(i, e);        return true;    }

我們來看看調整堆的方法(源碼解析):

上濾(源碼解析):

    // 選擇比較子上濾還是Comparable對象的上濾    private void siftUp(int k, E x) {        if (comparator != null)            // 優先選擇有比較子的進行上濾            siftUpUsingComparator(k, x);        else            // Comparable對象的上濾            siftUpComparable(k, x);    }    // Comparable對象的上濾    private void siftUpComparable(int k, E x) {        // 強制轉換成Comparable對象,這樣才能有下面的比較        Comparable key = (Comparable) x;        while (k > 0) {            // k是子結點在數組中的下標,(k-1)/2可以求出父結點下標            int parent = (k - 1) >>> 1;            // 取得父結點            Object e = queue[parent];            // 如果key大於或等於父節點            if (key.compareTo((E) e) >= 0)                break;            // key小於父結點            // 父結點與子結點交換值            queue[k] = e;            // k跑到父結點的位置            k = parent;        }        // 賦值到合適位置        queue[k] = key;    }    // 使用比較子的上濾(解析和siftUpComparable一樣)    private void siftUpUsingComparator(int k, E x) {        while (k > 0) {            int parent = (k - 1) >>> 1;            Object e = queue[parent];            if (comparator.compare(x, (E) e) >= 0)                break;            queue[k] = e;            k = parent;        }        queue[k] = x;    }

下濾(源碼解析):

   // 下濾(和上濾一樣有兩次方式)   private void siftDown(int k, E x) {        if (comparator != null)            siftDownUsingComparator(k, x);        else            siftDownComparable(k, x);    }    // Comparable對象的下濾    private void siftDownComparable(int k, E x) {        Comparable key = (Comparable)x;        // 計算出非葉子結點個數        int half = size >>> 1;        while (k < half) {            // 計算出左孩子的位置            int child = (k << 1) + 1;            // 取得左孩子的對象            Object c = queue[child];            // 右孩子的位置            int right = child + 1;            // 如果右孩子存在,並且左孩子大於右孩子            if (right < size &&                ((Comparable) c).compareTo((E) queue[right]) > 0)                // 選擇右孩子作為與父結點的比較對象                c = queue[child = right];            // 父子結點比較            if (key.compareTo((E) c) <= 0)                // 如果父節點小於或等於子結點,則找到要調整的位置                break;            // 子結點值賦值到父結點            queue[k] = c;            // 換子結點進入下次迴圈的比較            k = child;        }        // 找到調整的位置,賦值        queue[k] = key;    }    // 使用比較子的下濾(解析和siftDownComparable一樣)    private void siftDownUsingComparator(int k, E x) {        int half = size >>> 1;        while (k < half) {            int child = (k << 1) + 1;            Object c = queue[child];            int right = child + 1;            if (right < size &&                comparator.compare((E) c, (E) queue[right]) > 0)                c = queue[child = right];            if (comparator.compare(x, (E) c) <= 0)                break;            queue[k] = c;            k = child;        }        queue[k] = x;    }

總結:閱讀一下java容器包中的類後,對資料結構的瞭解又加深了一層,確實源碼裡的都是“好東西”。這裡的講的類還不全面,未完持續!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.