1. 程式人生 > >資料結構之List實現類

資料結構之List實現類

目錄

1.Arraylist

2.LinkedList

3.Vector

4.Stack


1.Arraylist

Arraylist作為常用的資料容器,還是有必要知道一些內部的細節。從執行緒安全方面來看,Arraylist是非執行緒安全,假設10個執行緒同時執行,往Arraylist新增100條資料,有可能出現Arraylist最終的資料總和會小於1000,所以開發中要注意,可以通過鎖去解決。而Vector就是執行緒安全的,不過Vector也有自身的缺點,如果儲存大量的資料,Vector會消耗的資源較大,或者做插入、刪除、resize等操作都有可能導致Vector的迭代器失效,總之各有優點。

Arraylist內部,就先從構造方法看起:

//內部儲存資料,數量變化到一定程度去操作的陣列
transient Object[] elementData;

//資料個數
private int size;

//預設初始化容量為10
private static final int DEFAULT_CAPACITY = 10;

//預設空陣列
private static final Object[] EMPTY_ELEMENTDATA = {};

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //指定大小的初始化
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //使用預設空陣列
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //容量大小 < 0時候丟擲的異常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

   
public ArrayList() {
       //無參建構函式,初始化一個空的陣列
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

當我們在初始化Arraylist時,如未指定大小,儲存資料的陣列會設定預設大小為10。後面當儲存資料個數大於10時,Arraylist的儲存陣列會指向新陣列,且新陣列的大小為舊陣列大小的1.5倍。後面大小如接著超過,會重複這個操作,想想資料量大的時候,使用Arraylist確實會影響效能。下面看看擴容的方法:

private void grow(int minCapacity) {
        // 舊陣列數量和擴容後的數量
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        //新容量比期望的最小容量小,將期望的最小容量作為當前容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

        //新容量大於規定最大值,呼叫大的“擴容”方法hugeCapacity
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

        //舊的數量容量變大,原資料存入,索引不變
        elementData = Arrays.copyOf(elementData, newCapacity);
    }


 private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();

        //最小期望大小如大於MAX_ARRAY_SIZE,設定新容量為(2^31-1)
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

因為內部是陣列,所以保留了索引的概念,因為add和remove和get等方法能很好理解,主要就是add函式多次呼叫到一定數量會觸發"擴容"。

2.LinkedList

LinkedList是一種連結串列的結構,看看其內部的一個私有類Node,正式這些內部的node節點類組成了連結串列的形式

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
  
/**
     * Pointer to first node.
     * 指向第一個node
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * 指向最後一個lode
     */
    transient Node<E> last;  

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
  }

......
}

單看一個node類節點。item變數就是本節點值,prev就是前一個節點值,next即為下一個節點值。這樣就形成了一條節點關聯起來的鏈式結構。

LinkedList有一些特殊的函式,比如:

public void addFirst(E e) {
        linkFirst(e);
    }

  
public void addLast(E e) {
        linkLast(e);
}

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);
}

看名字估計也能知道,就是直接在連結串列頭、尾新增或者刪除資料。

3.Vector

Vector為向量佇列,內部儲存資料也是陣列,採用的儲存方式類似於LinkedList的"擴容",先看看它的4個構造方法:

//capacity是Vector的預設容量大小,capacityIncrement是每次Vector容量增加時的增量值。
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
}


//指定Vector的容量大小
public Vector(int initialCapacity) {
        this(initialCapacity, 0);
}


//構造一個空的向量,內部資料陣列大小為10
public Vector() {
    this(10);
}

//構造一個包含指定元素元素的向量集合,按照集合的順序返回
public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

其空參構造方法,預設建立容量為10的陣列。同時也可以指定大小,主要看看capacityIncrement這個引數,解釋為"擴容增量",可以先看看下面的程式碼:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;

        //擴容增量的設定與否影響擴容後的大小
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
}

可以看到如無設定capacityIncrement,容器數量個數會增長為當前容器數量的一倍。

內部常見函式有:

轉載這個老哥的方法整理,有點多:https://www.cnblogs.com/zedosu/p/6662231.html

synchronized boolean        add(E object)
             void           add(int location, E object)
synchronized boolean        addAll(Collection<? extends E> collection)
synchronized boolean        addAll(int location, Collection<? extends E> collection)
synchronized void           addElement(E object)
synchronized int            capacity()
             void           clear()
synchronized Object         clone()
             boolean        contains(Object object)
synchronized boolean        containsAll(Collection<?> collection)
synchronized void           copyInto(Object[] elements)
synchronized E              elementAt(int location)
             Enumeration<E> elements()
synchronized void           ensureCapacity(int minimumCapacity)
synchronized boolean        equals(Object object)
synchronized E              firstElement()
             E              get(int location)
synchronized int            hashCode()
synchronized int            indexOf(Object object, int location)
             int            indexOf(Object object)
synchronized void           insertElementAt(E object, int location)
synchronized boolean        isEmpty()
synchronized E              lastElement()
synchronized int            lastIndexOf(Object object, int location)
synchronized int            lastIndexOf(Object object)
synchronized E              remove(int location)
             boolean        remove(Object object)
synchronized boolean        removeAll(Collection<?> collection)
synchronized void           removeAllElements()
synchronized boolean        removeElement(Object object)
synchronized void           removeElementAt(int location)
synchronized boolean        retainAll(Collection<?> collection)
synchronized E              set(int location, E object)
synchronized void           setElementAt(E object, int location)
synchronized void           setSize(int length)
synchronized int            size()
synchronized List<E>        subList(int start, int end)
synchronized <T> T[]        toArray(T[] contents)
synchronized Object[]       toArray()
synchronized String         toString()
synchronized void           trimToSize()

4.Stack

稱之為棧,本身繼承自Vector,有著資料先進後出的特性。

內部儲存也是由陣列實現

protected Object[] elementData;

常用的幾個方法:

//push:將元素新增到陣列的末尾
public E push(E item) {
        addElement(item);
        return item;
}


//加了鎖,可以看出push新增資料也執行緒安全的
public synchronized void addElement(E obj) {
        //數量+1,下個索引資料指向引數傳遞的資料
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
}
//peek:取出棧頂元素,不執行刪除
public synchronized E peek() {
        int     len = size();
        if (len == 0)
            throw new EmptyStackException();
        //返回指定索引資料
        return elementAt(len - 1);
}




//pop時:取出棧頂元素,並將該元素從棧中刪除
  public synchronized E pop() {
        E       obj;
        int     len = size();
        //拿到最後一個數據
        obj = peek();
        //刪除最後一個數據
        removeElementAt(len - 1);
        return obj;
}

5.hashmap

hashmap內部是16位的陣列,陣列中儲存的每一個元素又是連結串列的表頭,表頭這個節點是個Entry型別。

final K key;
V value;
final int hash;
HashMapEntry<K, V> next;

上圖看出Entry中包含鍵對值中的key和value,next指向的是下一個節點Entry。hash作用主要用來計算值儲存的位置,公式為:

hash(key)%len。將hash(key)值對len,也就是16取餘。

轉:https://www.cnblogs.com/huozhong/p/5896077.html