java集合源碼詳解


一 Collection接口

1.List

  1.1ArrayList

  特點

1.底層實現基於動態數組,數組特點根據下表查找元素速度所以查找速度較快.繼承自接口  Collection ->List->ArrayList

 2.擴充機制 初始化時數組是空數組,調用add()第一次存放元素時長度默認為10,滿了擴容機制 原來數組 + 原來數組的一半 使用數組copy()方法

    2.1 構造一個初始容量為空列表。(不給指定大小時為空)

    

    2.2使用add()方法,將指定的元素追加到列表的末尾。集合的長度 size ++ 先使用后自增1

    

     2.3 將數組長度設置為10

    

    2.4判斷長度是否大於當前數組 調用grow()方法擴容

    

     2.5  newCapacity = oldCapacity + (oldCapacity >> 1);  oldCapacity >> 1其實就是oldCapacity 除以2,返回一個copyOf的數組

     

 3.線程不安全 ,效率高

1.2vector

  1.采用動態數組,初始化時默認長度10

  

  2.由於是默認初始化 沒有給定capacityIncrement 所以為 0  所以新數組長度為 oldCapacity + oldCapacity 也就是增長為原來2倍,如果給定增長值capacityIncrement 

  后,擴充為:原來大小+增量也就是newCapacity= oldCapacity + capacityIncrement

  

1.3LinkedList

   1.適合插入,刪除操作,性能高

   

   2.有序的元素,元素可以重復

   3.原理 

    3.1插入時只需讓88元素記住前后倆元素, 刪除88元素只需讓88前面元素(6)記住該元素后面(35)元素 時間復雜度 O(1)< O(logn)< O(n)< O(n^2)

  

    3.2對於某一元素,如何找到它的下一個元素的存放位置呢?對每個數據元素ai,除了存儲其本身的信息之外,

還需存儲一個指示其直接后繼存放位置的指針。在每個結點中再設一個指向前驅的指針域

    

    

    prior 前面節點指針域 next后一個節點指針域

    

    

     (圖片來源<<數據結構 java語言描述>>)

     3.3 add() 時在尾部追加元素 ...等等

    

2 set接口

 2.1HashSet

  1.元素輸出時不允許元素重復,允許元素為null (因為string 重寫了hashCode,equals方法所以可以去重)

  

   2.無序的

   3.實現源碼

    3.1 使用默認無參初始化時 其實使用的是HashMap實現的

       構造一個新的空集;支持的stt>HashMaps/tt>實例具有默認的初始容量(16)和負載因子(0.75)。

    

     3.2 向HashSet添加元素時 使用HashMap的put()方法,把值作為HashMap的key (key肯定不可以重復的,唯一的)

    

    所以詳細實現源碼下面介紹HashMap時再說

    4. HashSet的子類LinkedHashSet 底層使用雙向鏈表

      4.特點 4.1.按照添加順序輸出 ; 元素不重復

         4.2使用無參構造的初始化容量(16)和負載因子(0.75)構造一個新的空鏈接哈希集。

          

          4.3調用父類HashSet 構造 初始化的對象是LinkedHashMap() 關系又跑到map上去了

          

            4.4LinkedHashMap()調用他爹(HashMap)的構造 

          

          4.5 調用LinkedHashMap() 構造時 設置了hash順序

          accessOrder //這個鏈接哈希映射的迭代方法:true表示訪問順序,false表示插入順序。

          

  2.2TreeSet

  

   1.可以排序(元素按照自然排序進行排列 根據KEY值排序)且不允許元素重復

    

    為什么會自動排序呢?

     1.1 默認無參構造時 初始化的是一個TreeMap對象

      

        2.2 調用add()方法時調用的是Map接口提供的put()方法 而treeMap 實現了map接口的put(); 所以底層存儲就是treeMap  紅黑樹

方法 源碼如下    

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
View Code

    2.不允許元素為null

      

   3.排序想按照對象的某個值排序,某個類實現接口 Comparator 重寫 compare()方法

private static void treeSet(){              //TreeSet

      Catss cat1=new Catss(2,"貓1",1);
      Catss cat2=new Catss(1,"貓2",2);
      Catss cat3=new Catss(3,"貓3",3);
      Catss cat4=new Catss(1,"貓4",4);
      Catss cat5=new Catss(1,"貓4",4);
      //TreeSet存儲數據要使用比較器
      TreeSet<Catss> t=new TreeSet<>(new CatComparator());
      t.add(cat1);
      t.add(cat2);
      t.add(cat3);
      t.add(cat4);
      t.add(cat5);
       for (Object f:t){
           System.out.println(f+"輸出順序是按照年齡排序遞增(年齡有一樣的將會覆蓋一個,只輸出一個)");//輸出順序是按照年齡排序遞增(年齡有一樣的將會覆蓋一個,只輸出一個)
       }
  }

class CatComparator implements Comparator<Catss> {
    @Override
    public int compare(Catss o1, Catss o2) {
        return o1.getAge()-o2.getAge();//根據年齡排序
    }

}
View Code

二 Map接口

 2.1 HashMap

   特點:

     1.無序的鍵值對存儲一組對象

     2.key必須保證唯一,Value可以重復

     3.key 和value可以為null

     4.線程不安全

      5.默認加載因子0.75,默認數組大小16 6.實現基於哈希表 (數組+鏈表/紅黑樹)

    

         源碼:1.加載因子0.75f

      

      2.實現map接口的put()方法 hashMap重寫put

       2.1為了一次存取便能得到所查記錄,在記錄的存儲位置和它的關鍵字之間建立一個確定的對應關系H,

以H(key)作為關鍵字為key的記錄在表中的位置,稱這個對應關系H為哈希(Hash)函數。按這個思想建立的表為哈希表。

       2.2解決Hash沖突

       根據設定的哈希函數H(key)和所選中的處理沖突的方法,將一組關鍵字映射到一個有限的、

地址連續的地址集(區間)上,並以關鍵字在地址集中的“象”作為相應記錄在表中的存儲位置,如此構造所得

的查找表稱之為“哈希表”,這一映射過程也稱為“散列”,所以哈希表也稱散列表。--<<數據結構 java語言描述>>

       2.3子類LinkHashMap,按照添加順序輸出特點參考上面LinkedHashSet

put()方法的大致流程如下:

  1. 首先,將插入的鍵值對通過hash算法計算出對應的桶位置。

  2. 檢查該桶位置上是否已經存在元素。如果該桶位置上沒有元素,則直接將鍵值對插入到該位置上。

  3. 如果該桶位置上已經存在元素,則需要進行鏈表或紅黑樹的操作。

    • 如果該位置上的元素個數小於8個,則將該元素添加到鏈表中。

    • 如果該位置上的元素個數已經達到8個,則將鏈表轉換為紅黑樹。如果該位置上的元素已經是紅黑樹,則直接將該鍵值對插入到紅黑樹中。

  4. 在插入鍵值對后,如果HashMap的大小超過了負載因子(load factor)*當前數組長度,則需要進行擴容操作。

    • 在擴容時,會創建一個新的數組,並將原來數組中的元素重新分配到新的數組中。

    • 擴容后,新的數組的大小為原來數組的兩倍,並將閾值(threshold)也擴大為原來的兩倍。

  5. 最后,將鍵值對插入到桶位置中,並返回null或原來桶位置上的值。

public V put(K key, V value) {
    // 計算鍵的hash值
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    // 獲取當前HashMap的table數組,如果為null,則進行初始化
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 計算key在數組中的下標i
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 如果當前位置為空,則直接創建新節點
        tab[i] = newNode(hash, key, value, null);
    else {
        // 如果當前位置不為空,判斷當前節點是否為要添加的key
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            // 如果是樹節點,則進行紅黑樹添加操作
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 如果當前位置是鏈表,則遍歷鏈表,查找key是否已存在
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    // 如果鏈表中沒有找到key,則創建新節點添加到鏈表尾部
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        // 如果鏈表長度已達到閾值,則進行鏈表轉換為紅黑樹的操作
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果e不為null,則說明key已存在,將其舊值替換為新值
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // 在LinkedHashMap中使用,記錄節點訪問順序
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 如果HashMap中鍵值對數量已達到閾值,則進行擴容
    if (++size > threshold)
        resize();
    // 在LinkedHashMap中使用,記錄節點插入順序
    afterNodeInsertion(evict);
    return null;
}

 

2.2Hashtable

 

   特點 1.線程安全調用put()時synchronized修飾

     

     2.默認構造大小11擴充因子0.75

     

       3.無序的,K和V 都不能為null ,允許元素重復

       

      

2.3TreeMap

    特點:1.可以排序(自然排序123),元素可以重復,K值不能重復  

public static void treeMapTest(){
        TreeMap<String, String> treeMap = new TreeMap<>();
        treeMap.put("1","A");
        treeMap.put("4","A");
        treeMap.put("2","B");
        treeMap.put("3","C");
        treeMap.forEach((k,v)->System.out.println("K值:"+k+"----->value值:"+v));
    }

    //K值:1----->value值:A
    //K值:2----->value值:B
    //K值:3----->value值:C
    //K值:4----->value值:A
View Code

      2.排序想按照對象的某個值排序,某個類實現接口 Comparator 重寫 compare()方法; 上面TreeSet 一樣

補充

1.負載因子0.75

除此之外,hash表里還有一個“負載極限”,“負載極限”是一個0~1的數值,“負載極限”決定了hash表的最大填滿程度。當hash表中的負載因子達到指定的“負載極限”時,hash表會自動成倍地增加容量(桶的數量),並將原有的對象重新分配,放入新的桶內,這稱為rehashing。HashSet和HashMap、Hashtable的構造器允許指定一個負載極限,HashSet和HashMap、Hashtable默認的“負載極限”為0.75,這表明當該hash表的3/4已經被填滿時,hash表會發生rehashing。“負載極限”的默認值(0.75)是時間和空間成本上的一種折中:較高的“負載極限”可以降低hash表所占用的內存空間,但會增加查詢數據的時間開銷,而查詢是最頻繁的操作(HashMap的get()與put()方法都要用到查詢);較低的“負載極限”會提高查詢數據的性能,但會增加hash表所占用的內存開銷。程序員可以根據實際情況來調整HashSet和HashMap的“負載極限”值。如果開始就知道HashSet和HashMap、Hashtable會保存很多記錄,則可以在創建時就使用較大的初始化容量,如果初始化容量始終大於HashSet和HashMap、Hashtable所包含的最大記錄數除以負載極限,就不會發生rehashing。使用足夠大的初始化容量創建HashSet和HashMap、Hashtable時,可以更高效地增加記錄,但將初始化容量設置太高可能會浪費空間,因此通常不要將初始化容量設置得太高 --<<java瘋狂講義>>
View Code

2.map集合的遍歷

public class Map09 {
    public static void main(String[] args){
        hashMap();
    }
    private static void hashMap(){
        Map<Integer,String> map=new HashMap<>();//Map<key,value>
        map.put(1,"key必須保證唯一");
        map.put(2,"無序的");
        map.put(3,"put方法");
        map.put(null,null);
        System.out.println(map.size());
        System.out.println("得到key=2的值:"+map.get(2));
        //第一種遍歷 lambda表達式遍歷forEach();非常簡便
        System.out.println("---------lambda表達式遍歷forEach()-----------------------");
        map.forEach((i, s) -> System.out.println("key="+i+"     value:"+s));
        //第二種遍歷  entrySet()方法
        System.out.println("---------entrySet()方法使用foreach遍歷-----------------------");
        Set<Map.Entry<Integer,String>> entry=map.entrySet();
        for (Map.Entry e:entry){
            System.out.println(e.getKey()+"->"+e.getValue());
        }
        System.out.println("---------entrySet()方法使用Iterator遍歷-----------------------");
        Set entries = map.entrySet();
        Iterator iterator = entries.iterator();
        while(iterator.hasNext()){
            Map.Entry entrys = (Map.Entry) iterator.next();
            Object key = entrys.getKey();
            String value = map.get(key);
            System.out.println(key+"        "+value+"-----");
        }
        System.out.println("----------keySet()方法---------------------");
        //第三種 遍歷鍵 keySet()方法
        Set<Integer> key=map.keySet();
        for (Integer i:key){
            String va=map.get(i);
            System.out.println(i+"->"+va);
        }
        //第四種遍歷值map.values()
        System.out.println("----------values()方法---------------------");
        Collection<String> vs=map.values();
        for (String str:vs){
            System.out.println(str);
        }

    }
}
View Code

 

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM