JAVA基礎複習(二)

    本着重新學習(看到什麼複習什麼)的原則,這一篇講的是JAVA的集合類。看了諸位大神的解釋後詳細的查了一些東西,記錄下來,也感謝各位在網絡上的分享!!!

    針對集合類,其實平時接觸最多的應該就是根據不同的應用場景使用的各種各樣功能強大的List或者Map,今天來總結一下,深入調查一下其中的區別和關聯。這張圖是很詳盡的一張關係網絡,可以先從這張圖入手。

    我首先從左上角的Iterator接口開始。

    1.Iterator接口和Iterable接口:

    說到Iterator就不得不提到Iterable接口,從名稱上就知道這兩者有着什麼貓膩,able表示能力,那麼實現Iterable接口就擁有了某種能力,但是能力是什麼實質上就在於Iterable接口內返回了一個Iterator接口的實例。就相當於是一個賦予能力,一個規範能力能做的事情。我們再回溯一下,那爲什麼一定要有兩個不同的接口去做所謂的兩件事呢?爲什麼不是在賦予能力的同時規範能力,只繼承或實現一個接口一舉兩得呢?實質上是與地址相關。(此時我突然發現了一個問題,明天查清楚。。。mark一下。。。)舉例說明,數組是順序的存儲結構,我們可以用下標的方式訪問數組中的任意元素,但是在增加刪除操作的時候,需要移動大量的元素,而對於鏈表而言,鏈表是鏈式的存儲結構,由於元素在鏈表中不是順序存儲,而是通過指向該元素的指針將元素關聯起來,那麼在進行增加刪除操作的時候只需要修改對應元素的指針就好,但是如果我們需要找到鏈表中的某一個元素,便只能從第一個元素開始。而Iterator接口中的三個方法(hasNext(),next(),remove())正是基於當前位置的,當我們稍後看一下當前位置的具體概念會發現當前位置都是在Iterator接口的實現中定義的,而如果直接繼承Iterator接口,必然會帶着位置信息進行每一步操作,單線程考慮好像沒什麼問題,該訪問訪問,從第一個元素開始就成了,但是如果兩個或多個線程同時訪問,並且此時線程間相差着幾個位置,查詢的時候應該怎麼辦呢?所以結論是不能直接繼承Iterator接口,所以纔有了一步又一步的繼承與實現,在真正處理數據結構關係的時候處理位置的問題。關於當前位置的理解我是這樣認爲的,現在只看一條之路(Iterator<——Collection<——List<——AbstractList<——ArrayList),跳過中間的所有實現路徑,我們看一下ArrayList是怎麼做到當前位置的指定的。

     /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        //我們暫且省去實現的其他方法
        ......
    }

    我們可以看到在ArrayList中定義了一個名爲Itr的內部類實現了Iterator接口,並且包含三個變量,cursor(代表了返回的下一個元素的下標),lastRet(代表了返回最後一個元素的下標,如果沒有最後一個元素,則返回-1)和expectModCount(期望的ArrayList修改的次數),而每一個接口方法的實現都是圍繞着幾個帶着位置信息的變量進行的更多的操作。同理,LinkedList中有名爲ListItr的內部類實現ListIterator接口,而ListIterator接口繼承自Iterator接口。

    2.Collection接口:

    Collection接口擁有着諸多子接口的,併爲諸多子接口提供常用的接口方法,當然,也包括取得Iterator接口對象用於集合的輸出。常用的方法(size(),isEmpty(),contains(),add(),remove(),toArray()等)都一直被繼承其子接口的類提供着實現。具體的區別等到描述到該集合類再進行總結。

    3.Queue接口:

    提到Queue接口,先來複習一下數據結構中的隊列。隊列是嚴謹的線性表結構,僅允許先進先出(FIFO),相當於是隻有一個入口就是隊尾,出口也只有一個便是隊首。可以分爲兩種,鏈式隊列和順序隊列,順序隊列會先申請好空間,而後進行操作,使用期間不會釋放,並且是固定長度,所以會有空間浪費的問題,而鏈式隊列會在操作中申請和釋放新的結點,並且不會固定長度,所以不會有空間浪費的問題,但會有一些操作時間上的消耗。這就是數據結構中的隊列一些概念。而在Queue接口的定義中並不總是FIFO的方式排序元素,即方法的定義是相同的但具體實現在各種不同的隊列排序策略中是不同的。如優先級隊列或者LIFO隊列,雖然實現不同,但是必須指定元素順序的定義。

    進去看過源碼會發現,感覺Queue接口的定義中是兩套東西啊,(add(),remove(),element())和(offer(),poll(),peek())兩組,我摘抄一點源碼和註釋出來比對一下就會知道原因了。

    /**
     ......
     * @return {@code true} (as specified by {@link Collection#add})
     * @throws IllegalStateException if the element cannot be added at this
     *         time due to capacity restrictions
     ......
     */
    boolean add(E e);

    /**
     ......
     * @return {@code true} if the element was added to this queue, else
     *         {@code false}
     ......
     */
    boolean offer(E e);

    /**
     ......
     * @return the head of this queue
     * @throws NoSuchElementException if this queue is empty
     */
    E remove();

    /**
     ......
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    E poll();

    /**
     ......
     * @return the head of this queue
     * @throws NoSuchElementException if this queue is empty
     */
    E element();

    /**
     ......
     * @return the head of this queue, or {@code null} if this queue is empty
     */
    E peek();

    可以看到每一組相對的方法,實質上做的是一樣的事情(咳咳,我沒摘抄具體的方法定義原因,想了解的可以直接看源碼),但是返回值上都有區別,前者會拋出異常等待處理,後者會返回false或者null。

    從源碼正上方的@see註解可以看到跳轉,發現會有分別繼承AbstractQueue類和實現BlockingQueue接口的。

    AbstractQueue是一個抽象類,繼承了AbstractCollection抽象類和實現了Queue接口,在其中提供了基於offer,poll和peek方法的add,remove和element方法,並且提供了clear和addAll方法,從註釋說明看來“拓展該類的隊列必須最少實現一個不允許插入null值的offer()方法,如果不能滿足條件,請考慮爲AbstractCollection創建子類”,故這個類中的實現適用於不允許包含null值的Queue。

    BlockingQueue接口是繼承了Queue接口的,從註釋說明看來“BlockingQueue接口主要用於生產者-消費者隊列,但還支持Collection接口,其是線程安全的。”也可以得知,BlockingQueue的方法有四種形式,不同的方式處理結果也不同,從方法的定義和處理方法上也可以明確BlockingQueue是一種帶有阻塞機制的隊列。

操作 拋出異常 特殊值 阻塞 超時

插入

add(e) offer(e) put(e) offer(e, time, unit)
刪除 remove() poll() take() poll(time, unit)
檢查 element() peek() not applicable not applicable

    前兩列的區別與Queue接口內的對應方法返回定義相同,後兩種的則說明了當隊列沒有空間時,隊列會一直阻塞線程直到拿到數據或者中斷,也就是阻塞列;當隊列沒有空間時,隊列會等待一定時間,如果超出一定的時間會中斷的超時列。

    /**
     * Inserts the specified element into this queue, waiting if necessary
     * for space to become available.
     ......
     */
    void put(E e) throws InterruptedException;

    /**
     * Inserts the specified element into this queue, waiting up to the
     * specified wait time if necessary for space to become available.
     ......
     */
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

    圖中的Deque接口是繼承了Queue接口,根據doc可知它實現的是“雙端隊列”,是一種具有隊列和棧兩種性質的數據結構。它還可以將輸入或輸出端口進行限制,從而形成一端輸出(輸入)受限的雙端隊列(一個端點允許插入和刪除,另一個端點只允許插入(輸出)的雙端隊列)。不過據度娘說“儘管雙端隊列看起來似乎比棧和隊列更靈活,但實際上在應用程序中遠不及棧和隊列有用。”。

    關於Queue其中更具體的分類如是否有界,單雙端,是否阻塞,是否存在優先級等又分別形成了更多的實現類,根據其依據的不同的數據結構,達成不同的功能實現,詳盡的分類日後詳細學習。(mark*2。。。)

    4.List接口:

    List接口繼承了Collection接口,並且在List接口中存在(ListIterator<E> listIterator();正如上文中所述,ListIterator接口是繼承Iterator接口的)返回一個List的迭代器。它是一個有序的集合,可以準確的通過位置索引去控制每一個元素在列表中的位置,並且可以達成列表內元素搜索。與Collection不同的是,List是允許重複值的,並且在一些實現類中也同樣允許空元素(null)。List集合的三個實現類包括ArrayList,LinkedList和Vector,三個子類還分別實現了RandomAccess接口(ArrayList和Vector支持隨機訪問)和Deque接口(LinkedList),這表示他們在某些地方可以進行分類。

類別 底層數據結構 查詢速度 增刪速度 線程安全 效率
ArrayList 數組
Vector 數組
LinkedList 鏈表

    數組的查詢由於有索引的原因,比只能從首元素逐個讀取下一個元素地址的鏈表查詢速度快一些。但是由於鏈表的每個元素中都存有下一個元素的地址,故元素的增加刪除又比之數組快一些(數組則需要在所選元素索引位置上將他的下一個元素進行左移或右移完成刪除和增加的操作)。

    在ArrayList接口的源碼中可以發現,實質上我們平時使用的是不定長的ArrayList(基於動態數組,但是如果已知ArrayList能達到的容量,在初始化時指定長度應該會節省開銷)。因爲它定義了一個DEFAULT_CAPACITY,即如果我們不給定長度,它會給定一個默認長度10,如果我們給定長度,那麼它將返回一個長度爲給定值的對象。但是實際使用中我們通常不會只給定10個元素,這就是ArrayList的擴容。通過在ArrayList中使用add方法添加超過10個元素可以發現,它會先使用ensureCapacityInternal方法判定數組此時是否是空數組,如果是便會賦予一個最小容量DEFAULTCAPACITY_EMPTY_ELEMENTDATA,否則便會賦予一個DEFAULT_CAPACITY和(size+1)返回的最大值作爲最小所需容量(minCapacity)。此後ensureCapacityInternal方法內調用ensureExplicitCapacity方法來確認擴容大小,如果最小擴容所需容量(minCapacity)大於數組當前的長度,那麼就需要擴容。擴容調用grow方法通過右移一位的方式進行擴容1.5倍(int newCapacity = oldCapacity + (oldCapacity >> 1); >>是帶符號右移位運算符,>>1是右移一位,也就相當於是除以2,便相當於新的容量會在原來容量的基礎上加一半的容量,也就是1.5倍)。相關方法源碼如下:

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

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

    相比較而言,同樣基於動態數組的Vector在擴容時直接增加兩倍。所以在節約空間方面使用ArrayList更加有利。

    private void grow(int minCapacity) {
        // overflow-conscious code
        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);
    }

    相比較於ArrayList的風光無限,Vector看起來比較沒落。線程安全的特性反而讓Vector降低了效率,並且由於雙倍擴容,所以在數據量相當大的時候會消耗更多的內存空間和資源。Vector接口在線程方面通過synchronized關鍵字進行同步,使得在Vector接口內是十分安全的,但是在某些場景下還是會出現同樣的問題。所以綜合考慮使用ArrayList接口更佳。

    LinkedList接口維護的是一個雙向鏈表(在查閱過程中發現,在jdk1.7開始,便從雙向循環鏈表改爲雙向鏈表,這個需要注意一下),即它的每一個節點都有兩個指針分別稱爲前驅和後驅。而雙向循環鏈表和雙向鏈表的區別就在於頭結點的前驅指向尾結點,同理尾結點的後驅也指向頭結點,形成一個環狀。舉兩個例子說明下。

    LinkedList接口的頭尾:在雙向鏈表中會有頭結點和尾結點的區別,而對於雙向循環鏈表只需要一個Header,並且在初始化時將Header的前驅和後驅均指向Header本身,形成環。

    LinkedList接口的插入:雙向鏈表第一次插入時需要判斷第一個元素是否是null,也就是說該列表是否爲空。雙向循環鏈表只需要分配內存,而後節點指針分別指向對應的前驅和後驅就好,如果爲空,則指向自己,即保證爲環。

    LinkedList接口的這個變化是要注意的,感覺在面試裏被考到的機率還挺大的。

    5.Set接口:

    Set接口一直以其不支持重複值而聞名,所以很多時候在一些數組去重的場景都會出現Set的身影。根據類內說明可知,Set接口是數據抽象建模,接口特性是不允許出現重複元素,也就代表着最多隻會有一個值爲null的元素。但是我沒有從中發現另一個常見概念,即“List是有序的,Set是無序的”。實際上這裏所謂的有序和無序指是否按照添加元素的順序存儲,如果有序,那麼輸出的時候也會是按照添加元素的順序輸出,而不是我們意義上的有“順序”。但是Set接口的每一個實現類內部可能都有自己的排序方式,表現上是沒有按照添加元素順序輸出,即無序,但是按照某些實現類內的排序方式來說,可能Set都是有序的(如TreeSet),我簡單測試了一些數據,每次HashSet接口的輸出都是相同的,所以這個點我還有一些疑問。

package com.day_2.excercise_1;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

public class TrySet {
	public static void main(String[] args) {
		Set<String> set = new HashSet<String>();
		set.add("test1");
		set.add("test2");
		...
		set.add("test24");
		set.add("test25");
		set.forEach(e-> System.out.print(e+"->"));
		System.out.println();
		
		Set<String> set2 = new LinkedHashSet<String>();
		set2.add("test1");
		set2.add("test2");
		...
		set2.add("test24");
		set2.add("test25");
		set2.forEach(e-> System.out.print(e+"->"));
		System.out.println();
		
		Set<String> set3 = new TreeSet<String>();
		set3.add("test1");
		set3.add("test2");
		...
		set3.add("test24");
		set3.add("test25");
		set3.forEach(e-> System.out.print(e+"->"));
		
	}
}

    根據HashSet源碼可見,初始化了一個用transient修飾的HashMap對象,並且HashMap的所有構造方法都是在構造HashMap,所以HashSet是基於HashMap實現的。而HashMap是key,value形式,但是我們在使用HashSet內方法時並沒有添加過另一個值的原因是在HashSet中定義了一個虛擬值PRESENT作爲value。看到這裏我們再回頭看HashSet的唯一,無序等特點可以發現均來自HashMap的key值唯一和無序存儲特性(由於調用add方法也是調用HashMap的put方法,所以可見HashSet是按元素的哈希值存儲,所以是無序的)。

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

    // HashMap put()
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    // HashMap hash()
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    關於transient修飾符的作用,明明已經實現了Serializable接口,又爲何要使用transient修飾符,並且在類內聲明瞭兩個私有方法writeObject和readObject呢?通過打斷點測試可以發現JDK在進行對象序列化(ObjectOutputStream)和反序列化(ObjectInputStream)會判定是否自己聲明瞭同名方法並使用同名方法。這裏mark一篇文章(https://my.oschina.net/demons99/blog/1929309),文章中的舉例還是很清晰的,我這裏百度翻譯了一下,如下:

    For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn’t even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt.

    “例如,考慮哈希表的情況。物理表示是包含鍵值項的哈希桶序列。條目所在的bucket是其鍵的散列代碼的函數,一般來說,不能保證從JVM實現到JVM實現是相同的。事實上,從一次運行到另一次運行,它甚至不能保證是相同的。因此,接受哈希表的默認序列化格式將構成嚴重的錯誤。序列化和反序列化哈希表可能會產生不變量嚴重損壞的對象。”

    大致瞭解了一些,因爲無法保證JVM的實現相同,所以不能保證每一次或通過序列化後在反序列化時能得到原有的結果。故不使用原有的接口方法而是自己定義方法。

    TreeSet是一個有序集合類,同HashSet相同TreeSet也是基於Map,不過TreeSet基於的是TreeMap。由於TreeMap底層數據結構是紅黑樹,所以基於TreeMap的TreeSet也相當於是基於紅黑樹,而紅黑樹是一個自平衡二叉查找樹,這保證了TreeSet的有序性。此時需要注意,TreeSet不能傳入null,否則會拋出NullPointerException 異常。(但是我也看到了一些文章對此有別的見解,mark一下。。。)

    /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element {@code e} to this set if
     * the set contains no element {@code e2} such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns {@code false}.
     *
     * @param e element to be added to this set
     * @return {@code true} if this set did not already contain the specified
     *         element
     * @throws ClassCastException if the specified object cannot be compared
     *         with the elements currently in this set
     * @throws NullPointerException if the specified element is null
     *         and this set uses natural ordering, or its comparator
     *         does not permit null elements
     */
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

    相比較而言,HashSet由於基於哈希表,所以速度快,而TreeSet有序,各有特長。

    6.Map接口:

    Map是替代了Dictionary的一個接口,存儲基於<key,value>形式的映射數據。常用實現類有HashMap,TreeMap,Hashtable,SortedMap。

    HashMap是一個根據鍵的哈希值存儲數據的Map,其每一個鍵值對也叫做Entry。底層實現是一種“數組+鏈表”的數據結構(也可稱爲哈希桶(又被稱爲開鏈法,開散列法。哈希桶的數組中存的是指針,並且數組中每一個位置都存着一段鏈表,當插入數據時不用處理哈希衝突,直接將數據鏈接在鏈表即可。參考鏈接:https://blog.csdn.net/qq9116136/article/details/80327841)),並且當鏈表的長度大於8時會轉換爲紅黑樹。HashMap集合了數組(查詢速率快,插入刪除慢)和鏈表(查詢速率慢,插入刪除快)的特性,使得操作速度均很快。同之前所講相同,HashMap也有默認大小,並且對於數據結構轉換也有對應的默認值定義。有默認值就會有擴容(resize())。在resize方法中定義了擴容大小爲原來的兩倍。(想要具體瞭解resize方法可以查看參考鏈接:https://www.cnblogs.com/winterfells/p/8876888.html

    //默認大小
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    //最大容量(如果傳入容量過大將被替換爲該值)
    static final int MAXIMUM_CAPACITY = 1 << 30;

    //鏈表轉換爲紅黑樹閾值
    static final int TREEIFY_THRESHOLD = 8;

    //樹結構還原爲鏈表閾值
    static final int UNTREEIFY_THRESHOLD = 6;

    //部分resize擴容部分代碼
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {//如果當前容量大於0
            if (oldCap >= MAXIMUM_CAPACITY) {
                //當前容量已經超過最大容量,直接返回
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }//當前容量沒有超過最大容量,返回原來的兩倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        ...
        return newTab;
    }

    此外HashMap是非線程安全的。並且是可以接受null值的(key和value都可以爲null)。

    與HashMap相對比,HashTable是線程安全的,內部方法大部分均存在synchronized修飾符。並且HashTable不允許有null值的存在(key和value都不能爲null)。所以由於線程安全的問題,HashMap效率會比HashTable高一些。

    與TreeSet介紹時相同,TreeMap是基於紅黑樹的,故可以按照key的大小順序進行排序,而HashMap和HashTable不能保證數據有序。

    LinkedHashMap則可以保證數據保持插入順序。

    我測試了一些數據,代碼如下:

package com.day_2.excercise_1;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class TryMap {
	public static void main(String[] args) {
		
		System.out.println("============HashMap============");
		
        Map<Object,Object> hashmap = new HashMap<Object,Object>();    
        hashmap.put(null, "這是key爲null的情況");
        hashmap.put("ValueNull",null);
//        hashmap.put(null,null);// 同樣可以key和value均爲null,key不可重複故暫且註釋
        
        Iterator<Map.Entry<Object, Object>> hashit = hashmap.entrySet().iterator();
        while (hashit.hasNext()) {
        Map.Entry<Object, Object> entry = hashit.next();
        System.out.println("key: " + entry.getKey() + " \r\nvalue: " + entry.getValue());
        }
        
        System.out.println("============Hashtable============");
        
        try{
        	Map<Object,Object> tablemap = new Hashtable<Object,Object>();
            tablemap.put(null, "這是key爲null的情況");
            tablemap.put("ValueNull",null);
        }catch(NullPointerException e){
        	System.out.println("捕捉了NullPointerException異常");
        }
        
        System.out.println("============LinkedHashMap============");
        
        Map<String,String> linkedmap = new LinkedHashMap<String,String>();
        
        linkedmap.put("1", "String1");
        linkedmap.put("4", "String4");
        linkedmap.put("2", "String2");
        linkedmap.put("3", "String3");
        linkedmap.put("5", "String5");
        linkedmap.put("7", "String7");
        linkedmap.put("6", "String6");
        
        Iterator<Map.Entry<String, String>> linkedit = linkedmap.entrySet().iterator();
        while (linkedit.hasNext()) {
        Map.Entry<String, String> entry = linkedit.next();
        System.out.println("key: " + entry.getKey() + " \r\nvalue: " + entry.getValue());
        }
        
        System.out.println("============TreeMap============");
        
        Map<String,String> treemap = new TreeMap<String,String>();
        //往map集合添加 key  和 value 
        treemap.put("1", "String1");
        treemap.put("4", "String4");
        treemap.put("2", "String2");
        treemap.put("3", "String3");
        treemap.put("5", "String5");
        treemap.put("7", "String7");
        treemap.put("6", "String6");
        
        Iterator<Map.Entry<String, String>> treeit = treemap.entrySet().iterator();
        while (treeit.hasNext()) {
        Map.Entry<String, String> entry = treeit.next();
        System.out.println("key: " + entry.getKey() + " \r\nvalue: " + entry.getValue());
        }
        
    }
}

    這一篇拖拖拉拉寫了大概一週,查閱了很多大佬的文章,都非常優秀,才能讓我在自己瞎琢磨源碼的時候有了一定的理解,看源碼果然是一個很枯燥的過程,但追根溯源之後學到的和記下的也着實很多。mark的點比較多,以後有時間一一解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章