Java 容器基本介紹

在介紹Java容器以前,先看一個簡單的容器分類,以作到能夠大致瞭解Java容器分類:java

簡單容器類庫

容器基本介紹

Java容器類類庫的用途是保存對象,並將其劃分爲兩個不一樣的概念:程序員

  1. Collection:一個獨立元素的序列,這些元素都服從一條或多條規則。 List 必須按照插入的順序保存元素,而 Set 不能有重複元素。 Queue 按照排隊規則來肯定對象產生的順序。
  2. Map:一組成對的鍵值對,容許你使用鍵來查找值。又被稱爲映射表關聯數組字典

List

  1. ArrayList:擅長隨機訪問元素,可是在List的中間插入和移除元素時較慢。
  2. LinkedList:經過較低的代價能夠在List中間進行插入和刪除操做,提供了優化的順序訪問。LinkedList在隨機訪問方面相對比較慢,可是它的特性集較ArrayList更大。
    • LinkedList 也像 ArrayList 同樣實現了基本的List接口。
    • LinkedList還添加了可使其用作棧、隊列或雙端隊列的方法。
    • getFirst()element() 徹底同樣,都返回列表的頭(第一個元素),而並不移除它,若是 List 爲空,則拋出 NoSuchElementExceptionpeek() 方法,在列表爲空時返回null。
    • removeFirst()remove() 也徹底同樣,移除並返回列表的頭,若是 List 爲空,則拋出 NoSuchElementException。poll(),在列表爲空時返回null。
    • add()addLast() 同樣,將某個元素插入到列表的尾部。
    • removeLast()pollLast() 移除並返回列表的最後一個元素。

Stack

後進先出 的容器。LinkedList具備可以實現棧的全部功能的方法,能夠直接將LinkedList做爲棧使用。棧操做:數組

  • push() :入棧。
  • peek() :獲取棧頂元素,可是並不將其從棧頂移除。
  • pop() :移除並返回棧頂元素。

Queue

隊列是典型的先進先出的容器。LinkedList 實現了Queueu接口,從而支持隊列的行爲。安全

  • offer() :將一個元素插入到隊尾。
  • peek()element() 都將在不移除的狀況下返回對頭,element() 在隊列 爲空,則拋出 NoSuchElementExceptionpeek() 方法,在列表爲空時返回 null
  • poll()remove() 移除並返回對頭。**remove()**若是隊列爲空,則拋出 NoSuchElementException。poll(),在列表爲空時返回null。

PriorityQueue

先進先出 描述了最典型的 隊列規則 。所謂 隊列規則 是指在給定一組隊列中的元素的狀況下,肯定下一個彈出隊列的元素的規則。 先進先出 聲明的是下一個元素應該是等待時間最長的元素。數據結構

PriorityQueue是優先級隊列,其聲明下一個彈出的元素是最須要的元素(具備最高優先級)。ide

當你在PriorityQueue 上調用 offer() 方法來插入一個對象時,這個對象會在隊列中排序。默認的排序將使用對象在隊列隊列中的天然順序,可是你能夠經過提供本身的Comparator接口實現來修改這個順序。PriorityQueue 能夠確保當你調用peek()、poll()和remove()方法時,獲取的元素將是隊列中優先級最高的元素。函數

使用示例:性能

public class TestQueueElement {
    public int getAge() {
        return age;
    }

    public int getSex() {
        return sex;
    }

    public String getName() {
        return name;
    }

    private int age;
    private int sex;
    private String name;

    public TestQueueElement(int age, int sex, String name) {
        this.age = age;
        this.sex = sex;
        this.name = name;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + " { name:" + name + " ,age:" + age + ",sex:" + sex + "}";
    }
}



/** * 1.以age字段的升序進行排序, * 2.在age相同時,以name字段的長度的升序進行排序 */
public class TestQueueElementComparator implements Comparator<TestQueueElement> {
    @Override
    public int compare(TestQueueElement o1, TestQueueElement o2) {
        int result = 0;

        if (o1.getName().length() > o2.getName().length()) {
            result += 1;
        } else if (o1.getName().length() < o2.getName().length()) {
            result -= 1;
        }

        if (o1.getAge() > o2.getAge()) {
            result += 2;
        } else if (o1.getAge() < o2.getAge()) {
            result -= 2;
        }

        return result;
    }
}


public class Main {

    public static void main(String[] args) {

        PriorityQueue<TestQueueElement> pq = new PriorityQueue<>(10, new TestQueueElementComparator());

        pq.offer(new TestQueueElement(10, 0, "name1"));
        pq.offer(new TestQueueElement(9, 0, "name1"));
        pq.offer(new TestQueueElement(11, 0, "name1"));
        pq.offer(new TestQueueElement(10, 0, "name11"));
        pq.offer(new TestQueueElement(10, 0, "name111"));
        pq.offer(new TestQueueElement(8, 0, "name"));
        pq.offer(new TestQueueElement(8, 0, "name111"));

        while (!pq.isEmpty()) {
            System.out.println(pq.poll());
        }
    }
}

輸出結果:
TestQueueElement { name:name ,age:8,sex:0}
TestQueueElement { name:name111 ,age:8,sex:0}
TestQueueElement { name:name1 ,age:9,sex:0}
TestQueueElement { name:name1 ,age:10,sex:0}
TestQueueElement { name:name11 ,age:10,sex:0}
TestQueueElement { name:name111 ,age:10,sex:0}
TestQueueElement { name:name1 ,age:11,sex:0}

複製代碼

Set

存入 Set 的每一個元素都必須是惟一的,由於 Set 不保存重複元素。加入 Set 的元素必須定義 equals() 方法以確保對象的惟一性。Set 具備和 Collection 徹底同樣的接口,Set接口不保證維護元素的次序。優化

  1. HashSet:爲快速查找而設計的Set,存儲順序沒有意義。存入 HashSet 的元素必須定義 hashCode()。若是沒有其餘限制,這應該是默認的選擇,由於其對速度進行了優化。
  2. TreeSet:按照比較結果的升序保存對象,將元素存儲在紅-黑樹數據結構中。保持次序的Set,底層爲樹結構。使用它能夠從Set中提取有序的序列。元素必須實現 Comparable 接口。
  3. LinkedHashSet:按照被添加的順序保存對象,使用了鏈表來維護元素的插入順序。具備HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。因而在使用迭代器遍歷 Set 時,結果會按元素插入的次序顯示。元素也必須實現 hashCode()方法。

SortedSet

SortedSet中的元素能夠確保處於排序狀態。this

  • Comparator comparator(): 返回當前Set使用的Comparator;或者返回null,表示以天然方式排序。
  • Object first():返回容器中的第一個元素。
  • Object last():返回容器中的最後一個元素。。
  • SortedSet subSet(fromElement,toElement)生成此Set的子集,範圍由fromElement(包含)到 toElement(不包含)的鍵肯定。
  • SortedMap headSet(toElement)生成次Set的子集,由小於toElement的元素組成。
  • SortedMap tailSet(fromElement)生成此Set的子集,由大於fromElement的元素組成。

Map

對Map中使用的鍵的要求與對 Set 中的元素的要求是同樣的。任何鍵都必須具備一個 equals() 方法,若是建被用於散列Map,那麼他還必須具備恰當的 hashCode() 方法,若是鍵被用於 TreeMap ,那麼它必須實現 Comparable。

  1. HashMap:提供最快的查找技術,沒有按照任何明顯的順序來保存元素。HashMap就是利用對象的 hashCode() 進行快速查詢的,此方法能夠快速提升性能。在沒有其餘的限制時,HashMap是默認選擇。插入和查詢 鍵值對 的開銷是固定的。能夠經過構造器設置容量和負載因子,已調整容器的性能。
  2. TreeMap:基於紅黑樹的實現,按照比較結果的升序保存鍵值對,它們會被排序(次序由 Comparable 或 Comparator 決定)。TreeMap的特色在於,所獲得的結果是通過排序的。TreeMap 是惟一的帶有 subMap() 方法的,他能夠返回一個子樹。
  3. LinkedHashMap:按照插入順序保存鍵,只是比Hashmap的速度慢一點,而在迭代訪問時反而更快,由於它是使用鏈表維護內部次序的。
  4. WeakHashMap:弱鍵映射,容許釋放映射所指向的對象。若是映射以外沒有引用指向某個 ,則此 能夠被垃圾收集器回收。
  5. ConcurrentHashMap:一種線程安全的Map,它不涉及同步加鎖。
  6. IdentityHashMap:使用 == 代替 equals() 對 進行比較的散列映射。

SortedMap

使用SortedMap(TreeMap 是其現階段的惟一實現),能夠確保鍵處於排序狀態。

  • Comparator comparator(): 返回當前Map 使用的Comparator;或者返回null,表示以天然方式排序。
  • T firstKey():返回Map中的第一個鍵。
  • T lastKey():返回Map中的最後一個鍵。
  • SortedMap subMap(fromKey,toKey)生成此Map的子集,範圍由fromKey(包含)到 toKey(不包含)的鍵肯定。
  • SortedMap headMap(toKey)生成次Map的子集,由鍵小於toKey的全部鍵值對組成。
  • SortedMap tailMap(fromKey)生成此Map的子集,由鍵大於或等於fromKey的全部鍵值對組成。

迭代器

只是使用容器,不知道或者說不關心容器的類型,僅僅是獲取容器中的元素。迭代器統一了對容器的訪問方式。

迭代器是一個對象,它的工做是遍歷並選擇序列中的對象,而客戶端程序員沒必要知道或關心該序列底層的結構。此外,迭代器一般被稱爲 輕量級對象:建立它的代價小。可是對迭代器的使用是由限制的:

  1. 使用方法 iterator() 要求容器返回一個 IteratorIterator 將準備好返回序列中的第一個元素。
  2. 使用 next() 獲取序列中的下一個元素。
  3. 使用 hasNext() 檢查序列中是否還有元素。
  4. 使用 remove() 將迭代器新近返回的元素刪除。意味着在調用 remove() 以前必須先調用 next()

迭代器使用示例:

public static void main(String[] args) {

        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < 10; i++) list.add(i);

        System.out.println(list.toString());

        Iterator<Integer> it = list.iterator();

        //遍歷迭代器對象,循環結束,it中不在具備元素
        while (it.hasNext()) {
            Integer integer = it.next();
            System.out.println(integer.toString());
        }

        it = list.iterator();
        
        //刪除it迭代器中的元素,同時list中對應的元素也被刪除
        for (int i = 0; i < 5; i++) {
            it.next();
            it.remove();
        }
    }
複製代碼

ListIterator

ListIterator 是一個更增強大的 Iterator 的子類型,它只能用於各類 List 類的訪問。 儘管 Iterator 只能向前移動,可是 ListIterator 能夠雙向移動 )。它還能夠產生相對於迭代器在列表中指向的當前位置的前一個和後一個元素的索引,而且可使用 set() 方法替換它訪問過的最後一個元素。你能夠經過調用 listIterator() 方法產生一個指向 List 開始處的 ListIterator,而且還能夠經過調用 listiterator(n) 方法建立一個一開始就指向列表索引爲n的元素處的 ListIterator

使用示例:

public class Test {
    private String id;

    public Test(int id) {
        this.id = "test:" + id;
    }

    @Override
    public String toString() {
        return id;
    }
}


 public static void main(String[] args) {

        List<Test> list = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Test test = new Test(i);
            list.add(test);
        }

        System.out.println(list.toString());

        ListIterator<Test> it = list.listIterator();
        while (it.hasNext()) {
            //it.next() 獲取下一個元素
            //it.nextIndex() 獲取下一個元素的索引
            //it.previousIndex() 獲取上一個元素的索引
            System.out.println(it.next() + "," + it.nextIndex() + "," + it.previousIndex() + ";");
        }

        System.out.println();

        // 是否有上一個元素
        while (it.hasPrevious()) {
            //it.previous() 獲取上一個元素
            System.out.println(it.previous() + " ");
        }

        it = list.listIterator(3);
        while (it.hasNext()) {
            it.next();
            it.set(new Test(20));
        }
        System.out.println(list.toString());
    }
複製代碼

散列和散列碼

散列碼是 相對惟一 的,用以表明對象的int值,它是經過將對象的某些信息進行轉換而生成的。hashCode() 是根類Object中的方法,所以全部的Java 對象均可以產生散列碼。

首先,使用散列的目的在於:想要使用一個對象來查找另外一個對象。

其次,散列的價值在於速度:散列將鍵保存在某處,以便可以很快找到。存儲一組元素最快的數據結構是數組,因此使用它來表示鍵的信息 (注意;所說的是鍵的信息,而不是鍵自己)。因爲數組不保存鍵自己,而是經過鍵對象生成一個數字,將其做爲數組的下標,這個數字就是散列碼,由定義在Object中的、且可能由你的類覆蓋的hashCode()方法生成。

爲解決數組容量被固定的問題,不一樣的鍵能夠產生相同的下標。也就是說,可能會有衝突。所以,數組多大就不重要了,任何鍵總能在數組中找到它的位置。

因而查詢一個值得過程就是計算散列碼,而後使用散列碼查詢數組。若是可以保證沒有衝突,那可就有了一個完美的散列函數,可是這種狀況只是特例。一般,衝突由外部連接處理:數組並不直接保存值,而是保存值的list。而後對list中的值使用equals()方法進行線性查詢。這部分的查詢天然會比較慢(由於線性查詢是最慢的查詢方式),可是,若是散列函數好的話,數組的每一個位置就只有較少的值。所以,不是查詢整個list,而是快速的跳到數組的某個位置,只對不多的元素驚醒比較。從而使HashMap的查詢速度加快。

當用本身建立用做 HashMap 的鍵的類,必須在其中放置必須的方法:equals() 和 hashCode() 兩個方法。HashMap 使用 equals() 判斷當前的鍵是否與表中存在的鍵相同。hashCode()並不須要老是可以返回惟一的標識碼,可是equals()方法必須嚴格地判斷兩個對象是否相同。

如何正確的覆蓋 equals()

正確的 equals() 方法必須知足下列5個條件:

  1. 自反性:對任意x, x.equals(x) 必定返回 true。
  2. 對稱性: 對任意x和y,若是y.equals(x)返回true,則x.equals(y)也返回true。
  3. 傳遞性:對任意x,y,z,若是x.equals(y)返回true,y.equals(z)返回true,則x.equals(z)必定返回true。
  4. 一致性:對任意x和y,若是對象中用於等價比較的信息沒有改變,那麼不管調用x.equals(y)多少次,返回的結果應該保持一致,要麼一直是true,要麼一直是false。
  5. 對任意不是null的x,x.equals(null)必定返回false。

強調:默認的Object.equals() 只是比較對象的地址。

如何正確的覆蓋 hashCode()

在明白瞭如何散列以後,編寫本身的hashCode()就更具備意義了。所以設計hashCode()是最重要的因素就是:不管什麼時候,對同一個對象調用hashCode()都應該生成一樣的值。因此,若是你的hashCode()方法依賴於對象中易變的數據,此時用戶就要小心,由於數據發生變化時,hashCode() 就會生成一個不一樣的散列碼,至關於產生了一個不一樣的鍵。此外,也不該該是hashCode() 依賴於具備惟一性的對象信息,尤爲是使用this的值,這樣將沒法生成一個新的鍵,只能產生很糟糕的hashCode()。應該使用對象內有意義的識別信息。

所以,要想使hashCode()實用,它必須速度快,而且必須有意義。也就是說,它必須基於對象的內容生成散列碼。因此,散列碼沒必要是獨一無二的(應該更關注生成速度,而不是惟一性),可是經過hashCode()和 equals(),必須可以徹底肯定對象的身份。

由於在生成桶的下標前,hashCode() 還須要作進一步的處理,因此散列碼的生成範圍並不重要,只要是int便可。

好的hashCode()應該產生分佈均勻的散列碼。若是散列碼都幾種在一塊,那麼HashMap或者HashSet 在某些區域的負載會很重,這樣就不如分佈均勻的散列函數。

所以一個好的hashCode() 應該遵循的指導是:

  1. 給 int 變量result 賦予某個非零值常量。
  2. 爲對象內每一個有意義的域f(即每一個能夠作equals()操做的域)計算出一個int散列碼c;
    域類型 計算
    boolean c=(f?0:1)
    byte、char、short或int c=(int)f
    long c=(int)(f^(f>>>32))
    float c=Float.floatToIntBits(f)
    doble long L = Double.doubleToLongBits(f);
    c=(int)(L^(L>>>32))
    Object,其equals()調用這個域的 equals() c=f.hashCode()
    數組 對每一個元素應用上述規則
  3. 合併計算獲得的散列碼:result = 37 * result + c;
  4. 返回result。
  5. 檢查hashCode()最後導出的結果,確保相同的對象就有相同的散列碼。

下面即是遵循這個指導的一個示例:

public class CountedString {
    private static List<String> created = new ArrayList<>();

    public String getString() {
        return mString;
    }

    public void setString(String string) {
        mString = string;
    }

    private String mString;
    private int id;

    public CountedString(String string) {
        mString = string;
        created.add(string);

        for (String s2 : created) {
            if (s2.equals(mString)) id++;
        }
    }

    @Override public String toString() {
        return "String:" + mString + " id:" + id + " hashCode():" + hashCode();
    }

    @Override public int hashCode() {
        int result = 17;
        result = 37 * result + mString.hashCode();
        result = 37 * result + id;
        return result;
    }

    @Override public boolean equals(Object obj) {
        return obj instanceof CountedString && mString
                .equals(((CountedString) obj).mString) && id == ((CountedString) obj).id;
    }

    public static void main() {
        Map<CountedString, Integer> map = new HashMap<>();
        CountedString[] cs = new CountedString[5];

        for (int i = 0; i < cs.length; i++) {
            cs[i] = new CountedString("hi");
            map.put(cs[i], i);
        }

        System.out.println(map);

        for (CountedString cst : cs) {
            System.out.println("Looking up:" + cst);
            System.out.println(map.get(cst));
        }
    }

}

複製代碼

Java完整類庫

完整容器類庫
相關文章
相關標籤/搜索