java_15:Java容器

Java集合

前言:
集合和數組都可以對多個數據進行存儲操作,簡稱Java容器

數組存儲的特點:

1.一旦初始化,長度不可變

2.一旦數組定義好,數據類型也就確定了

3.可以存儲有序可重複的數據,對於無序不可重複的需求不能滿足

4.數組提供的方法有限,對於添加、刪除、插入數據等操作不方便,效率也不高

5.沒有現成的方法獲取數組中實際有效的元素個數

而集合可以靈活的處理數組的一些缺點


java_15:Java容器

一、Collection接口

單列集合,用來存儲一個一個的對象

1.1 List接口

可以存儲有序的可重複的數據,是動態的數組,可以存儲null值

1. ArrayList
  1. jdk1.2實現,底層數據結構:Object[] elementData.

  2. 線程不安全,但效率高,查找元素的操作方便

  3. 源碼分析

    (1)jdk7:
    *初始化:
    ArrayList list=new ArrayList();//底層創建一個長度爲10的Object數組;
    *添加元素:
    add元素時,直接想數組的對應下標一次添加
    list.add(123)===elementData[0]=new Integer(123)
    *擴容:
    如果此次添加元素的操作導致底層數組容量不夠,則觸發擴容。擴容爲原來的1.5倍。並將原數組的內容複製到新數組
    oldCapacity + (oldCapacity >> 1) 
    *注意:
    儘量使用帶參數的構造器,指明初始化容量,儘量避免擴容,因爲擴容用到Arrays.copyOf(),這個操作代價很高
    ArrayList list=new ArrayList(int capacity);
    (2)jdk1.8
    *初始化:
    ArrayList list=new ArrayList();//底層創建一個長度爲0的數組Object
    *添加元素:
    第一次調用add()時,底層才創建長度爲10的數組,並將數據加入的數組中
    list.add(123)
    *擴容:
    與jdk7無異
    總結:
    jdk7中的ArrayList的對象的創建類似於單例模式的餓漢式,
    jdk8中的ArrayList的對象的創建類似於單例模式的懶漢式,延遲數組創建,節省內存

2. LinkedList

  1. jdk 1.2實現,底層數據結構:雙向鏈表

    //節點Node類
    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;
        }
    }
    //LinkedList
    內部聲明瞭Node類型的兩個屬性first和last,默認爲null
    transient Node<E> first;
    transient Node<E> last; 

java_15:Java容器

  1. 線程不安全,對於頻繁的插入和刪除操作效率較高

  2. 源碼分析

    *初始化:
    LinkedList lis=new LinkedList();//內部聲明瞭Node類型的兩個屬性first和last,默認爲null
    *添加元素:
    list.add(123);//將123封裝到Node中,創建了Node對象
    其中,Node類的定義體現了LinkedList的雙向鏈表的說法

3.Vector

  1. jdk1.0實現,List接口的古老實現類,底層數據結構:Object[] elementData

  2. 線程安全,但效率不高

    public synchronized boolean isEmpty() {
           return elementCount == 0;
       }
  3. 源碼分析:

    *初始化:
    jdk7和jdk8中通過Vector()構造器創建對象時,底層都創建了長度爲10的數組
    *擴容:
    默認擴容爲原來的數組長度的2倍

4.List接口常用方法

    * 增:add(Object obj)
    * 刪:remove(int index)/remove(Object obj)--remove(new Integer(2))
    * 改:set(int index,Object ele)
    * 查:indexOf(Obj obj)/lastIndexOf(Obj obj)/get(int index)
    * 插:add(int index,Object obj)
    * 長度:int size();
    * 遍歷:
        Iterator 迭代器方式(hasnext(),next()){next:先下移指針,再返回元素。開始是在第一個元素的前一位置}
        foreach()
        普通的循環

5.怎樣得到一個下線程安全的List

1.使用Vector--不建議,同步的,訪問速度慢,開銷大
2.Collections.synchronizedList(list)
    List<String> list = new ArrayList<>(); 
    List<String> synList = Collections.synchronizedList(list); 
3.concurrent併發包下的CopyOnWriteArrayList<>()
    List<String> list = new CopyOnWriteArrayList<>(); 

6.CopyOnWriteArrayList

1.讀寫分離:

寫操作在一個複製的數組進行,讀操作還是在原數組進行,互不影響

寫操作需要加鎖,防止併發寫入時導致寫入數據丟失

寫操作結束後要把原始數組指向新的複製數組

2.特點:寫時複製,讀寫分離。在寫的時候,允許讀操作,可以提高讀操作的性能

3.適合場景:讀多寫少

//寫操作加鎖
public boolean add(E e) {     final ReentrantLock lock = this.lock;     lock.lock();     try {         Object[] elements = getArray();         int len = elements.length;         Object[] newElements = Arrays.copyOf(elements, len + 1);         newElements[len] = e;         setArray(newElements);         return true;     } finally {         lock.unlock();     } } 

final void setArray(Object[] a) {     array = a; } 
//讀操作
@SuppressWarnings("unchecked") private E get(Object[] a, int index) {     return (E) a[index]; } 

缺陷:java_15:Java容器

1.2 Set接口

(1)存儲無序的不可重複的數據

以HashSet爲例說明:
(1)無序性:不等於隨機性,存儲的數據在底層數組中並非按照數組索引的順序添加, 而是根據數組的哈希值進行添加
(2)不可重複性:保證添加的元素按照equals()判斷時,不能返回true
     即相同的元素只能添加一個

(2)向Set中添加的數據,其所在的類一定要重寫hashCode()和equals()

(3)重寫的hashCode()和equals()儘可能保持一致性,相等的對象必須具有相等的散列碼

1.HashSet

  1. 底層數據結構:HashMap(),數組+鏈表

  2. 線程不安全,可以存儲null

  3. 源碼分析

    添加元素的過程:以HashSet爲例
    (1)調用元素a所在類的hashCode()方法,計算元素a的哈希值
    (2)此哈希值接着通過某種算法計算出HashSet底層數組的存放位置(即爲:索引位置)
    (3)判斷數組此位置是否已有元素:
        如果沒有,則元素添加成功,---》情況1
    如果有其他元素b(或以鏈表形式存在的多個元素),則比較元素a與元素b的hash值;
    (4)如果hash值不相同,則元素a添成功---》情況2
    如果hash值相同,進而需要調用元素a所在equals()方法,
    (5)  equals()方法返回true,元素a添加失敗
          equals()返回false,則元素a添加成功---》情況3
    說明:
       對於添加成功的情況2和3而言,元素a與已經存在指定索引位置上數據以鏈表的方式存儲
    jdk7:元素a放到數組中,指向原來的元素,鏈表中a在首位
    jdk8:原來數組中的元素指向a,鏈表中a放在尾部
    總結:
       七上八下
    

2.LinkedHashSet

  1. 是HashSet的子類,因爲添加了Linked,所以遍歷內部數據時 可以按照添加的順序遍歷

  2. 對於頻繁的遍歷操作,效率高於HashSet

  3. 源碼分析:底層是LinkedHashMap

    LinkedHashSet作爲HashSet的子類,在添加數據的同時,
    每個數據還維護兩個引用,記錄此數據前一個數據和後一個數據
    優點:對於頻繁的遍歷操作,LinkedHashSet效率高於HashSet
    

3.TreeSet

  1. 底層數據結構:紅黑樹,可以按照添加元素的指定屬性進行排序

  2. 源碼分析

    1.向TreeSet中添加的數據,要求是相同的類對象,不能添加不同類的對象
    2.兩種排序方式:自然排序和定製排序
    3.自然排序中,比較兩個對象是否相同的標準爲compareTo()返回0,不再是equals()
    4.定製排序中,比較兩個對象是否相同的標準爲compa()返回0,不再是equals()
    5.構造器可以傳入一個比較器做參數    
       public TreeSet(Comparator<? super E> comparator) {
           this(new TreeMap<>(comparator));
       }
    @Test
       public void test4(){
    
           Comparator comparator=new Comparator() {
               //按照年齡從小到大排列
               @Override
               public int compare(Object o1, Object o2) {
                   if (o1 instanceof Person && o2 instanceof Person) {
                       Person p1 = (Person) o1;
                       Person p2 = (Person) o2;
                       return Integer.compare(p1.age,p2.age);
                   }else {
                       throw new RuntimeException("輸入的數據類型不匹配");
                   }
               }
           };
           TreeSet set1=new TreeSet(comparator);//將比較器傳進去
           set1.add(new Person("ZhaoMin", 20));
           set1.add(new Person("WangYiBo", 22));
           set1.add(new Person("XiaoZhan", 28));
           set1.add(new Person("XiaoZhan", 26));
           Iterator iterator=set1.iterator();
           while (iterator.hasNext()) {
               System.out.println(iterator.next());
           }
       }
    
4.常用方法

使用的都是Collection中聲明過的方法

5.TreeSet\HashSet\LinkedHashSet的區別

java_15:Java容器

二、Map接口

  1. 雙列集合,存儲key-value數值對的數據。entry{key,value}

  2. Map中的key:
    無序的不可重複的,用Set存儲所有的key;
    key所在類要重寫equals和hashcode

3.Map中的value:
無序的、可重複的,使用Collection存儲所有的value
value所在的類要重寫equals

4.Map中的entry:
無序的、不可重複的
使用Set存儲所有的entry

1.HashMap
  1. Map的主要實現類,線程不安全,效率高。

  2. 可以存儲null的key和value,鍵值對

java_15:Java容器

  1. 底層數據結構:

    1.7:數組+鏈表

    1.8:數組+鏈表+紅黑樹

  2. 源碼分析:
(1)jdk1.7
    *初始化:
    HashMap map=new HashMap();//在實例化以後,底層創建了長度爲16的一維數組Entry[] table
    *底層結構:
        數組+鏈表
    *put(key,value):
    (1)首先調用key1所在類的hashCode()計算key1的哈希值,此哈希值經過某種算法以後,得到在數組Entry中的存放位置;
   (2)如果此位置的上的數據爲空,則key1-value1添加成功---情況1
   (3)如果此位置上的數據不爲空,(意爲着在此位置已經存在一個或多個數據(以鏈表形式存在)),比較key1和已經存在的一個或多個數據的哈希值:
      都不相同:key1-value1添加成功---情況2
      和某一數據(key2-value2)相同,繼續比較:調用key1所在類的equals(key2):
         如果equals()返回false:key1-value1添加成功---情況3
         如果equals()返回true:使用value1替換value2
   補充:關於情況2、3:此時key1-value1和原來的數據以鏈表的形式存儲
   *擴容:在不斷添加的過程,會涉及到擴容問題,
        默認的擴容方式:當超出臨界值12(且要存放放入位置非空)時, 擴容爲原來容量的2倍,並將原有的數據複製過來
(2)jdk8:
*初始化:
    new HashMap();//底層沒有創建一個長度爲16的數組
*map.put(key,value):
    -首次調用put()方法創建一個長度爲16的數組Node[]
    -與1.7類似
    -使用紅黑樹:
     當數組中的某一個索引位置上的元素以鏈表形式存在的數據個數>8,且當前數組的長度>64時,此時此索引位置上的索引數據改爲使用紅黑樹存儲,提高效率,
*底層結構:
        數組+鏈表+紅黑樹
*補充常量:
 * EFAULT_INITIAL_CAPACITY:HashMap的默認容量:16
 * DEFAULY_LOAD_FACTORY:HashMap的默認加載因子:0.75
 * threshold:擴容的臨界值=容量*填充因子: 16*0.75》=12
 * TREEIFY_THRESHOLD:Bucket中鏈表長度大於該默認值,轉化爲紅黑樹:8
     當小於該值師,又轉爲鏈表。8--符合泊松分佈
     紅黑樹使用的頻率不高,到8的時候按照泊松分佈,出現的概率非常小        0.000000006 
 * MIN_TREEIFY_CAPACITY:桶中的Node被樹化時最小的hash表容量:64
 * 擴容是爲了儘量減少鏈表的使用
  1. 負載因子值得大小對HashMap有什麼影響

    (1)負載因子的大小決定了HashMap的數據密度
    (2)負載因子越大,密度越大,越容易碰撞,數組中鏈表越長,造成查詢、插入時比較的次數增多,性能下降
    (3)負載因子越小,越容易觸發擴容,數據密度越小,發生碰撞機率變小,數組中鏈表越短,查詢和插入時比較次數越少,性能會更高。 但會浪累一定的內容空間,而且經常擴容影響性能。建議初始化時預設大一點的空間。
    (4)負載因子設置爲0.7-0.75,此時平均檢索長度接近於常數
    

6.實現細節:

(1)確定桶的下標(key在數組中的索引)

①取模:hash%capacity()---性能不高,如果hash爲負值,則索引也爲負,不可取
②位運算:hash&(length-1)--提高性能,解決負數問題,length=2^n
hash&(length-1),length-1=11111...,所以&之後得到的數組下標肯定在
    0--length-1(2^n-1)的範圍內
 爲什麼是2^n,就是爲了讓它-1之後得到的是一個二進制表示全爲1的結果

(2)擴容--動態擴容
java_15:Java容器
擴容時:capacity爲原來的2倍,使用resize()擴容,需要將原數組中的鍵值對插入新的數組中。並重新計算桶下標

java_15:Java容器

2.LinkedHashMap

  1. 繼承於HashMap,保證在遍歷map元素時,可以按照添加的順序實現遍歷
原因:在原有的HashMap底層結構基礎上,添加了一對指針head和tail,維護了一個雙向鏈表指向前一個和後一個       
對於頻繁的遍歷操作,此類執行效率高於HashMap
  1. LinkedHashMap 重要的是以下用於維護順序的函數,它們會在 put、get 等方法中調用。

    void afterNodeAccess(Node<K,V> p) {
       //當一個節點被訪問時,如果 accessOrder 爲 true,則會將該節點移到鏈表尾部。也就是說指定爲 LRU 順序之後,在 每次訪問一個節點時,會將這個節點移到鏈表尾部,保證鏈表尾部是近訪問的節點,那麼鏈表首部就是近久未 使用的節點
    } 
    void afterNodeInsertion(boolean evict) {
       //在 put 等操作之後執行,當 removeEldestEntry() 方法返回 true 時會移除晚的節點,也就是鏈表首部節點 first
       //removeEldestEntry() 默認爲 false,如果需要讓它爲 true,需要繼承 LinkedHashMap 並且覆蓋這個方法的實現, 這在實現 LRU 的緩存中特別有用,通過移除近久未使用的節點,從而保證緩存空間足夠,並且緩存的數據都是 熱點數據。
    } 
    

    3.LRU緩存

java_15:Java容器

3.Map中常用的方法:
添加:put(Object key,Object value)
刪除:remove(Object key)\clear()
修改:put(Object key,Objec value)
查詢:get(Object key)
長度:size()
遍歷:keySet()/values()/entrySet()
     Set set=map.keySet();
        Iterator iterator=set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        //2.Collection values();//遍歷所有的value
        Collection collection=map.values();
        for (Object obj : collection) {
            System.out.println(obj);
        }
        //3.entrySet();//遍歷所有的鍵值對Entry
        //--方式一:
        Set set1=map.entrySet();
        for (Object obj : set1) {
            //entrySet集合中所有的元素都是entry
            Map.Entry entry=(Map.Entry)obj;
            System.out.println(entry.getKey()+"-->"+entry.getValue());

        }
        System.out.println("====方式二====");
        //--方式二:
        Set set2=map.keySet();
        Iterator iterator1=set2.iterator();
        while (iterator1.hasNext()) {
            Object key=iterator1.next();
            Object value=map.get(key);
            System.out.println(key+"-->"+value);
        }

4.TreeMap

保證按照添加的key-value對進行排序,實現排序遍歷,此時考慮key的自然排序、定製排序底層使用紅黑樹

向TreeMap中添加key-value,要求key必須是由同一個類創建的對象  因爲要按照key進行排序:自然排序(默認)、定製排序 
 //定製排序
    @Test
    public void test1(){
        Person p1 = new Person("Tom", 12);
        Person p2 = new Person("Jerry", 10);
        Person p3 = new Person("Lion", 15);
        Person p4 = new Person("Jeff", 15);

        Map map=new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Person && o2 instanceof Person) {
                    Person p1 = (Person) o1;
                    Person p2 = (Person) o2;
                    return Integer.compare(p1.age,p2.age);
                }
                throw new RuntimeException("輸入的類型不匹配");
            }

        });
        map.put(p1, 90);
        map.put(p2, 89);
        map.put(p3, 96);
        map.put(p4, 88);

        Set entry=map.entrySet();
        Iterator iterator=entry.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

}

java_15:Java容器

5.Hashtable

(1)古老的實現類,線程安全,效率低

(2)不能存儲null的key和value

6.Properties

常用來處理配置文件,key-value都是String類型

7.HashMap和Hashtable的區別:

(1)線程安全:
    HashMap線程不安全效率高,Hashtable線程安全效率低
(2)存儲值:
    HashMap:可以存儲null的key和value
    Hashtable:不能存儲null的key和value
(3)元素次序:
    HashMap 不能保證隨着時間的推移 Map 中的元素次序是不變的。
(4)HashMap 的迭代器是 fail-fast 迭代器。每次檢查一下結構有沒有變化,如果發生變化就拋出異常ConcurrentModificationException
  結構發生變化是指添加或者刪除至少一個元素的所有操作,或 者是調整內部數組的大小,僅僅只是設置元素的值不算結構發生變化。

8.ConcurrentHashMap

  1. ConcurrentHashMap 和 HashMap 實現上類似,主要的差別:ConcurrentHashMap 採用了分段鎖 (Segment),每個分段鎖維護着幾個桶(HashEntry),多個線程可以同時訪問不同分段鎖上的桶,從而使其併發 度更高(併發度就是 Segment 的個數)。

  2. Segment 繼承自 ReentrantLock

    static final class Segment<K,V> extends ReentrantLock implements Serializable { 
    
       private static final long serialVersionUID = 2249069246763182397L; 
    
       static final int MAX_SCAN_RETRIES =         Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; 
    
       transient volatile HashEntry<K,V>[] table; 
    
       transient int count; 
    
       transient int modCount; 
    
       transient int threshold; 
    
       final float loadFactor; } 
    final 
    
  3. 默認的併發級別爲 16,也就是說默認創建 16 個 Segment。

    static final int DEFAULT_CONCURRENCY_LEVEL = 16; 
    

    4.size操作
    每個 Segment 維護了一個 count 變量來統計該 Segment 中的鍵值對個數。

    /**  * The number of elements. Accessed only either within locks  * or among other volatile reads that maintain visibility.  */ 
    transient int count; 
    

    java_15:Java容器

    java_15:Java容器

8.TreeMap/HashMap/LinkedHashMap/ConcurrentHashMap

java_15:Java容器

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