Java集合框架之Collections接口及實現類

集合框架(collections framework)

現實生活中:不少相同事物湊在一塊兒,好比人羣
數學中的集合:具備共同屬性的事物的整體
java中的集合框架:是一種工具類,就像是容器,儲存任意數量的具備共同屬性的對象html

其實說白了,能夠把一個集合當作一個微型數據庫,操做不外乎「增刪改查」四種操做,咱們在學習使用一個具體的集合類時,須要把這四個操做的時空複雜度弄清楚了,基本上就能夠說掌握這個類了。java

兩大基類Collection與Map

在集合框架的類繼承體系中,最頂層有兩個接口:node

  • Collection表示一組純數據
  • Map表示一組key-value對

通常繼承自Collection或Map的集合類,會提供兩個「標準」的構造函數:面試

  • 沒有參數的構造函數,建立一個空的集合類
  • 有一個類型與基類(Collection或Map)相同的構造函數,建立一個與給定參數具備相同元素的新集合類

由於接口中不能包含構造函數,因此上面這兩個構造函數的約定並非強制性的,可是在目前的集合框架中,全部繼承自Collection或Map的子類都遵循這一約定。算法

Collection

輸入圖片說明

如上圖所示,Collection類主要有三個接口:數據庫

  • Set表示不容許有重複元素的集合(A collection that contains no duplicate elements)
  • List表示容許有重複元素的集合(An ordered collection (also known as a sequence))
  • Queue JDK1.5新增,與上面兩個集合類主要是的區分在於Queue主要用於存儲數據,而不是處理數據。(A collection designed for holding elements prior to processing.)

Collection接口部分源碼數組

public interface Collection<E> extends Iterable<E> {

    int size();
    boolean isEmpty();
    boolean contains(Object o);
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    void clear();

    Iterator<E> iterator();
    Object[] toArray();
}

以上是Collection接口的經常使用方法,由於3個子接口都繼承了這個接口,所以在它們各自特有方法外,都會實現以上方法。安全

Map

輸入圖片說明

Java 中有四種常見的Map實現——HashMap, TreeMap, Hashtable和LinkedHashMap:session

  • HashMap就是一張hash表,鍵和值都沒有排序。
  • TreeMap以紅黑樹結構爲基礎,鍵值能夠設置按某種順序排列。
  • LinkedHashMap保存了插入時的順序。
  • Hashtable是同步的(而HashMap是不一樣步的)。因此若是在線程安全的環境下應該多使用HashMap,而不是Hashtable,由於Hashtable對同步有額外的開銷,不過JDK 5以後的版本可使用conncurrentHashMao代替HashTable。

說到Map接口的話你們也許在熟悉不過了。Map接口實現的是一組Key-Value的鍵值對的組合。 Map中的每一個成員方法由一個關鍵字(key)和一個值(value)構成。Map接口不直接繼承於Collection接口(須要注意啦),由於它包裝的是一組成對的「鍵-值」對象的集合,並且在Map接口的集合中也不能有重複的key出現,由於每一個鍵只能與一個成員元素相對應。多線程

另外,Set接口的底層是基於Map接口實現的。Set中存儲的值,其實就是Map中的key,它們都是不容許重複的。

Map接口部分源碼

public interface Map<K,V> {

    int size();
    boolean isEmpty();
    boolean containsKey(Object key);
    boolean containsValue(Object value);
    V get(Object key);
    V put(K key, V value);
    void putAll(Map<? extends K, ? extends V> m);
    V remove(Object key);
    void clear();

    Collection<V> values();
    Set<K> keySet();
    Set<Map.Entry<K, V>> entrySet();
    interface Entry<K,V> {
        K getKey();
        V getValue();
        V setValue(V value);

    }
}

源碼中的方法你們也比較熟悉。在遍歷Map時,咱們能夠經過keySet()方法獲取到全部key,它返回的是一個Set對象,遍歷Set,經過key獲取value。也能夠經過entrySet()的方式獲取Entry的Set,遍歷Set,經過Entry的getValue()getKey()方法獲取值和對象,這些在後面會詳細講到。

List接口

List接口對Collection進行了簡單的擴充,所以它繼承了Collection接口。其中大部分方法和其繼承的Collection相同,至於不一樣之處也不太經常使用,你們能夠參考源碼。

特色:

List中存儲的元素是有序的,並且能夠重複的存儲相關元素。

ArrayList

特色:

ArrayList的底層使用數組實現,當元素的數量超過數組長度時,經過新建更大容量數組,將原數組內容拷貝一份,而後新增元素的方式存儲元素。

優勢:

相似數組的形式進行存儲,所以它的隨機訪問速度極快。

缺點:

不適合於在線性表中間須要頻繁進行插入和刪除操做。由於每次插入和刪除都須要移動數組中的元素。

能夠這樣理解ArrayList就是基於數組的一個線性表,只不過數組的長度能夠動態改變而已。

對於ArrayList的詳細使用信息以及建立的過程能夠查看jdk中ArrayList的源碼,這裏不作過多的講解。

對於使用ArrayList的開發者而言,下面幾點內容必定要注意啦,尤爲找工做面試的時候常常會被問到。做者去年面試的時候,都已經被問煩了

注意啦!!!!!!!!

一、關於擴容問題:

默認ArrayListde的默認構造函數ArrayList()會構造一個長度爲10的數組。
ArrayList(int initialCapacity)構造函數會構造一個指定長度的數組。
添加元素時,若是超出了長度,則以每次舊長度的3/2倍增加。

例:

new ArrayList(20);擴容幾回?
答案: 0次,由於直接產生了一個長度20的數組

二、ArrayList是線程不安全的,在多線程的狀況下不要使用。

若是必定在多線程使用List的,你可使用Vector,由於Vector和ArrayList基本一致,區別在於Vector中的絕大部分方法都使用了同步關鍵字修飾,這樣在多線程的狀況下不會出現併發錯誤哦,還有就是它們的擴容方案不一樣,ArrayList是經過原始容量*3/2,而Vector是容許設置默認的增加長度,Vector的默認擴容方式爲原來的2倍(能夠經過構造函數設置,如設置爲2,擴容後長度爲舊長度+2)。

切記Vector是ArrayList的多線程的一個替代品。

三、ArrayList實現遍歷的幾種方法

ArrayList<String> list = new ArrayList();
        list.add("hello");
        list.add(",");
        list.add("world");

        // 第一種遍歷方式使用foreach遍歷List,編譯器編譯時,會將這種方式轉化爲迭代器方式
        for (String str : list) {
            System.out.print(str);
        }
        System.out.println();

        // 第二種遍歷方式使用for循環依次獲得元素
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i));
        }
        System.out.println();

        // 第三種遍歷方式使用迭代器遍歷
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            System.out.print(iterator.next());
        }
        System.out.println();

LinkedList

特色:

LinkedList的底層用雙向鏈表實現。另外建議閱讀其源碼,並不難,會讓你有醍醐灌頂的感受。

優勢:

鏈表相對於實現ArrayList的數組來講,其存儲空間是散列的而不是連續的,所以在鏈表中間插入和刪除元素時,無需移動後面的元素,只須要改變3個節點的關聯便可。

缺點:

由於LinkedList不是空間連續的,所以隨機讀取時,須要從頭至尾的讀取,所以不如ArrayList來得快。另外,在使用雙向鏈表實現時,須要額外提供空間供記錄前驅節點和後繼節點的地址,消耗了額外空間。

對於使用LinkedList而言,下面幾點內容必定要注意啦

注意啦!!!!!!!!

一、LinkedList和ArrayList的區別和聯繫

主要從底層實現、優缺點(隨機讀取、新增、刪除)等方面總結,詳見以前總結的特色、優缺點,再也不贅述。

二、LinkedList的內部實現

強烈建議你們去看看源碼,其內部使用雙鏈表實現,若是你實在不想看,下面的代碼提供了其節點結構,和最經常使用的add()remove()方法。

// 長度
    transient int size = 0;
    
    // 節點的表示
    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;
        }
    }
    
    // 此段不是源碼,僅供理解思想
    private void add(Node<E> node, E data) {
        Node<E> newNode = new Node<E>(node.prev, data, node);
        newNode.prev.next = newNode;
        node.prev = newNode;
        size++;
    }

    // 此段不是源碼,僅供理解思想
    private E remove(Node<E> node) {
        node.next.prev = node.prev;
        node.prev.next = node.next;
        size--;
        return node.item;
    }

三、LinkedList不是線程安全的

注意LinkedList和ArrayList同樣也不是線程安全的,若是在對線程下面訪問能夠本身重寫LinkedList

而後在須要同步的方法上面加上同步關鍵字synchronized

四、LinkedList的遍歷方法

同ArrayList

LinkedList<String> list = new LinkedList();
        list.add("hello");
        list.add(",");
        list.add("world");

        // 第一種遍歷方式使用foreach遍歷List
        for (String str : list) {
            System.out.print(str);
        }
        System.out.println();

        // 第二種遍歷方式使用for循環依次獲得元素,這種方式用到了隨機讀取,特別特別不推薦
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i));
        }
        System.out.println();

        // 第三種遍歷方式使用迭代器遍歷
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            System.out.print(iterator.next());
        }
        System.out.println();
    }

五、LinkedList能夠被當作堆棧來使用

因爲LinkedList實現了接口Dueue,因此LinkedList能夠被當作堆棧來使用,這個你本身研究吧。

Vector

Vector和ArrayList不論在實現,仍是使用上,都大同小異。所以也就不細說,他們主要的不一樣就是Vector是線程安全的,它在一些方法上加了synchronized關鍵字。

劃重點了!!!!!!!!

一、Arraylist與Vector的區別

  • Vector是線程安全的,ArrayList不是線程安全的。
  • ArrayList在底層數組不夠用時在原來的基礎上擴展0.5倍(3/2),Vector是擴展1倍。

參見 https://zhuanlan.zhihu.com/p/28241176

Set接口

Set接口也是Collection接口的一個經常使用子接口,它區別於List接口的特色在於:

Set中的元素實現了不重複,有點象集合的概念,無序,不容許有重複的元素,最多容許有一個null元素對象。

須要注意的是:雖然Set中元素沒有順序,可是元素在set中的位置是有由該元素的HashCode決定的,其具體位置實際上是固定的。

此外須要說明一點,在set接口中的不重複是由特殊要求的。

舉一個例子:對象A和對象B,原本是不一樣的兩個對象,正常狀況下它們是可以放入到Set裏面的

可是

若是對象A和B的都重寫了hashcode和equals方法,而且重寫後的hashcode和equals方法是相同的話。那麼A和B是不能同時放入到Set集合中去的

也就是Set集合中的去重和hashcode與equals方法直接相關。

Set接口的常見實現類有HashSet,LinedHashSet和TreeSet這三個,如今依次介紹這三個類:

HashSet

HashSet是Set接口的最多見的實現類了。其最底層是經過Hash表(一個元素爲鏈表的數組)實現的。另外,Hash表底層依賴的兩個方法hashcode與equals方法,想存入HashSet的元素須要複寫這兩個方法。

爲何說其最底層呢?請先看下這段源碼:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    
    private transient HashMap<E,Object> map;
    
    public HashSet() {
        map = new HashMap<>();
    }
}

顯而易見,HashSet的內部是基於HashMap實現的,咱們都知道在HashMap中的key是不容許重複的,你換個角度看看,那不就是說Set集合嗎?

咱們只須要用一個固定值值代替Map中的value,

private static final Object PRESENT = new Object();    

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

jdk中用了一個靜態的Object對象代替了value。那Set中的元素作key即可以保證元素不重複。

下面講解一下HashSet使用和理解中容易出現的誤區:

一、HashSet中存放null值

HashSet中時容許出入null值的,可是在HashSet中僅僅可以存入一個null值哦。

二、 HashSet中存儲元素的位置是固定的

HashSet中存儲的元素的是無序的,這個沒什麼好說的,可是因爲HashSet底層是基於Hash算法實現的,使用了hashcode,因此HashSet中相應的元素的位置是固定的哦。

三、 遍歷HashSet的幾種方法

HashSet<String> hashSet = new HashSet<String>();
        hashSet.add("hello,");
        hashSet.add("hello,");
        hashSet.add("world");

        // 第一種遍歷方式,使用foreach遍歷
        for (String str : hashSet) {
            System.out.print(str);
        }
        System.out.println();

        // 第二種遍歷方式,使用迭代器遍歷
        Iterator<String> iterator = hashSet.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next());
        }

LinkedHashSet

LinkedHashSet不只是Set接口的子接口並且仍是上面HashSet接口的子接口,和HashSet由HashMap實現同樣,LinkedHashSet的底部由LinkedHashMap實現。

Set<String> set = new HashSet<String>();
        set.add("hello");
        set.add("world");

        for (String str : set) {
            System.out.print(str);
        }
        System.out.println();

        set = new LinkedHashSet<String>();
        set.add("hello");
        set.add("world");
        for (String str : set) {
            System.out.print(str);
        }

上面的程序輸出結果以下:

輸入圖片說明

可見,LinkedHashSet集合一樣是根據元素的hashCode值來決定元素的存儲位置,可是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。

由於它底層由LinkedHashMap實現,因此更多細節參加LinkedHashMap。

TreeSet

TreeSet是SortedSet接口的惟一實現類,TreeSet能夠確保集合元素處於排序狀態。

TreeSet支持兩種排序方式,天然排序 和定製排序,其中天然排序爲默認的排序方式。

向TreeSet中加入的應該是同一個類的對象。TreeSet判斷兩個對象不相等的方式是兩個對象經過equals方法返回false,或者經過CompareTo方法比較沒有返回0

天然排序

天然排序使用要排序元素的CompareTo(Object obj)方法來比較元素之間大小關係,而後將元素按照升序排列。

Java提供了一個Comparable接口,該接口裏定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現了該接口的對象就能夠比較大小。obj1.compareTo(obj2)方法若是返回0,則說明被比較的兩個對象相等,若是返回一個正數,則代表obj1大於obj2,若是是 負數,則代表obj1小於obj2。

定製排序

天然排序是根據集合元素的大小,以升序排列,若是要定製排序,應該使用Comparator接口,實現 int compare(T o1,T o2)方法

因爲水平和時間有限,Map相關內容暫不總結

https://www.cnblogs.com/xiohao/p/4309462.html

https://zhuanlan.zhihu.com/p/24338517?utm_source=wechat_session&utm_medium=social

相關文章
相關標籤/搜索