1. 程式人生 > 其它 >被討厭的勇氣

被討厭的勇氣

引入

陣列的不足:

  1. 長度固定,且一旦確定,無法更改

  2. 儲存的必須為同一型別

  3. 使用陣列進行增刪元素比較麻煩

集合

  1. 可以動態儲存任意多個物件,使用方便

  2. 提供了增刪改查的方法:add、remove、set、get

  3. 使用集合新增刪除等都比較簡潔

框架體系圖!

map 和 collection沒有關係

 //1. 集合主要是兩組 (單列集合,雙列集合)
 //2. Collection介面 有兩個重要子介面 List Set ,他們的實現都是單列集合
 //3. Map介面 實現的子類是雙列集合 Key-Value

Collection介面

  1. Collection實現子類,可以存放多個元素,每個元素可以是Object

  2. Collection介面 沒有直接的實現子類,是通過 List 和 Set 實現的

  3. 常用方法:

     
     import java.util.ArrayList;
     import java.util.List;
     
     public class collection_01 {
         @SuppressWarnings({"all"})
         public static void main(String[] args) {
     
             //1. 集合主要是兩組 (單列集合,雙列集合)
             //2. Collection介面 有兩個重要子介面 List Set ,他們的實現都是單列集合
             //3. Map介面 實現的子類是雙列集合 Key-Value
             List list = new ArrayList();
     //       add: 新增單個元素
             list.add("jack");
             list.add(134.9);
             list.add(false);
             System.out.println("list = " + list);
     //       remove: 刪除指定元素
     //       list.remove(0);//刪除第一個元素
             list.remove("jack");
             System.out.println("list = " + list);
     
     //       contains: 查詢元素是否存在
             System.out.println(list.contains(false));
     //       size: 獲取元素個數
             System.out.println(list.size());
     //       isEmpty: 判空
             System.out.println(list.isEmpty());
     //       clear: 清空全部元素
             list.clear();
             System.out.println("list = " + list);
     //       addAll:新增多個元素
             list.add("haikeyi");
             List list2 = new ArrayList();
             list2.add("niu");
             list2.add(957);
             list.addAll(list2);//引數是Collection c
             System.out.println("list = " + list);
     //       containsAll:查詢多個元素是否都存在
             System.out.println(list.containsAll(list2));
             System.out.println("list = " + list);
     //       removeAll:刪除多個元素
             System.out.println(list.removeAll(list2));
             System.out.println("list = " + list);
        }
     }
  4. Collection介面遍歷元素方式:使用 Iterator迭代器

    • Iterator 物件稱為迭代器

    • 所有實現了 Collection 介面的集合類都有一個 iterator() 方法,用以返回一個實現了Iterator介面的物件

    • Iterator 僅用於遍歷集合,Iterator 本身不存放物件

    •  
       import java.util.ArrayList;
       import java.util.Iterator;
       import java.util.List;
       
       public class Collection02 {
           @SuppressWarnings({"all"})
           public static void main(String[] args) {
               //建立一個list
               List list = new ArrayList();//向上轉型
               list.add("li");
               list.add(false);
               list.add(998);
               System.out.println("list = " + list);
       
               //遍歷list
               //1. 建立 list 對應的迭代器
               //2. 使用while迴圈遍歷
               Iterator iterator = list.iterator();//獲取iterator物件
               while (iterator.hasNext()){//判斷是否還有下一個元素
                   //在呼叫next() 前 必須先hasNext判斷
                   //否則丟擲 NosuchElementException
       
                   //編譯型別為 Object 執行型別視情況而定
                   Object ob = iterator.next();//next 返回的元素型別為 Object
                   System.out.println(ob);
                   //next() 作用 1.指標移向下一元素 2.輸出指向的元素
              }
       
       
       
               //1. 當退出while迴圈後,這時 iterator 指向最後的元素
               //2. 若需要再次遍歷,需要重置迭代器
               //3. 快捷生成 while -> 'itit' + enter
       
               iterator = list.iterator();//重置
               while (iterator.hasNext()) {
                   Object next =  iterator.next();
                   System.out.println(next);
       
              }
       
          }
       }
       
    • 增強for 遍歷

       
       import java.util.ArrayList;
       import java.util.List;
       
       
       public class CollectionFor {
           @SuppressWarnings({"all"})
           public static void main(String[] args) {
               //建立一個list
               List list = new ArrayList();//向上轉型
               list.add("li");
               list.add(false);
               list.add(998);
               System.out.println("list = " + list);
       
               //1. 增強for 用於集合或陣列
               //2. 底層仍是用 迭代器
               //3. 增強for就是簡化的 迭代器遍歷
               //4. 快捷輸入 'I' + enter
               for (Object obj :
                       list) {
                   System.out.println(obj);
              }
       
               //用於陣列
               int[] ints = {6, 7, 9, 4, 1, 5};
               for (int i : ints){
                   System.out.println(i);
              }
          }
       }

List 介面

List 介面是Collection 介面 的子介面

  1. List集合類中 元素有序(新增順序和取出順序一致)、且可重複

  2. List集合中的每個元素都有其對應的順序索引 (類似於陣列)

  3. List 介面的實現類特別多

  4. 常用方法:

    //subList 返回的區間為 fromIndex <= subList < toList



只要是List介面的實現類,

遍歷都有三種方式: 迭代器、增強for、普通for

 //迭代器
 Iterator iterator = list.iterator();
 while (iterator.hasNext()) {
     Object next = iterator.next();
     System.out.println(next);
 }
 
 //增強for
 for (Object o : list) {
     System.out.println(o);
 }
 
 //普通for
 for (int i = 0; i < list.size(); i++) {
     System.out.println(list.get(i));
 }

ArrayList 注意事項

  1. ArrayList 可以放任何Object 物件 ,包括 null

  2. ArrayList 是由陣列實現資料儲存的

  3. 它基本等同於Vector 但是由於它是執行緒不安全的(執行效率高)。

    在多執行緒情況下,不建議使用ArrayList

ArrayList 原始碼分析(含debug看原始碼設定)

先說結論:

  1. ArrayList 中維護了一個Object型別的陣列 elementData

     transient Object[] elementData;//transient (轉瞬即逝的) 表示該屬性不會被序列化
  2. 當首次建立ArrayList 物件時,如果使用的是無參構造器,則初始的elementData容量為0,第1次新增,則擴容 elementData 為10,如果再次擴容,則擴容 elementData 為1.5倍

  3. 如果使用的是指定大小的構造器,則初始大小為指定大小,如果再次擴容,則擴容 elementData 為1.5倍

檢視原始碼

自行debug

debug的一些設定:

使用 F7、shift + F8、F9快捷鍵,進行debug。 看原始碼

Vector

  1. 底層與 ArrayList 一樣 也是用的物件陣列,protected Object[] elementData;

  2. 是執行緒同步的,即執行緒安全,因為它的方法都有 synchronized 修飾

 

LinkedList

說明:

  1. 底層實現了雙向連結串列和雙端佇列的特點

  2. 可以新增任意元素,元素可以重複, 包括null

  3. 執行緒不安全,沒有實現執行緒同步

底層機制:

  1. 底層其實是維護了一個雙向連結串列

  2. first指向首節點,last指向尾節點,中間有prev和next指標。

    連結串列雖然不支援隨機存取,但是新增刪除效率更高。結構如圖:

 //自己Debug
 //看原始碼
 import java.util.LinkedList;
 
 public class LinkedListDebug {
     @SuppressWarnings({"all"})
     public static void main(String[] args) {
         LinkedList linkedList = new LinkedList();
 
         //Debug看原始碼
         //增加都是一樣的 用linkLast函式,尾插法
         for (int i = 0; i < 2; i++) {
             linkedList.add(i);
        }
 
         linkedList.add(100);
         linkedList.add(200);
 
         //看刪除
         linkedList.remove(2);
         //修改
         linkedList.set(1,"string");
         //獲得 也是用的    
         //checkElementIndex(index);
         //return node(index).item;
   
         linkedList.get(0);
 
         //遍歷 List的實現子類,遍歷都是三種,一樣的
         Iterator iterator = linkedList.iterator();
         while (iterator.hasNext()) {
             Object next =  iterator.next();
             System.out.println(next);
        }
 
    }
 }

Set 介面

基本介紹:

  1. 無序,沒有索引

  2. 不允許重複元素,最多包含一個null

  3. JDK API中Set介面的實現類

  • 常用方法與 Collection介面的一致

  • 遍歷方式:1. 迭代器 iterator 2. 增強for 注意不能使用索引獲取,所以普通for迴圈不行

 
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
 @SuppressWarnings({"all"})
 public class HashSet01 {
     public static void main(String[] args) {
         //Set
         //1. Set介面的實現類物件(簡稱Set實現類),不能存放重複元素
         //2. 只能存放一個null
         //3. Set存取是無序的(新增的順序和取出順序不一致)
         //4. 但取出的順序是固定的
         Set set = new HashSet();
         set.add("john");
         set.add("jack");
         set.add("smith");
         set.add(100);
         set.add(null);
         set.add("jack");
 
 
         //遍歷只有兩種方式 1. 迭代器 2.增強for(底層也是迭代器)
         //1.迭代器遍歷
         System.out.println("set" + set);
         Iterator iterator = set.iterator();
         while (iterator.hasNext()) {
             Object next =  iterator.next();
             System.out.println(next);
        }
         //2. 增強for
         for(Object o :set ){
             System.out.println(o);
        }
 
 
    }
 }

HashSet

  1. 實現了 Set 介面

  2. 底層其實是HaspMap

  3. 可以存放null,但只能存放一個(Set介面實現類物件都是這樣的,元素不能重複)

  4. 存取順序不一致(Set實現物件共有)

  5. HashSet底層是用 HashMap實現的,是(陣列+連結串列+紅黑樹)結構

看 add 的底層機制,深刻理解不能加入相同元素

 

 //預設建立時,其他欄位都是預設的(如int型別的變數就是0...),只有載入因子賦值了
 public HashMap() {
     this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
 }

 

 //add
 //1.
 public boolean add(E e) {
     return map.put(e, PRESENT)==null;
 }
 
 //2.
 public V put(K key, V value) {
     //hash 的計算是使資料雜湊存貯的關鍵
     return putVal(hash(key), key, value, false, true);
 }
     //2.1
     static final int hash(Object key) {
         int h;
         //無符號右移16位   ^ 是異或運算 (同為0,異為1)
         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
 
 //3.
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                boolean evict) {
     
 }
  //3.1 ( resize )擴容
     final Node<K,V>[] resize() {
       
    }

針對hash值的思考:

  1. (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 這一步獲得一個很大的hash值

  2. 將上面很大的一個hash值 跟 此時table的(length-1)進行與(&)運算,就能將這個巨大的hash值,雜湊到table上。


針對擴容的思考:

  1. 預設載入因子( loadFactor ) 大小為 0.75f

  2. 根據原始碼,每次新增元素都會 ++size , 所以擴容時,不管此時 table 陣列 有沒有滿,只要size達到 閾值, 都會擴容。

  3. 根據原始碼,擴容時,是看size是否達到閾值。

//putVal 部分原始碼
if (++size > threshold)
resize();

針對樹化連結串列結點的思考:

//putVal 部分原始碼
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);

//treeifyBin 部分原始碼
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
 1. TREEIFY_THRESHOLD 的值是8,因此,當某一table中的某一連結串列達到8個時,將會進行變化的樹化
 1. 但樹化有先決條件,(見treeifyBin 部分原始碼),若此時table中某一連結串列結點數大於等於8時,但 table的長度小於 MIN_TREEIFY_CAPACITY(值為64)時,就無法進行樹化,而是將需要新增的結點,繼續掛在原來的連結串列上。同時,resize()   table,將table擴容。

針對新增結點情況的思考:

 //putVal 部分原始碼
 
 //情況一:table 是新建的,預設為null,則先進行擴容
 if ((tab = table) == null || (n = tab.length) == 0)
     n = (tab = resize()).length;
 //情況二:此時table 已存在,且根據hash算出來的 table 的索引處暫無結點,直接新增
 if ((p = tab[i = (n - 1) & hash]) == null)
     tab[i] = newNode(hash, key, value, null);
 //情況三:table的索引處已有結點
 // 1. 新新增的Node 與 已有的結點不同。主要看:(原始碼:)
 // p.hash == hash &&
 //       ((k = p.key) == key || (key != null && key.equals(k))) )
 //   判斷是否是同一結點 ( 判斷關鍵 -> equals !)
 // 2. 如果是該索引處的連結串列中,已有同樣的結點,則不再新增
 // 3. 若沒有,則掛在連結串列尾。(注意樹化)
 // 4. 如果此時已經是紅黑樹,則不是掛在連結串列尾而是(原始碼)
 // e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 else

LinkedHashSet

  1. HashSet 的子類

  2. 底層是 LinkedHashMap 維護了一個數組 + 雙向連結串列

  3. LinkedHashMap 根據hashCode 值決定存放位置,同時使用雙向連結串列維護元素次序,所以看上去元素的取出順序和插入順序相同。

  4. 不允許新增重複元素

結構解讀:

  1. LinkedHashSet 中維護了hashtable 和 一個雙向連結串列(LinkedHashSet 有head 和 tail)

  2. 每個節點有 before 和 after 用於形成雙向連結串列

  3. 新增時,與HashSet類似,先求hash值,然後呼叫map.put -> putVal ;如果有重複的元素則不新增。同時 LinkedHashMap 重寫了 HashMap 的 newNode 方法,保證新新增的結點,是滿足雙向連結串列的。

  4. 遍歷時通過 head -> afer -> after -> ... -> tail 遍歷,保證與插入時相同。

其實底層都是HashMap

LinkedHashMap 重寫了 HashMap 的 newNode 方法,保證新新增的結點,是滿足雙向連結串列的。

 

TreeSet

底層其實是TreeMap

使用預設構造器的話,那麼TreeSet也是無序的

(方法都與 HashSet 相同)唯一區別:通過構造器可以使加入的元素有序。

注意:重寫compare後,判斷元素是否相同就要根據compare方法來決定,若該方法返回0,則不能新增相 應的新元素。

//底層其實是TreeMap
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
//使用構造器讓元素有序  例子
//根據重寫的compare方法,來確定順序
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//通過String 的 compareTo 方法,返回一個int
return ((String)o1).compareTo((String)o2);
}
});

add 的 底層也是TreeMap

與HashSet 相同,新增時,呼叫put方法,

public boolean add(E e){
return m.put(e, PRESENT)==null;//PRESENT 是TreeSet的常量。
}

 

Map介面

實現類的通用特點:

  1. Map用於儲存具有對映關係的 Key-Value (雙列集合)

  2. Map中的 Key和 Value 可以是任何引用型別的資料,會封裝到HashMap$Node中

  3. Map的Key 不允許重複,(因為用Key 算出hash值,儲存在table中)

  4. Map的Value 可以重複

  5. Map中的 Key 可以為null,Value 也可以為null。(因為key不能重複, 所以null 的key只能由一個)

  6. 新增時使用 put(...) 方法。當新新增的key 已存在,但 value 不同時,會覆蓋已存在的該key對應的value

     //map為Map介面的實現物件(實現類的物件)
     map.put("n1","jack");
  7. key與value 存在單向的一對一關係,即可以通過key 找到對應的value

     map.get("n1");//傳入一個key,返回對應的value
  8. 常用 String 類作為key (但別的資料型別也可以作為key)

  9. 結構: 除了 table 以外,還有一個EntrySet,為了方便遍歷。

  • 如圖:

    • EntrySet是為了遍歷而存在的一個Node的副本,其中的k - v都是指向table表中的k和v的引用

    • 因為 Node 實現了 Entry 介面,所以EntrySet中,可以存放Node

       class Node<K,V> implements Map.Entry<K,V>
    • 為了方便單獨遍歷k 和 v ,還另外提供了兩個內部類 KeySet 和 Values (分別實現了Set介面 和 Collection 介面)

    • Entry是Map的內部介面

    • Map的結構:有table 、entrySet、KeySet、Values


關於map的結構的思考:

反正記住他們的作用,都是通過方法獲得,然後再遍歷。

  1. entrySet是HashMap的一個Set 集合屬性,用於遍歷k-v

    2. KeySet 是HashMap的內部類,用於遍歷 k
    2. Valuse 是HashMap的內部類,用於遍歷 v

常用方法:

  1. put(" k "," v "); 新增

  2. remove(" k "); 根據鍵值刪除

  3. get(" k "); 根據鍵值獲取值

  4. size(); 獲取元素個數

  5. isEmpty(); 判空

  6. clear(); 清空map

  7. containsKey(" k "); 查詢鍵值是否存在,返回鍵值所對應的值

遍歷:

 package com.lxly.hsp.chapter14.map_;
 
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Set;
 
 @SuppressWarnings({"all"})
 public class HashMap01 {
     public static void main(String[] args) {
         //同樣:存入和取出順序不同,因為table中的資料是雜湊過的
         HashMap map = new HashMap();
         map.put("k", "v1");
         map.put("jame", "v2");
         map.put("zhang", "v3");
         map.put("li", "v4");
 
         System.out.println("-----------分割線------------");
         //三種遍歷方式:
 
         //第一種: values 遍歷 v   (用的不多)
         Collection values = map.values();
         //1.1 迭代器
         Iterator iterator1 = values.iterator();
         while (iterator1.hasNext()) {
             Object next = iterator1.next();
             System.out.println(next);
        }
         //1.2 增強for
         for (Object v : values) {
             System.out.println(v);
        }
 
         System.out.println("-----------分割線------------");
         //第二種:KeySet 遍歷 k (可以在遍歷時 ,通過k獲取v)
         Set keySet = map.keySet();
         //2.1 迭代器
         Iterator iterator = keySet.iterator();
         while (iterator.hasNext()) {
             Object next =  iterator.next();
             System.out.println(next + "--" + map.get(next));
 //           System.out.println(next);
        }
         //2.2 增強for
         //   重 點 掌 握
         for (Object k: keySet) {
             //通過get方法獲得value
             System.out.println(k + "-" + map.get(k));
 //           System.out.println(k);
        }
 
         System.out.println("-----------分割線------------");
         //第三種:entrySet 遍歷 k-v
         Set set = map.entrySet();
         //3.1 迭代器
         Iterator iterator2 = set.iterator();
         while (iterator2.hasNext()) {
             Object next = iterator2.next();//向上轉型
             System.out.println(next);//next 執行型別是 HashMap$Node
             //輸出格式 k1=v2
             //是因為 HashMap$Node 重寫了 toString