Java容器系列-Java容器總覽

Java 的容器是 Java 語言中很重要的一部分,平常寫代碼會大量用到各類容器。Java 中的容器有一個龐大的體系,糾纏於細節很難全面掌握。這篇文章就總覽一下 Java 的容器,而後再深刻到細節中學習。java

Java 中的容器主要分爲兩部分,CollectionMap 兩種。Collection 主要用於存儲單個的元素。而 Map 則主要是存儲鍵值對。算法

本文基於 JDK1.8編程

Collection

上圖中圓圈表明接口, 長方形表明,包括抽象類和普通類。綠色表明線程安全,黃色表明不是線程安全。上面的類圖中只包括了 java.util 下的類,java.util.concurrent 下面的容器類從功能的角度上來講並無太大不一樣,可是這個包下的類都是線程安全的。數組

從類圖中能夠看到 Collection 繼承了 Iterator 接口,說明全部的 Collection 均可以經過迭代器來進行訪問。安全

Collection 接口有三個子接口,ListSetQueue。List 會按照元素的插入順序保存元素,Set 中的元素都不能重複。Collection 中定義了一些公共的方法,都是一些基礎的工具方法,好比獲取容器的大小、判斷容器時候爲空、清空容器、迭代容器元素等方法。在 JDK1.8 之後,在 Collection 接口中加入了 default 方法,這些方法都是用於支持 Java8 的函數式編程。微信

interface Collection<E> extends Iterable<E> {
    
    int size();
    
    boolean isEmpty();
    
    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    default <T> T[] toArray(IntFunction<T[]> generator) {
        return toArray(generator.apply(0));
    }

    boolean add(E e);

    boolean remove(Object o);
    
    boolean containsAll(java.util.Collection<?> c);

    boolean addAll(java.util.Collection<? extends E> c);

    boolean removeAll(java.util.Collection<?> c);

    default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

    boolean retainAll(java.util.Collection<?> c);
    void clear();
    boolean equals(Object o);

    int hashCode();

    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }

    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}
複製代碼

List

List 接口下的 ArrayList 平常寫代碼使用的不少。ArrayList 的部分代碼以下。從代碼中能夠看到,ArrayList 底層的數據結構就是一個數組,並且 ArrayList 實現了 RandomAccess 來支持隨機訪問。數據結構

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

    transient Object[] elementData;
}
複製代碼

ArrayList 與數組的功能很像,可是提供了更多便利的操做。Vector 與 ArrayList 的功能基本一致,可是是線程安全的,Vector 的子類 Stack 一樣也是線程安全的,可是這些類基本都不推薦再使用了。若是要使用線程安全的類,java.util.concurrent 中的 CopyOnWriteArrayList 是一種更好的選擇。多線程

LinkedList 與 ArrayList 功能也比較相近,從功能的角度上來講,它們之間最大的區別在於 ArrayList 支持隨機訪問,而 LinkedList 則不支持。LinkedList 部分代碼以下,能夠看到 LinkedList 底層使用的是雙向鏈表的數據結構。並且還實現了 Deque 接口,因此除了能夠做爲列表容器來使用以外,還能夠做爲隊列或者雙端隊列來使用。併發

class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    
    transient int size = 0;

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

}
複製代碼

LinkedList 一樣在 java.util.concurrent 中提供 LinkedBlockingQueue 和 LinkedBlockingDeque 來實現一樣的功能,除了在多線程環境比 LinkedList 更有優點外,功能方面基本沒有差異。app

Set

各種 Set 的共同點在於 set 的元素是不重複的,這一特性在一些狀況下很是有用,HashSet 是用的最多的 Set 類。如下是 HashSet 的部分代碼,比較有意思的是 HashSet 底層是使用 HashMap 實現的,全部的值都存着在 HashMap 的 Key 中,Value 的位置就放一個固定的對象 PRESENT。

class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {

    private transient HashMap<E, Object> map;

    private static final Object PRESENT = new Object();

    public HashSet() {
        map = new HashMap<>();
    }
}
複製代碼

HashSet 裏面的元素是無序的,若是須要讓 set 中元素有序,那麼就可使用 LinkedHashSet,LinkedHashSet 中經過構造一個雙向鏈表來記錄插入順序。而 TreeSet 則是經過底層的紅黑樹結構提供了排序順序的訪問方式,具體用哪一種能夠看具體的需求。一樣 Set 也有線程安全的版本 CopyOnWriteArraySet

Queue

Queue/Deque 是 Java 中的提供的 隊列接口。ArrayQueue 是具體可使用的隊列類,能夠做爲普通隊列或則雙端隊列來使用。可是隊列在併發狀況使用的更多一點,使用 LinkedBlockingQueue 或者 LinkedBlockingDeque 會是更好的選擇。有時候除了順序隊列以外,可能還須要經過優先級來調度的隊列,PriorityQueue 就是爲這個需求而生的,在併發狀況下與之對應的就是 PriorityBlockingQueue。

Map

Map 的類圖結構相對來講就簡單不少。全部的 Map 類都繼承了 Map 接口。HashMap 是使用的最多的 Map 類,HashMap 也是無序的,和 Set 相似,LinkedHashMap 和 TreeMap 也從不一樣的方面保證順序,LinkedHashMap 經過雙向鏈表來記錄插入順序。TreeMap 則是對其中的元素進行排序,能夠按照排序的順序進行訪問。

做爲 Map 的典型實現,HashMap 代碼結構就複雜的多,HashMap 號稱是有着 O(1) 的訪問速度(只是近似,在極端狀況下可能退化成 O(N))。這麼快速的關鍵在於哈希函數的實現,哈希函數好的實現能夠幫助鍵值對均勻的分佈,從而有 O(1) 的訪問速度,如下是 HashMap 的哈希函數的實現,並且 HashMap 的擴容和處理哈希碰撞等問題的處理也很複雜。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
複製代碼

與 Collection 中的結構相似,HashTable 也與 HashMap 功能相似,可是 HashTable 是線程安全的。一樣由於 HashTable 實現的方式不如 java.util.concurrent 中提供的性能好,因此不推薦使用 HashTable。在併發狀況下推薦使用 ConcurrentHashMap,ConcurrentHashMap 經過分段鎖的機制,在併發狀況下也能有較好的性能。若是在併發狀況下也須要保證 Map 的順序,那就使用 ConcurrentNavigableMap。

Collections 工具類

在 java.util 包下有一個 Collections 類,這是一個工具類,裏面全部的方法都是靜態的,並且類不能被實例化。裏面提供了各類方法,能夠用來更有效率的操做各種容器對象。

好比對 List 排序:

ArrayList<Integer> list = new ArrayList();
list.add(1);
list.add(4);
list.add(6);
list.add(2);
list.add(8);
Collections.sort(list);
複製代碼

固然還能夠自定義排序的規則,本身實現一個 Comparator 而後做爲參數傳入就行了。

Collections.sort(list, new Comparator<Integer>() { 
    @Override   
    public int compare(Integer o1, Integer o2) {
        return o1 > o2 ? 1 : 0;    
    }
 });
複製代碼

還有開箱即用的二分查找算法:

Collections.binarySearch(list, 2);
複製代碼

還能夠直接把 list 進行反轉:

Collections.reverse(list);
複製代碼

還能夠把 list 使用洗牌算法打亂:

Collections.shuffle(list);
複製代碼

以上只是其中的一部分方法,還有能夠交換 list 中的元素,找出 list 中的最小、最大值等方法。

由於 java.util 包下的容器大部分都不是線程安全的,Collections 有一類方法能夠把 普通的容器對象轉成線程安全的對象:

Collections.synchronizedList(list);
複製代碼

對於 Map 和 Set 也有相似的工具方法。

在併發環境下,還能夠把一個普通容器對象轉化成一個不可變的容器對象,這樣在併發環境下也是線程安全的:

Collections.unmodifiableList(list);
複製代碼

(完)

原文

關注微信公衆號,聊點其餘的

相關文章
相關標籤/搜索