ArrayList、Vector和LinkedList
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 = {}; //實例化一個空數組 privatestatic 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