Java容器(List、Set、Map)知識點快速複習手冊(上)

Java容器(List、Set、Map)知識點快速複習手冊(上)

前言

本文快速回顧了Java中容器的知識點,用做面試複習,事半功倍。html

上篇:主要爲容器概覽,容器中用到的設計模式,List源碼java

中篇:Map源碼git

下篇:Set源碼,容器總結github

其它知識點複習手冊

  • Java基礎知識點面試手冊(上)面試

  • Java基礎知識點面試手冊(下)

概覽

容器主要包括 Collection 和 Map 兩種,Collection 又包含了 List、Set 以及 Queue。算法

Collection

Java容器(List、Set、Map)知識點快速複習手冊(上)
Java容器(List、Set、Map)知識點快速複習手冊(上)

數組和集合的區別:編程

  • 長度
  • 數組的長度固定
  • 集合的長度可變
  • 內容
  • 數組存儲的是同一種類型的元素
  • 集合能夠存儲不一樣類型的元素(可是通常咱們不這樣幹..)
  • 元素的數據類型
  • 數組能夠存儲基本數據類型,也能夠存儲引用類型
  • 集合只能存儲引用類型(若存儲的是簡單的int,它會自動裝箱成Integer)

1. Set(元素不可重複)

  • HashSet:基於HashMap實現,支持快速查找,但不支持有序性操做。

TreeSet:基於紅黑樹實現,支持有序性操做,可是查找效率不如 HashSet,HashSet 查找時間複雜度爲 O(1),TreeSet 則爲 O(logN);設計模式

LinkedHashSet:具備 HashSet 的查找效率,且內部使用鏈表維護元素的插入順序。數組

2. List(有序(存儲順序和取出順序一致),可重複)

  • ArrayList:基於動態數組實現,支持隨機訪問;安全

  • Vector:和 ArrayList 相似,但它是線程安全的;

  • LinkedList:基於雙向鏈表實現,只能順序訪問,可是能夠快速地在鏈表中間插入和刪除元素。不只如此,LinkedList 還能夠用做棧、隊列和雙向隊列。

3. Queue

  • LinkedList:能夠用它來支持雙向隊列;

  • PriorityQueue:基於堆結構實現,能夠用它來實現優先隊列。

Map

Java容器(List、Set、Map)知識點快速複習手冊(上)

  • HashMap:基於哈希實現;

  • HashTable:和 HashMap 相似,但它是線程安全的,這意味着同一時刻多個線程能夠同時寫入 HashTable 而且不會致使數據不一致。它是遺留類,不該該去使用它。

  • ConcurrentHashMap:支持線程安全,而且 ConcurrentHashMap 的效率會更高,由於 ConcurrentHashMap 引入了分段鎖。

  • LinkedHashMap:使用鏈表來維護元素的順序,順序爲插入順序或者最近最少使用(LRU)順序。

  • TreeMap:基於紅黑樹實現。

Fail-Fast 機制和 Fail-Safe 機制

https://blog.csdn.net/Kato_op/article/details/80356618

Fail-Fast

Fail-fast 機制是 java 集合(Collection)中的一種錯誤機制。 當多個線程對同一個集合的內容進行操做時,就可能會產生 fail-fast 事件。

  • 迭代器在遍歷時直接訪問集合中的內容,而且在遍歷過程當中使用一個modCount變量,

  • 集合中在被遍歷期間若是內容發生變化(增刪改),就會改變modCount的值,

  • 每當迭代器使用 hashNext()/next()遍歷下一個元素以前,都會執行checkForComodification()方法檢測,modCount變量和expectedmodCount值是否相等,

  • 若是相等就返回遍歷,不然拋出異常,終止遍歷.

注意,若是集合發生變化時修改modCount值, 恰好有設置爲了expectedmodCount值, 則異常不會拋出.(好比刪除了數據,再添加一條數據)

因此,通常來講,存在非同步的併發修改時,不可能做出任何堅定的保證。

迭代器的快速失敗行爲應該僅用於檢測程序錯誤, 而不是用他來同步。

java.util包下的集合類都是Fail-Fast機制的,不能在多線程下發生併發修改(迭代過程當中被修改).

Fail-Safe

採用安全失敗(Fail-Safe)機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先copy原有集合內容,在拷貝的集合上進行遍歷。

原理:

因爲迭代時是對原集合的拷貝的值進行遍歷,因此在遍歷過程當中對原集合所做的修改並不能被迭代器檢測到,因此不會出發ConcurrentModificationException

缺點:

迭代器並不能訪問到修改後的內容(簡單來講就是, 迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的)

使用場景:

java.util.concurrent包下的容器都是Fail-Safe的,能夠在多線程下併發使用,併發修改

容器中使用的設計模式

迭代器模式

Java容器(List、Set、Map)知識點快速複習手冊(上)

  • Iterator它是在ArrayList等集合的內部類的方式實現
    Collection 實現了 Iterable 接口,其中的 iterator() 方法可以產生一個 Iterator 對象,經過這個對象就能夠迭代遍歷 Collection 中的元素。

從 JDK 1.5 以後可使用 foreach 方法來遍歷實現了 Iterable 接口的聚合對象。

List<String> list =newArrayList<>();
list.add("a");
list.add("b");
for(String item : list){ 
    System.out.println(item);
}

適配器模式

適配器模式解釋:https://www.jianshu.com/p/93821721bf08

java.util.Arrays#asList() 能夠把數組類型轉換爲 List 類型。

@SafeVarargs
public  static <T>  List<T>  asList(T... a)

若是要將數組類型轉換爲 List 類型,應該注意的是 asList() 的參數爲泛型的變長參數,所以不能使用基本類型數組做爲參數,只能使用相應的包裝類型數組。

Integer[ ]  arr =   {1, 2, 3};
List list = Arrays.asList(arr);

也可使用如下方式生成 List。

List  list = Arrays.asList(1 ,2, 3);

源碼分析

ArrayList

關鍵詞

  • 默認大小爲 10
  • 擴容 1.5 倍,加載因子爲 0.5
  • 基於動態數組實現
  • 刪除元素時不會減小容量,若但願減小容量則調用trimToSize()
  • 它不是線程安全的
  • 它能存放null值。
  • 擴容操做須要調用 Arrays.copyOf() 把原數組整個複製到新數組
  • 刪除須要調用 System.arraycopy() 將 index+1 後面的元素都複製到 index 位置上,複製的代價很高。 -序列化:只序列化數組中有元素填充那部份內容

概覽

Java容器(List、Set、Map)知識點快速複習手冊(上)

實現了 RandomAccess 接口,所以支持隨機訪問。這是理所固然的,由於 ArrayList 是基於數組實現的。

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

擴容

若是不夠時,須要使用 grow() 方法進行擴容,新容量的大小爲 oldCapacity+(oldCapacity>>1),也就是舊容量的 1.5 倍。

擴容操做須要調用 Arrays.copyOf() 把原數組整個複製到新數組中

所以最好在建立 ArrayList 對象時就指定大概的容量大小,減小擴容操做的次數。

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

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

加入元素:add

add(E e)

首先去檢查一下數組的容量是否足夠

  • 足夠:直接添加
  • 不足夠:擴容
    擴容到原來的1.5倍,第一次擴容後,若是容量仍是小於minCapacity,就將容量擴充爲minCapacity。

add(int index, E element)

步驟:

  • 檢查角標
  • 空間檢查,若是有須要進行擴容
  • 插入元素

刪除元素:remove

步驟:

  • 檢查角標
  • 刪除元素
  • 計算出須要移動的個數,並移動
  • 設置爲null,讓GC回收(因此說不是馬上回收,而是等待GC回收)
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;
}

須要調用 System.arraycopy() 將 index+1 後面的元素都複製到 index 位置上,複製的代價很高。

複製數組:System.arraycopy()

看到arraycopy(),咱們能夠發現:該方法是由C/C++來編寫的

Java容器(List、Set、Map)知識點快速複習手冊(上)

Fail-Fast

modCount 用來記錄 ArrayList 結構發生變化的次數。結構發生變化是指添加或者刪除至少一個元素的全部操做,或者是調整內部數組的大小,僅僅只是設置元素的值不算結構發生變化。

在進行序列化或者迭代等操做時,須要比較操做先後 modCount 是否改變,若是改變了須要拋出 ConcurrentModificationException。

private void writeObject(java.io.ObjectOutputStream s)
   throws java.io.IOException{
   // Write out element count, and any hidden stuff
   int expectedModCount = modCount;
   s.defaultWriteObject();

   // Write out size as capacity for behavioural compatibility with clone()
   s.writeInt(size);

   // Write out all elements in the proper order.
   for (int i=0; i<size; i++) {
       s.writeObject(elementData[i]);
   }

   if (modCount != expectedModCount) {
       throw new ConcurrentModificationException();
   }
}

構造器

ArrayList 提供了三種方式的構造器:

  • public ArrayList()能夠構造一個默認初始容量爲10的空列表;
  • public ArrayList(int initialCapacity)構造一個指定初始容量的空列表;
  • public ArrayList(Collection c)構造一個包含指定 collection 的元素的列表,這些元素按照該collection的迭代器返回它們的順序排列的。

序列化

補充:transient講解

http://www.importnew.com/21517.html

你只須要實現Serilizable接口,將不須要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。

ArrayList 基於數組實現,而且具備動態擴容特性,所以保存元素的數組不必定都會被使用,那麼就不必所有進行序列化。

保存元素的數組 elementData 使用 transient 修飾,該關鍵字聲明數組默認不會被序列化。

transient Object[] elementData; // non-private to simplify nested class access

ArrayList 實現了 writeObject() 和 readObject() 來控制只序列化數組中有元素填充那部份內容。

private void readObject(java.io.ObjectInputStream s)
   throws java.io.IOException, ClassNotFoundException {
   elementData = EMPTY_ELEMENTDATA;

   // Read in size, and any hidden stuff
   s.defaultReadObject();

   // Read in capacity
   s.readInt(); // ignored

   if (size > 0) {
       // be like clone(), allocate array based upon size not capacity
       ensureCapacityInternal(size);

       Object[] a = elementData;
       // Read in all elements in the proper order.
       for (int i=0; i<size; i++) {
           a[i] = s.readObject();
       }
   }
}
private void writeObject(java.io.ObjectOutputStream s)
   throws java.io.IOException{
   // Write out element count, and any hidden stuff
   int expectedModCount = modCount;
   s.defaultWriteObject();

   // Write out size as capacity for behavioural compatibility with clone()
   s.writeInt(size);

   // Write out all elements in the proper order.
   for (int i=0; i<size; i++) {
       s.writeObject(elementData[i]);
   }

   if (modCount != expectedModCount) {
       throw new ConcurrentModificationException();
   }
}

序列化時須要使用 ObjectOutputStream 的 writeObject() 將對象轉換爲字節流並輸出。而 writeObject() 方法在傳入的對象存在 writeObject() 的時候會去反射調用該對象的 writeObject() 來實現序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理相似。

ArrayList list = new ArrayList();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);

Vector

關鍵詞

  • 默認大小爲 10(與Arraylist相同)
  • 擴容 2 倍,加載因子是 1(Arraylist是擴容 1.5 倍,加載因子爲 0.5)
  • 其它幾乎與ArrayList徹底相同,惟一的區別在於 Vector 是同步的,所以開銷就比 ArrayList 要大,訪問速度更慢。
  • 使用了 synchronized 進行同步
  • Vector是jdk1.2的類了,比較老舊的一個集合類。應使用JUC的CopyOnWriteArrayList代替

替代方案

可使用 Collections.synchronizedList(); 獲得一個線程安全的 ArrayList。

List<String> list = new ArrayList<>();
List<String> synList = Collections.synchronizedList(list);

也可使用 concurrent 併發包下的 CopyOnWriteArrayList 類。

List<String> list = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList

關鍵詞

  • 寫操做在一個複製的數組上進行,讀操做仍是在原始數組中進行,讀寫分離,互不影響。
  • 寫操做須要加鎖,防止併發寫入時致使寫入數據丟失。
  • 寫操做結束以後須要把原始數組指向新的複製數組。
  • 適用於讀操做遠大於寫操做的場景。

讀寫分離

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

final void setArray(Object[] a) {
    array = a;
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
    return (E) a[index];
}

適用場景

CopyOnWriteArrayList 在寫操做的同時容許讀操做,大大提升了讀操做的性能,所以很適合讀多寫少的應用場景。

缺陷

  • 內存佔用:在寫操做時須要複製一個新的數組,使得內存佔用爲原來的兩倍左右;
  • 數據不一致:讀操做不能讀取實時性的數據,由於部分寫操做的數據還未同步到讀數組中。
    因此 CopyOnWriteArrayList 不適合內存敏感以及對實時性要求很高的場景。

LinkedList

關鍵詞

  • 雙向鏈表
  • 默認大小爲 10
  • 帶 Head 和 Tail 指針
  • Node 存儲節點信息

概覽

Java容器(List、Set、Map)知識點快速複習手冊(上)

基於雙向鏈表實現,內部使用 Node 來存儲鏈表節點信息。

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

每一個鏈表存儲了 Head 和 Tail 指針:

transient Node<E> first;
transient Node<E> last;

Java容器(List、Set、Map)知識點快速複習手冊(上)

ArrayList 與 LinkedList 比較

  • ArrayList 基於動態數組實現,LinkedList 基於雙向鏈表實現;
  • ArrayList 支持隨機訪問,LinkedList 不支持;
  • LinkedList 在任意位置添加刪除元素更快。

刪除元素:remove

Java容器(List、Set、Map)知識點快速複習手冊(上)

獲取元素:get

  • 下標小於長度的一半,從頭遍歷
  • 反之,從尾部遍歷

替換元素:set

set方法和get方法其實差很少,根據下標來判斷是從頭遍歷仍是從尾遍歷

其餘方法

LinkedList實現了Deque接口,所以,咱們能夠操做LinkedList像操做隊列和棧同樣

LinkedList的方法比ArrayList的方法多太多了,這裏我就不一一說明了。具體可參考:

參考

  • https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Java%20%E5%AE%B9%E5%99%A8.md
  • Eckel B. Java 編程思想 [M]. 機械工業出版社, 2002.
  • Java Collection Framework
  • Iterator 模式
  • Java 8 系列之從新認識 HashMap
  • What is difference between HashMap and Hashtable in Java?
  • Java 集合之 HashMap
  • The principle of ConcurrentHashMap analysis
  • 探索 ConcurrentHashMap 高併發性的實現機制
  • HashMap 相關面試題及其解答
  • Java 集合細節(二):asList 的缺陷
  • Java Collection Framework – The LinkedList Class

關注我

本人目前爲後臺開發工程師,主要關注Python爬蟲,後臺開發等相關技術。

原創博客主要內容:

  • 筆試面試複習知識點手冊
  • Leetcode算法題解析(前150題)
  • 劍指offer算法題解析
  • Python爬蟲相關實戰
  • 後臺開發相關實戰

同步更新如下幾大博客:

  • Csdn:

http://blog.csdn.net/qqxx6661

擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發

  • 知乎:

https://www.zhihu.com/people/yang-zhen-dong-1/

擁有專欄:碼農面試助攻手冊

  • 掘金:

https://juejin.im/user/5b48015ce51d45191462ba55

  • 簡書:

https://www.jianshu.com/u/b5f225ca2376

  • 我的公衆號:Rude3Knife

Java容器(List、Set、Map)知識點快速複習手冊(上)

相關文章
相關標籤/搜索