1. 程式人生 > >[Java8 Collection原始碼+演算法+資料結構]-List(二)

[Java8 Collection原始碼+演算法+資料結構]-List(二)

本人大二學生黨,最近研究JDK原始碼,順便複習一下資料結構與演算法的知識,所以就想寫這些系列文章。這是[Java Collection原始碼+演算法+資料結構]系列的第二篇。
上篇文章中,瞭解了Map的基本原理,現在我們來了解List家族。

簡述

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

List就是一個列表,可以儲存重複值,可以儲存null。且List介面繼承了Collection介面,下面看看Collection

The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The JDK does not provide any direct implementations of this interface: it provides implementations of more specific subinterfaces like Set

and List. This interface is typically used to pass collections around and manipulate them where maximum generality is desired.

Collection是類集框架的root,ListSet都是繼承Collection的,但是Collection介面本身沒什麼卵用。。所以還是繼續回到List裡面把。

ArrayList

ArrayList是我們用的最多的一個實現類,下面一起走進去看看原始碼
要點1:

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
*/
private int size; /** * 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

可以看到List採用陣列儲存資料,就和Array相對應咯,然後size屬性是儲存元素的個數(真正的有多少個元素,而不是elementData的大小,因為elementData.length為elementData佔用了多少的記憶體塊,可以有些沒有儲存物件,所以size表示真正的元素的個數)

要點2:

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

ListMap一樣(Map預設16, 2<<4),也是有預設的長度的,不夠的時候就會擴容。怎麼擴容呢?在每此向List裡面新增元素的時候,就會去檢查是否超出了容量。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

最終在grow裡面可以明確的看到卻是擴容了,
int newCapacity = oldCapacity + (oldCapacity >> 1);也就是擴大了1.5倍(那麼思考一哈:HashMap一次擴容多少呢?)。
但是擴容了就有一個問題—–會有多餘的空間,而這些空間卻沒有儲存物件。如果當記憶體不足的時候,就是一個麻煩的事啦,別怕。

    /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        //真正的元素的個數 < elementData的大小
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
              //在這裡呼叫Array.copyOf(T[] original, int newLength)之後會進行說明
        }
    }

這樣就會把那些多餘的空間給釋放掉咯。

要點3:
那麼List怎麼新增資料,修改資料,刪除資料呢?
下面進入程式碼裡面看看:

    //先檢查是否index越界,然後想陣列一樣直接取
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    //修改資料
    //先檢查是否index越界,然後存進去返回oldValue
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    //首先檢查容量是否足夠,然後再存進去
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        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);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;

    public boolean remove(Object o) {
        if (o == 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;
    }

上面的程式碼應該都不難,只有有一個函式需要注意:
System.arraycopy()Arrays.copyOf()。下面進去看一哈:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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;
    }

    /* @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. */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

copyOf方法裡面我們可以看到重新建立了一個copy陣列,然後再呼叫arraycopy方法。arraycopy方法還是很好理解的,是一個native方法。copyOf方法可以看成arraycopy的一個封裝。所以我們只需要瞭解copyOf方法。下面看看怎麼用的:

    @Test
    public void testCopy() {
        int[] arr = {1, 2, 3, 4, 5};

        int[] copied = Arrays.copyOf(arr, 10); //產生了一個新的陣列
        System.out.println(Arrays.toString(copied));

        copied = Arrays.copyOf(arr, 3);
        System.out.println(Arrays.toString(copied));
    }

輸出:

[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
[1, 2, 3]

LinkedList

LinkedList其實是一個雙向連結串列,但是由於實現了Deque介面,可以看作佇列,也可以看出棧。

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

下面看看佇列是怎麼實現的:
Queue
- offer() 入佇列
- poll() 出佇列,然後刪除對首元素
- peek() 取對首元素,但不刪除對收元素
- element() 取對首元素,與peek()的區別就是element()不能返回null

Stack
- push()壓棧
- pop()出棧(刪除)
- peek()出棧(不刪除)
- element()出棧(不刪除,而且不能為null)

下面一起看看這些方法:

     public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    public E element() {
        return getFirst();
    }
     public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
     public boolean offer(E e) {
        return add(e);
    }
     public void push(E e) {
        addFirst(e);
    }
    public E pop() {
        return removeFirst();
    }

還是呼叫的其他方法:

    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

    /**
     * Links e as first element.
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

到頭還是2個方法,在連結串列的頭部和連結串列的尾部進行操作,沒什麼好說的啦。

比較

首先看看ArrayList的簽名:

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

哦豁!RandomAccess是什麼鬼?

/**
 * 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.
 **/
public interface RandomAccess {
}

這是一個標記介面,和Serializable一樣,實現這個介面代表可以實現fast (generally constant time) random access。主要目的就是幫助演算法改進其行為。

if(list instanceof RandomAccess){
    for (int i=0, n=list.size(); i++)
          list.get(i);
}
else{
    for (Iterator i=list.iterator(); i.hasNext(); )
          i.next();
}

對於實現RandomAccess介面的,上面的遍歷比下面的快,所以可以通過instanceof關鍵字來判斷使用那種方法遍歷。
參考文獻
所以對於ArrayList使用for迴圈變數最快
LinkedList使用foreach或者iterator遍歷快
HashMap使用EntrySet+foreach遍歷最快啦)

郵箱:[email protected]

相關推薦

[Java8 Collection原始碼+演算法+資料結構]-List()

本人大二學生黨,最近研究JDK原始碼,順便複習一下資料結構與演算法的知識,所以就想寫這些系列文章。這是[Java Collection原始碼+演算法+資料結構]系列的第二篇。 上篇文章中,瞭解了Map的基本原理,現在我們來了解List家族。 簡述

查詢演算法 淺談演算法資料結構: 七 叉查詢樹 淺談演算法資料結構: 十一 雜湊表

閱讀目錄 1. 順序查詢 2. 二分查詢 3. 插值查詢 4. 斐波那契查詢 5. 樹表查詢 6. 分塊查詢 7. 雜湊查詢   查詢是在大量的資訊中尋找一個特定的資訊元素,在計算機應用中,查詢是常用的基本運算,例如編譯程式中符號表的查詢。本文

資料結構):演算法及其描述

一、演算法及其描述 1、什麼是演算法 資料元素之間的關係有邏輯關係和物理關係,對應的操作有邏輯結構上的操作功能和具體儲存結構上的操作實現。 把 具體儲存結構上的操作實現方法 稱為演算法。 確切地說,演算法是對特定問題求解步驟的一種描述,它是指令的有限序列,其中每一

資料結構)LinkedList原始碼分析

一、基本概念 1、關係圖: public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, C

基礎演算法資料結構)字首、中綴、字尾表示式

目錄 簡介 字首表示式計算 字尾表示式計算 簡介 中綴表示式(正常的表示式) \[ (1+2)*3-4 \] 字首表示式(運算子位於運算元之前) \[ -*+1234 \] 字尾表示式(運算子位於運算元之後) \[ 12+3*4- \] 字首表示式計算 從右向左遍歷,

演算法資料結構叉樹查詢

目錄 概要 樹的介紹 二叉樹的介紹 二叉查詢樹的C實現 1. 節點定義 2 遍歷 3. 查詢 4. 最大值和最小值 5. 前驅和後繼 6. 插入 7. 刪除 8. 列印 9. 銷燬二叉樹 完整的實現程式碼 二叉查詢樹的C測試程式 下面對

python演算法資料結構013--叉樹的實現及按先序,後序,中序遍歷的遞迴實現

二叉樹的深度優先遍歷: (可以用遞迴或者堆疊實現) 先序:根節點->左子樹->右子樹 中序: 左子樹->根節點->右子樹 後序:左子樹->右子樹->根節點 二叉樹按廣度優先遍歷:從上到下,從左到右遍歷按層次遍歷(利用佇列實現) cl

資料結構叉查詢樹Java實現原始碼及註釋

二叉查詢樹(Binary Search Tree),(又:二叉搜尋樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別為二叉排序樹。以下是樓主用jav

資料結構叉樹一些基本演算法

二叉樹中搜索某個元素的演算法。 /** * 查詢二叉樹中元素 * @param root * @param data * @return */ Boolean FinadIn

資料結構叉樹的構建及遍歷(遞迴演算法

題目描述: 編一個程式,讀入使用者輸入的一串先序遍歷字串,根據此字串建立一個二叉樹(以指標方式儲存)。 例如如下的先序遍歷字串: ABC##DE#G##F### 其中“#”表示的是空格,空格字元代表空樹。建立起此二叉樹以後,再對二叉樹進行中序遍歷,輸出遍歷結果。 具體程式

C語言基本資料結構叉樹的三種遍歷,節點數以及深度演算法

關於二叉樹的定義,網上有比較好的介紹,在這裡就簡單介紹二叉樹的一些性質 二叉樹的基本性質 1)二叉樹的第i層上至多有 2^(i-1)(i ≥1)個結點; 2)深度為 h 的二叉樹中至多含有 2^h – 1 個結點; 3)若在任意一棵二叉樹中,有 n0 個葉子結點,有 n2

資料結構叉樹演算法題思路

首先需要做到掌握三種常規遍歷(前、中、後)以及按層遍歷,幾乎所有的演算法題都逃不開這三種方法。 其次,做二叉樹題目,很多情況都可以使用遞迴的方法來做,要經常想這個。 舉例: 1. 二叉樹映象問題 2. 二叉樹找子樹問題

演算法資料結構) 基於連結串列的佇列

基於連結串列的佇列 一個數據的集合如果以連結串列來儲存,那麼它的容量就是無限的。 實現的過程中,需要注意連結串列為空的情況下,需要對頭引用和尾引用做特殊處理。 實現程式碼 /** * Created by 18855127160 on 2

資料結構叉樹遍歷的遞迴演算法

        二叉樹是資料結構這門課程中非常重要的知識點,也是最基本的一種樹形結構。在二叉樹的遍歷又是這部分內容的重中之重,那麼今天就這部分內容和大家做一個分享。所謂二叉樹遍歷,就是按照某種特定的次序,遍訪整個二叉樹中的每個結點,使得每個結點被訪問一次,而且只訪問一次。

Redis系列(五):資料結構List雙向連結串列中基本操作操作命令和原始碼解析

1.介紹 Redis中List是通過ListNode構造的雙向連結串列。 特點: 1.雙端:獲取某個結點的前驅和後繼結點都是O(1) 2.無環:表頭的prev指標和表尾的next指標都指向NULL,對連結串列的訪問都是以NULL為終點 3.帶表頭指標和表尾指標:獲取表頭和表尾的複雜度都是O(1) 4.帶連結串

資料結構——線索叉樹(程式碼)

線索二叉樹 C++ 環境codeblocks17 通過 /* 線索二叉樹 @CGQ 2018/10/29 */ #include <iostream> #include <stdio.h> #include <stdlib.h> using namesp

資料結構

***********************特殊的線性表-------棧**************************** 棧: 先進後出、後進先出 棧的插入運算 叫做入棧 棧的刪除運算 叫做出棧 演示程式碼: package com.chapter11; //棧的介面public int

資料結構叉樹的相關操作(待更)

#include "stdio.h" #include "stdlib.h" typedef struct node { char data; struct node *rchild,*lchild; }bintnode; typedef bintnode *bintree;//指向該結構體

python 資料結構 list(3)

list解析 先看下面的例子,這個例子是想得到1到9的每個整數的平方,並且將結果放在list中打印出來 >>> power2 = [] >>> for i in range(1,10): ... power2.append(i*i) .

資料結構 互換叉樹中所有結點的左右子樹 C

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!