1. 程式人生 > >ArrayList、Vector和LinkedList

ArrayList、Vector和LinkedList

否則 表示 rec 大小 一次 最大值 array length net

List接口特點

1、有序的 collection。

2、可以對列表中每個元素的插入位置進行精確地控制。

3、可以根據元素的索引訪問元素,並搜索列表中的元素。

4、列表通常允許重復的元素。

5、允許存在 null 元素。

6、實現List接口的常用類有LinkedList、ArrayList、Vector和Stack。

ArrayList特點

1、java.util.ArrayList<E> : List 接口的大小可變數組的實現類

2、ArrayList 內部基於 數組 存儲 各個元素。

3、所謂大小可變數組,是指當 數組容量不足以存放新的元素時,創建新數組,並將原數組中的內容復制過來。

4、源碼分析

//elementData中已存放的元素的個數,註意:不是elementData的容量
private int size;
//elementData的默認容量為10
private static final int DEFAULT_CAPACITY = 10;
//對象數組:ArrayList的底層數據結構,transient表示該字段不進行序列化操作
transient Object[] elementData;
//實例化一個空數組
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//實例化一個空數組
private
static final Object[] EMPTY_ELEMENTDATA = {}; protected transient int modCount = 0; @Native public static final int MAX_VALUE = 0x7fffffff; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** *指定初始容量的有參構造 */ public ArrayList(int initialCapacity) { //如果初始容量大於0就,對象數組就指向到一個新的數組,大小為所指定的大小
if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //如果大於0就指向一個空數組 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 無參構造 */ public ArrayList() { //對象數組就指向到一個空數組 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 向elementData末尾中添加元素 */ public boolean add(E e) { //確保對象數組elementData有足夠的容量,可以將新加入的元素e加進去 ensureCapacityInternal(size + 1); //加入新元素e,size加1 elementData[size++] = e; return true; } // minCapacity = seize+1,即表示執行完添加操作後,數組中的元素個數 private void ensureCapacityInternal(int minCapacity) { //判斷是否是空數組 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //用最小容量和10進行比較,取最大值賦值給最小容量 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } /** *確保數組的容量足夠存放新加入的元素,若不夠,要擴容 */ private void ensureExplicitCapacity(int minCapacity) { modCount++; //如果數組個數(size+1)數組長度就需要進行擴容 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { int oldCapacity = elementData.length; // 將舊的數組容量增加為原來的1.5倍作為新的容量 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果新的容量小於數組個數,將數組個數賦值給新容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新的容量大於最大容量,就根據數組個數來決定新的容量大小 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 根據新的容量,將數組拷貝到新的數組並賦值給數組 elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { // 如果數組個數小於0拋出OutOfMemoryError異常 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); // 如果最數組個數大於最大容量 就返回最大值,否則返回最大容量 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** * 向elementData指定位置添加元素 */ public void add(int index, E element) { //指定位置檢查 rangeCheckForAdd(index); // 擴容檢查 ensureCapacityInternal(size + 1); // Increments modCount!! //通過拷貝使數組內位置為 index 到 (size-1)的元素往後移動一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 找到位置添加元素 elementData[index] = element; // 元素個數加一 size++; } // 判斷指定位置是否超出數組容量 private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * @param src 原數組. * @param srcPos 原數組的起始位置. * @param dest 目標數組. * @param destPos 目標數組的起始位置. * @param length 拷貝長度. */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); public E remove(int index) { //指定位置檢查 rangeCheck(index); modCount++; //保留要刪除的值 E oldValue = elementData(index); //要移動元素個數 int numMoved = size - index - 1; if (numMoved > 0) //通過拷貝使數組內位置為 index+1到 (size-1)的元素往前移動一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); //清除末尾元素讓GC回收 elementData[--size] = null; // clear to let GC do its work //返回刪除的值 return oldValue; }

ArrayList在無參的add方法中,每次插入新的元素時,先判斷是否需要擴容,判斷數組是否為空,為空則建默認的容量10賦值給minCapacity,判斷是否要擴容,第一次添加,數組的size是10,之後添加元素時,在ensureExplicitCapacity方法中判斷數組元素個數即size+1(形參minCapacity)是否超過數組長度,超過則需要進行擴容操作,擴容是將舊的容量擴大到1.5倍,然後將數組拷貝到新的數組完成擴容操作。最後將元素添加,並size+1。ArrayList在指定位置添加元素時,是先檢查指定位置是否在數組範圍內,即數組中元素個數是1,則index得小於或者低於1。然後通過拷貝使數組內位置為 index 到 (size-1)的元素往後移動一位,騰出位置之後放入元素,數組個數加一。

Vector特點

1、java.util.Vector<E> :List 接口的內部用數組存放元素的實現類

2、內部也是用數組存放元素。

3、與 ArrayList 不同的是 擴容方式不同,Vector 每次增長的容量是固定的,大部分方法都被 synchronized 關鍵字修飾。

4、Vector 是線程安全的。

5、源碼分析

protected transient int modCount = 0;
//elementData中已存放的元素的個數,註意:不是elementData的容量
protected int elementCount;
//對象數組:Vector的底層數據結構
protected Object[] elementData;
//增長系數
protected int capacityIncrement;

@Native public static final int   MAX_VALUE = 0x7fffffff;

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
 *指定初始容量和增長系數的有參構造
 */
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
         //如果初始容量大於0就,對象數組就指向到一個新的數組,大小為所指定的大小
        this.elementData = new Object[initialCapacity];
        //給增長系數賦值
        this.capacityIncrement = capacityIncrement;
}
/**
 *無參構造
 */
public Vector() {
        //調用指定初始容量的有參構造,設置默認初始容量為10


        this(10);
}
/**
 *指定初始容量的有參構造
 */
public Vector(int initialCapacity) {
        //調用指定初始容量和增長系數的有參構造,設置增長系數為0
        this(initialCapacity, 0);
}

/**
 * 向elementData末尾添加元素
 */
public synchronized boolean add(E e) {
        modCount++;
        //確保對象數組elementData有足夠的容量,可以將新加入的元素e加進去
        ensureCapacityHelper(elementCount + 1);
        //加入新元素e,elementCount加1
        elementData[elementCount++] = e;
        return true;
}
public synchronized void addElement(E obj) {
        modCount++;
        //確保對象數組elementData有足夠的容量,可以將新加入的元素e加進去
        ensureCapacityHelper(elementCount + 1);
        //加入新元素e,elementCount加1
        elementData[elementCount++] = obj;
 }
/**
 * 向elementData指定位置添加元素
 */
public void add(int index, E element) {
        insertElementAt(element, index);
}

public synchronized void insertElementAt(E obj, int index) {
        modCount++;
       // 如果index大於數組個數拋出ArrayIndexOutOfBoundsException異常

        if (index > elementCount) {
            throw new ArrayIndexOutOfBoundsException(index
                                                     + " > " + elementCount);
        }
        //確保對象數組elementData有足夠的容量,可以將新加入的元素e加進去
        ensureCapacityHelper(elementCount + 1);
        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
        // 找到位置添加元素
        elementData[index] = obj;    
        // 元素個數加一
        elementCount++;
}

/**
 * @param      src      原數組.
 * @param      srcPos   原數組的起始位置.
 * @param      dest     目標數組.
 * @param      destPos  目標數組的起始位置.
 * @param      length   拷貝長度.
 */
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

private void ensureCapacityHelper(int minCapacity) {
        //元素個數如果大於數組長度就擴容 
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}

private void grow(int minCapacity) {
      
        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);
}

private static int hugeCapacity(int minCapacity) {
       // 如果數組個數小於0拋出OutOfMemoryError異常
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
       // 如果最數組個數大於最大容量 就返回最大值,否則返回最大容量
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

Vector和ArrayList一樣都是基於數組實現的,且默認長度都是10,不同的是ArrayList擴容方式是擴大為原來的1.5倍,Vector默認是擴大為原來的兩倍。其次Vector的方法都是同步的(Synchronized),是線程安全的,而ArrayList的方法不是。

LinkedList特點

1、java.util.LinkedList<E> :List 接口的實現類和Queue接口子類Deque的實現類

2、內部基於鏈表實現,增刪快、叠代快,隨機訪問能力較差。

3、鏈表中的每個節點都是 LinkedList.Node 類型的對象。

4、LinkedList提供額外的get、remove、insert方法在LinkedList的首部或尾部。

5、這些操作使LinkedList可被用作堆棧(stack),隊列(queue)或雙向隊列(deque)。

6、LinkedList沒有同步方法。如果多個線程想訪問同一個List,則必須自己實現訪問同步。

7、源碼分析

// 鏈表中存放節點個數
transient int size = 0;
// 鏈表頭結點
transient Node<E> first;
// 鏈表尾結點
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;
        }
}
/**
 * 無參構造
 */
public LinkedList() {
}
/**
 * 有參構造,將集合中元素插入鏈表
 */

public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
}

public boolean addAll(Collection<? extends E> c) {
        //以size作為插入下標,插入集合中所有
        return addAll(size, c);
}

/**
 * 以index為下標,添加所有到元素尾部
 */
public boolean addAll(int index, Collection<? extends E> c) {
        //指定位置檢查
        checkPositionIndex(index);
        //集合轉換為數組
        Object[] a = c.toArray();
        //獲取新增元素數量
        int numNew = a.length;
        //如果新增元素為0就不增加
        if (numNew == 0)
            return false;
        Node<E> pred, succ;    
        //如果index是size,在鏈表尾部追加數據
        if (index == size) {
            succ = null;//原節點為空,
            pred = last;//前置節點為尾節點,
        } else {
            //如果index!=size通過遍歷獲取到原節點
            succ = node(index);
            //原節點的前置節點通過pred進行保留
            pred = succ.prev;
        }
        //遍歷數組,依次插入節點
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //生成新節點,前置節點為pred,後置節點為null
            Node<E> newNode = new Node<>(pred, e, null);
            //如果前置節點是null,則更新頭節點
            if (pred == null)
                first = newNode;

            else
                //如果前置節點不為null,則更新前置節點的後置節點為新節點
                pred.next = newNode;
            //設置當前節點為下次的前置節點,為下次插入做準備
            pred = newNode;
        }
        //在鏈表尾部加數據時,原節點為null,則更新尾節點為最後插入的節點
        if (succ == null) {
            last = pred;
        } else {
            //不是在鏈表尾部追加數據時,將最後插入的節點的後置節點更新為原節點
            pred.next = succ;
            //原節點的前置節點更新為最後插入的節點
            succ.prev = pred;
        }
        //更新節點個數
        size += numNew;
        modCount++;
        return true;
}
/**
 * 添加節點
 */
public boolean add(E e) {
         //生成新節點 並插入到 鏈表尾部, 更新 last/first 節點。
        linkLast(e);
        return true;
}
//生成新節點 並插入到 鏈表尾部, 更新 last/first 節點。
void linkLast(E e) {
        //用l記錄尾節點
        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加1
        size++;
        modCount++;
}
/**
 * 添加節點到指定位置
 */
public void add(int index, E element) {
         //指定位置檢查
        checkPositionIndex(index); 
         //指定位置等於元素個數,在尾節點插入
        if (index == size)
            linkLast(element);
        else
              //在中間插入
            linkBefore(element, node(index));
}

private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
}

void linkBefore(E e, Node<E> succ) {
        // 用pred記錄指定位置節點(原節點)的前置節點
        final Node<E> pred = succ.prev;
        //生成新節點,前置節點為pred,後置節點為指定位置的節點(原節點)
        final Node<E> newNode = new Node<>(pred, e, succ);
        //將新節點作為原節點的前置節點
        succ.prev = newNode;
        //如果原節點的前置節點為空,更新頭節點
        if (pred == null)
            first = newNode;
        else    
            //如果原節點的前置節點不為空則更新前置節點的後置節點為新節點
            pred.next = newNode;
        //更新節點個數
        size++;
        modCount++;
}
/**
 * 移除指定位置的節點
 */

public E remove(int index) {
        //指定位置檢查
        checkElementIndex(index);
        //移除節點
        return unlink(node(index));
}
/**
 * 移除節點
 */
E unlink(Node<E> x) {
        // 記錄節點信息
        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;
            //刪除除節點的前置節點置為null
            x.prev = null;
        }
        // 刪除節點是否為尾節點
        if (next == null) {
            // 更新尾結點為刪除節點的前置節點
            last = prev;
        } else {    
           //更新刪除節點的後置節點的前置節點為刪除節點的前置節點
            next.prev = prev;
            //刪除除節點的後置節點置為null
            x.next = null;
        }
        //刪除除節點的元素值置為null
        x.item = null;
        //更新節點數並返回刪除節點的元素值
        size--;
        modCount++;
        return element;
}
/**
 * 獲取指定位置的節點的元素值
 */
public E get(int index) {
         //指定位置檢查
        checkElementIndex(index);
         //獲取元素值
        return node(index).item;
}
/**
 *會根據index處於前半段還是後半段 進行一個折半,以提升查詢效率
 */
Node<E> node(int index) {
        //如果index< size/2從頭結點開始遍歷
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
             //如果index>=size/2從尾結點開始遍歷
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
}

LinkedList的批量增加是找到添加的位置,然後通過循環數組,依次執行插入節點。LinkedList的移除節點則是先根據index判斷及誒單是否存在,再根據所移除節點判斷是否為頭節點和尾節點,更新移除節點的前置節點和後置節點的信息,然後在把移除節點的信息置為null。查詢操作則是根據index是處於前半段還是後半段,進行折半,以此來提高查詢效率。

ArrayList和Vector

1、聯系

  • 都是List接口(List接口繼承了Collection接口)的實現類
  • 都是基於數組實現的,位置都是有序的,並且數據允許重復

2、區別

  • Vector類的方法大部分用synchronized修飾,是線程安全的,而ArrayList是線程不安全的,它的方法之前不是同步的
  • 擴容方式不同,Vector是默認增加容量一倍,或者是按照指定的容量增量進行增加。而ArrayList是增加容量為原來的二分之一

ArrayList和LinkedList

1、聯系

  • 都是List接口(List接口繼承了Collection接口)的實現類
  • 都是線程不安全的

2、區別

  • ArrayList是基於動態數組實現的,LinkedList是基於鏈表實現的
  • 對於隨機訪問get和set,ArrayList優於LinkedList,因為LinkedList是通過先折半然後遍歷節點來獲取指定節點,則ArrayList只需要通過索引就可以獲取到
  • 對於新增和刪除操作add和remove,LinkedList優於ArrayList,因為ArrayList要移動數據,而LinkedList只需要記錄前後節點即可

牛客網筆試題

1、java 中的集合類包括 ArrayList 、 LinkedList 、 HashMap 等,下列關於集合類描述錯誤的是?

正確答案: C

A、ArrayList和LinkedList均實現了List接口

B、ArrayList訪問速度比LinkedList快

C、隨機添加和刪除元素時,ArrayList的表現更加快速

D、HashMap實現Map接口,它允許任何類型的鍵和值對象,並允許將NULL用作鍵或值

2、實現或繼承了Collection接口的是()

正確答案: B C E

A、Map

B、List

C、Vector

D、Iterator

E、Set

3、ArrayLists和LinkedList的區別,下述說法正確的有?

正確答案: A B C D

A、ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。

B、對於隨機訪問get和set,ArrayList覺得優於LinkedList,因為LinkedList要移動指針。

C、對於新增和刪除操作add和remove,LinkedList比較占優勢,因為ArrayList要移動數據。

D、ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間。

參考博客鏈接

https://blog.csdn.net/zxt0601/article/details/77341098

轉載請於明顯處標明出處

https://www.cnblogs.com/AmyZheng/p/9428922.html

ArrayList、Vector和LinkedList