1. 程式人生 > 實用技巧 >Java集合初體驗(謹個人學習記錄)

Java集合初體驗(謹個人學習記錄)

概述

Java 集合可分為 Collection 和 Map 兩種體系

  • Collection 介面 :單列資料 。定義了存取一組物件的方法的集合
    • Lits :有序列表 ,可重複的集合
    • Map :無序列表 ,不可重複的集合
  • Map 介面 :雙列資料 。儲存具有對映關係 “key” :“value” 鍵值對 的集合

Collection 介面繼承樹

Map 介面繼承樹

{name:"張三"} >> 鍵值對

Collection 介面方法

介面方法 作用
add(Object obj)
addAll(Collection coll)
新增元素
int size() 獲取有效元素的個數
void clear() 情況集合
boolean isEmpty() 是否是空集合
boolean contains(Object obj) 是否包含某個元素 ,通過 equals() 判斷是否為同一個物件
boolean containsAll(Collection coll) 是否包含某個元素 ,拿兩個集合的元素挨個比較
boolean remove(object obj) 刪除元素 ,刪除第一個匹配的元素
boolean retainAll(Collection c) 取兩個集合的交集
Object[] toArray() 轉成物件陣列
iterator() 迭代器物件 ,集合遍歷

Collection 子介面之一 :List介面 概述

  • 使用 List 替代 陣列 。
  • 元素有序 且 元素可以重複
  • 每個元素對應一個索引值
  • 常用實現類 :ArrayList 、LinkedList 、Vector [執行緒安全]

ArrayList 原始碼分析 【底層使用 陣列儲存 】查詢快 ,使用索引值查 [執行緒不安全 ]

​ jdk 1.7 :

​ 構造器初始化的時候.就建立一個 長度為 10 的陣列容量 Object[] elemrntData;

​ jdk 1.8 :

​ 構造器初始化的時候並不會建立陣列長度 ,而是在第一次 呼叫 list.add(); 方法的時候,才去建立陣列長度為 10

​ 後續操作與 jdk 1.7 無異

LinkedList 原始碼分析 【底層使用 雙向連結串列儲存】 增刪快 ,[執行緒不安全 ]

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

List 介面方法

介面方法 作用
void add(int index, Object ele); 在 index 索引位置插入 元素 ele
boolean addAll(int index, Collection eles); 從 index 索引位置開始將集合 eles 中的所有元素新增進來
Object get(int index); 獲取指定 index 索引值位置的元素
int indexOf(Object obj); 返回元素 obj 在集合中首次出現的位置
int lastIndexOf(Object obj); 返回元素 obj在當前集合中最後一次出現的的位置
Object remove(int index); 移除指定 index索引值位置的元素 ,並返回此元素
Object set(int index, Object ele); 設定指定 index索引值位置的元素為 ele
List subList(int fromIndex, int toIndex); 返回從 fromIndex 到 toIndex 索引值位置的子集合 。

**總結 :List 常用方法 **

​ 增 :add(Object obj); / add(int index, Object obj);

​ 刪 :remove(int index); / remove(Object obj);

​ 改 :set(int index, Object ele);

​ 查 :get(int index);

​ 長度 :size();

​ 遍歷 :

​ ① 、迭代器 Iterator

Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next);
}

​ ②、forEach

for (Object obj : list) {
    System.out.println(obj);
}

​ ③、for 迴圈

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

Collection 子介面之二 :Set 介面

簡介 :

  • Set 介面是Collection的子介面 ,Set 介面沒有提供額外的方法
  • Set 集合不允許包含相同的元素 ,否則會新增操作失敗
  • Set b判斷兩個物件是否相同 根據 equals() 方法 。而不是根據 “==” 運算子 。

常用實現類之一 :HashSet

HashSet 底層 :陣列 + 連結串列 的結構

  • HashSet 按照 Hash 演算法來儲存集合中的元素 ,具有很好的 存取 、查詢 、刪除 效能 。

  • 特點 :

    • 無序列表

    無序性 :不等於隨機性 。

    ​ 是指 儲存的資料在底層陣列中並非按照陣列索引的順序新增 ,而是根據資料的雜湊值確定儲存位置

    • 不是執行緒安全
    • 元素可以為 null
  • HashSet 集合判斷兩個個元素相等的標準

    • 兩個物件通過 hashCode() 方法比較相等 。並且兩個物件的 equals() 方法返回值相等 。
  • 存放在 Set 容器中的物件 ,必須重寫 equals() 方法和 hashCode(Object obj); 方法

  • 不可重複性

    ​ 是指 保證新增的元素按照 equals() 判斷時 ,不能返回 true . 即:相同的元素只能新增一個 。

  • 新增元素的過程 :以HashSet 為例 :

    ​ 向HashSet 中新增元素a ,首先呼叫元素a所在類的hashCode() 方法 ,計算元素a的雜湊值 ,

    ​ 此雜湊值接著通過某個演算法計算出在 HashSet 底層陣列中的存放位置 (索引位置),判斷

    ​ 陣列對應索引位置是否已經有元素 :

    ​ 情況一 :

    ​ 如果此位置上沒有其他元素 ,則元素a新增成功 。

    ​ 情況二 :

    ​ 如果此位置上有其他元素b(或以連結串列形式存在的多個元素),則比較元素a與元素b的hash值 :

    ​ 如果hash值不相同 ,則元素a新增成功 。

    ​ 情況三 :

    ​ 如果hash值相同 ,進而需要呼叫元素a所在類的equals() 方法 :

    ​ equals() 返回 true ,元素 a 新增失敗 。

    ​ equals() 返回 false ,則元素 a 新增成功 。


Set 實現類之三 :LinkedHashSet

  • LinkedHashSet 根據元素的 hashCode 值來決定元素的儲存位置 。但它同時使用雙向連結串列維護元素的次序 。
  • LinkedHashSet 插入效能略低 HashSet ,但在 迭代器訪問 Set 裡的全部元素時有很好的效能 。
  • 不允許集合元素重複 。

Set 實現類之三 :TreeSet

  • TreeSet 是 SortedSet 介面的實現類 。TreeSet 可以確保元素處於排序狀態 。
  • TreeSet 底層使用 紅黑樹 資料結構儲存資料 。
  • TreeSet 兩種排序方法 :自然排序定製排序 。預設情況下 TreeSet 採用自然排序

instanceof 嚴格來說是Java中的一個雙目運算子,用來測試一個物件是否為一個類的例項

1、向 TreeSet 中新增資料 ,要求必須是相同類的物件 ,例如 :要麼就都是整型 ,要麼都是字串 ,不能參半

2、兩種排序方式 :自然排序(實現 Comparable 介面) 和 定製排序(Comparator

3、自然排序中 ,比較兩個物件是否相同的標準 :comparmTo() 返回0

1、自然排序

public class User implements Comparable {
    private Integer age;
    // ... 省略set get 
    @Override
    public int compareTo(Object o) {
        if (o instanceof  User) {
            User user = (User) o;
            return this.age.compareTo(user.age);
        } else {
            throw new RuntimeException("型別不匹配 ");
        }
    }
    /*
    	Set seen = new TreeSet();
        seen.add(new User(12));
        seen.add(new User(123));
        seen.add(new User(456));
    */

2、定製排序

Comparator comparator = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof User && o2 instanceof User) {
                    User u1 = (User) o1;
                    User u2 = (User) o2;
                    return Integer.compare(u1.getAge(), u2.getAge());  // 從小到大
                    // return Integer.compare(u1.getAge(), u2.getAge());	從大到小
                } else {
                    throw new RuntimeException("型別不匹配 ");
                }
            }
        };
        Set seen = new TreeSet(comparator);
        seen.add(new User(12));
        seen.add(new User(123));
        seen.add(new User(456));

Map :介面

簡介 :

HashMap [主要實現類],執行緒不安全,可以儲存nul鍵值對

  • key :無序的,不可用重複 ,使用 Set 儲存所有的 keu ,儲存類物件 需要 重寫類的 hashCode 和 equals
  • value :無序的,可重複的 ,使用 Collection 儲存 value 。儲存類物件需要重寫 equals
  • 一個鍵值對key:value 構成一個 Entry 物件 entry無序的,不可重複讀, 使用 Set 儲存entry 。

LinkedHashMap [對於頻繁的遍歷] ,效率高於HashMap

TreeMap [排序 使用 key作為排序條件]

Hashtable [執行緒安全,效率低] jdk1.0 ,目前不推薦使用 。

面試題

HashMap 的底層 :在jdk7及以前 是 陣列 + 連結串列 結構

​ 在 jdk1.8 是 陣列+連結串列+紅黑樹 結構

HashMap的底層實現原理 ?以 jdk 1.7 為例

HashMap map = new HashMap();

​ 在例項化以後 ,底層建立了長度為 16 的一維陣列 Entry[] table.

map.put(key1,value1);

​ 首先,呼叫 key1 所在類的hashCode() 計算 key1 的雜湊值 ,此雜湊值經過演算法計算後,得到在Entry陣列中的存放位置 。

​ 如果此位置上的資料為空 ,此時的 key1-value1 新增成功 --- 情況 1

​ 如果此位置上的資料不為空 ,(此位置已存在資料 ,),比較 key1 和已經存在的資料的雜湊值 :

​ 如果 key1 的雜湊值與已經存在的資料的雜湊值不相同 ,此時 key1-value1 新增成功 --- 情況 2

​ 如果 key1 的雜湊值和已經存在的某一個數據(key2-value2)的雜湊值相同 ,則繼續比較 :呼叫 key1 所在類的equals(key2)方法 ,比較 :

​ 如果equals() 返回 false ,此時 key1-value1 新增成功 --- 情況 3

​ 如果 equals() 返回true ,使用 value1 替換 value2 .

擴容問題 :擴容為 原來的兩倍 。

補充 :關於情況2和情況3 :此時 key1-value1 和原來的資料以連結串列的形式儲存

介面方法 作用
新增、刪除、修改操作
Object put(Object key, Object value) 將指定key-value新增(修改)到當前map物件中
void putAll(Map m) 將 Map m 中的所有key-value鍵值對存放到當前map中
Object remove(Object key) 移除指定 key 的 key-value鍵值對 ,並返回 value
void clear() 清空當前 map 中的所有資料
元素查詢操作
Object get(Object key) 獲取指定 key 對應的 value
boolean containsKey(Object key) 是否包含指定的 key
boolean containsValue(Object value) 是否包含指定的 value
int size() 返回 map 中 key-value 鍵值對的個數
boolean isEmpty() 判斷當前 map 是否為空
boolead equals(Object obj) 判斷當前 map 和引數對 obj 是否相等
元檢視操作
Set keySet() 返回所有 key 構成的 Set 集合
Collection values() 返回所有的 value 構成的 Collection 集合
Set entrySet() 返回 所有 key-value 鍵值對 構成的 Set 集合

面試題

談談你對 HashMap 中 put/get方法的認識 ?

再談談 HashMap 的擴容機制 ?預設大小是多少?什麼是負載因子(或填充比)

什麼是吞吐臨界值 (或閥值 、threshold)

HashMap 原始碼中的常量


Map 原始碼分析 :jdk 1.8 (未完)

簡要說明

jdk 1.8 初始化容量 是在 第一次 put 的時候 對陣列進行初始化 。

jdk 1.7 是在構造器初始化容量大小 .

1.8 HashMap 結構圖

  • :JDK 1.8 中 涉及的 資料結構

1、位桶陣列 (定義儲存資料的陣列) 【陣列 】

transient Node<K,V>[] table;	// 鍵值對 <k,v>

2、陣列元素 Node<k,v> ,實現類 Entry 介面 【連結串列 】

Node 是單向連結串列,Node<k,v>是一個內部類 ,相當於是 一個 節點類

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    // 構造器 Hash值 ,鍵 ,值 ,下一個節點 (當前節點的下一個節點引用 。)
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    // 判斷兩個 Node 是否相等 ,若 key 和 value 都相等 ,返回true 。可以與自身比較為true /
    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

3、紅黑樹 【樹結構】

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // 父節點
    TreeNode<K,V> left;	 // 左子樹
    TreeNode<K,V> right;  // 右子樹 
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;	// 樹節點顏色屬性 
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }

    /**
         * Returns root of tree containing this node.
         */
    // 返回當前節點的 根節點 。
    final TreeNode<K,V> root() {
        for (TreeNode<K,V> r = this, p;;) {
            if ((p = r.parent) == null)
                return r;
            r = p;
        }
    }
  • 二 : 構造器
// 1、初始化時 指定初始容量和負載因子 .如果指定的(初始容量為負數或者負載因子非正).丟擲異常 IllegalArgumentException
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}
// 2、指定初始容量 使用預設的負載因子 0.75f
public HashMap(int initialCapacity) {
    // 呼叫的還是 兩個引數的構造器 
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 3、沒有引數 ,預設初始容量 16 ,預設負載因子 0.75f
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 4、用 m 的元素初始化雜湊對映 
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
  • 三 : HashMap 的存取機制

1、get(Object key); 如何 getValue 值 【獲取指定 key 對應的 value】

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; 		// Entry 物件陣列 
        Node<K,V> first, e; 	// 在 tab 陣列中經過雜湊的第一個位置
        int n; 
        K k;
        // 找到插入的第一個 Node節點 ,方法是 hash值和 n-1 相與 ,tab[(n - 1) & hash])
        // 在一條鏈上的hash值是相同的 
        if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
            // 檢查第一個 Node節點 是不是要找的 Node
            if (first.hash == hash && // always check first node
                // 判斷條件是 hash值要相同 ,並且 key值相同 
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 檢查 first 後面的 Node
            if ((e = first.next) != null) {
                if (first instanceof TreeNode) // 如果是樹節點的一個例項 ,則呼叫樹的方法 查詢目標節點 
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                /*遍歷連結串列.找到 key值和 hash值 都相等的 Node*/
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        // 沒有返回 null
        return null;
    }

get(key) 方法時獲取 key 的 hash值 ,計算 hash&(n-1) 得到在連結串列陣列中的儲存位置 [ first=tab[hash&(n-1)] ] ,

先判斷 first 的 key 是否等於引數 key ,不等就遍歷後面的連結串列找到相同的 key值 返回對應的 value值即可 .

**2、put(key, value); ** 【將指定key-value新增(修改)到當前map物件中】

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)  
        n = (tab = resize()).length; // 位桶初始化 預設容量 16
    // 如果 tab在((n-1)&hash)的值是空 ,就新建一個節點插入到該位置 
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    /*表示有衝突,開始處理衝突*/
    else {
        Node<K,V> e; K k;
        // 檢查第一個Node, P是不是要找的值 
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode) // 如果p 已經是樹節點的一個例項 ,即這裡已經是樹結構了
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else { // p與新節點既不完全相同 ,也不是 TreeNode 例項 .普通節點
            for (int binCount = 0; ; ++binCount) {
                /*指標為空,就掛在後面 */
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null); // 指向 新節點 ,將新節點插入到連結串列尾部 
                    /*如果衝突的個數已經達到8個 ,判斷是否需要改變衝突的儲存結構
                      	treeifyBin 首先判斷當前的 HashMap 長度 ,如果個數不足64,只進行擴容
                      		resize(),擴容tab,如果個數達到64,將衝突的儲存結構變為紅黑樹 .
                    */
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                /*如果有相同的 key值就結束遍歷 .*/ 
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;	// 相當於 pNode = pNode.next ,迴圈變數 
            }
        }
        /*如果連結串列上有相同的 key值 新的value 替換(覆蓋) 舊的value*/
        if (e != null) { // existing mapping for key 鍵的現有對映 ,鍵的 value 存在 
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e); // 用於 LinkedHashMap
            return oldValue;	// 返回存在的 value值 
        }
    }
    ++modCount;
    /*如果當前大小大於門限 ,門限是初始容量的0.75倍*/
    if (++size > threshold)
        resize();	// 擴容兩倍
    afterNodeInsertion(evict); // 用於 LinkedHashMap
    return null;
}

1、判斷鍵值對陣列 tab[] 是否為空或為null ,否則以預設大小 resize() ;

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

2、根據鍵值 key 計算hash值 得到插入的陣列索引 i ,如果 tab[i] == null ,直接新建節點新增 . 不為null 轉入第三步

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

3、判斷當前陣列中處理 hash 衝突的方式是連結串列還是紅黑樹(check 第一個節點型別即可),分別處理 .

  • 四 :resize() 擴容機制

構造 hash 表時,如果不指定初始大小 ,預設容量大小是 16 (Node 陣列大小16) ,如果Node[] 陣列中的元素達到(填充比 * Node.length) , 則重新調整 HashMap 大小 變為原來的兩倍大小 ,【擴容很耗時!】

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length; // 第一次put oldCap=0
    int oldThr = threshold; // 當前臨界值 threshold=0 賦值給 oldThr
    int newCap, newThr = 0;
    // 如果舊錶的長度不為空
    if (oldCap > 0) {	// 第一次put 不進去 
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        /*把新表的長度設定為舊錶長度的兩倍 ,newCap=2*oldCap */
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            /*把新表的門限設定為舊錶門限的兩倍 ,newThr = 2*oldThr */
            newThr = oldThr << 1; // double threshold
    }
    /*如果舊錶的的長度是0,說明是第一次初始化表*/
    else if (oldThr > 0) // initial capacity was placed in threshold // 第一次put 不進去 
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults // 第一次put 進去 
        newCap = DEFAULT_INITIAL_CAPACITY; // 將預設容量16 賦值給 newCap
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);// newThr=(預設容量*載入因子)=12
    }
    if (newThr == 0) {  
        float ft = (float)newCap * loadFactor;	// 新表長度 * 載入因子 
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr; // 將計算出的閥值賦值給臨界值
    @SuppressWarnings({"rawtypes","unchecked"})
    /*開始構造新表 ,初始化表中的資料 .*/
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // new Node ,陣列出現容量是16 newCap=16 ,
    table = newTab; // 把新表賦值給 table
    /*如果舊錶不為空 ,把舊錶中的資料移動到新表中 */
    if (oldTab != null) {	
        /*遍歷原來的舊錶*/
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null) // 說明舊錶這個Node 沒有連結串列 ,直接存放在新表的 e.hash & (newCap - 1)位置 .
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode) // e 是 樹結構的例項 ,則進行紅黑樹的重hash分佈 
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                /*如果e後面有連結串列,表示e後面鏈著單鏈表 ,需要遍歷連結串列 .將每個節點重新計算在新表的位置,並進行搬運 .*/
                else { // preserve order 保證順序
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;	// 記錄下一個節點 
                        /*新表是舊錶的兩倍容量 ,實際上把連結串列分為兩隊 ,
                        		e.hash & oldCap偶數一對 , e.hash & oldCap奇數一對 */
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {	//lo隊不為null ,放在新表原位置
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {	//hi隊不為null ,放在新表 j + oldCap 位置 
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;	// 返回新表 
}
  • 五 :JDK1.8 使用紅黑樹的改進

在 jdk 1.7 中 ,HashMap 結構為 陣列+連結串列 。

在 jdk 1.8 中 ,HashMap 處理 “碰撞” 情況增加了 紅黑樹這種資料結構 ,當碰撞節點較少,採用連結串列儲存 ,當 閥值 > 8 時 ,

並且 陣列個數達到 64 ,則採用紅黑樹 (特點 : 查詢時間是 O(logn)),儲存 ,將連結串列儲存轉換成紅黑樹儲存 。

​ 最壞的情況即 所有的key 都對映到同一個 位桶(陣列索引位置)中 ,HashMap就退化成一個連結串列 ,查詢時間從O(1) 到 O(n)

升級為 紅黑樹(也是一個二叉樹)

​ 使用 hash值 作為樹的分支變數 ,如果兩個 hash 值不相等 ,但指向同一個陣列索引 ,hash 值 較大的會插入右子樹 。

​ 如果 hash 值相等 ,key 值 最好實現了Comparable ,可以按順序插入 (自然排序 、定製排序 ) .

遍歷方式 :

// 第一種 此方式可以同時取出 key value 的值 
Iterator<Map.Entry<String,Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<String, Integer> next = iterator.next();
    System.out.println(next.getKey() + next.getValue());
}
// 第二種 先取出 key 的值 ,然後通過get(key)方法 獲取value 效率較低 
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
    String next = iterator.next();
    System.out.println(next + map.get(next));
}

LinkedHashMap (瞭解)

簡介 :

​ LinkedHashMap 是 HashMap 的子類 ,內部也是呼叫父類的 put 方法 ,只是 建立Node 節點的時候 ,呼叫 LinkedHashMap 本身的 newNode()方法

// LinkedHashMap 部分原始碼 
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

TreeMap (瞭解)

簡介 :

​ 向 TreeMap 中新增 key-value ,要求 key 必須由同一個類建立的物件 。

​ 因為要按照 key 進行排序 :自然排序 、定製排序 。(也只能說 key 排序 ,value排序無效 )

Properties 處理屬性檔案

Properties 是 Hashtable 的子類

Properties pros = new Properties();
FileInputStream fs = new FileInputStream("類路徑下的 properties字尾檔案 ");
pros.load(fs); // 載入流對應的檔案
String name = pros.getProperties("key");
String age = pros.getProperties("value");