Java集合之HashMap

public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
HashMap繼承了AbstractMap,Map,Cloneable,Serializable,表示是映射,存儲Key-Value,可以被克隆,可以序列化
一、常量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 // 默認容量是16
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量是2的30次冪
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默認負載因子是0.75
static final int UNTREEIFY_THRESHOLD = 6; // 紅黑樹轉為鏈表閾值,當HashMap擴容時,如果發(fā)現(xiàn)鏈表長度小于6,則由紅黑樹退化為鏈表
static final int TREEIFY_THRESHOLD = 8; // 鏈表轉為紅黑樹的閾值,在HashMap擴容時,如果發(fā)現(xiàn)鏈表長度大于8,則將鏈表轉化為紅黑樹
static final int MIN_TREEIFY_CAPACITY = 64; // 當HashMap中的容量大于64時,才考慮樹形化,否則桶內元素過多時,考慮的時擴容而不是樹形化
二、變量
transient Node<K,V>[] table; // Node數(shù)組table,即哈希桶數(shù)組
transient int size; // 實際存儲的key-value數(shù)量
transient Set<Map.Entry<K,V>> entrySet; // 待定??????
int threshold; // HashMap擴容閾值,當size >= threshold時,HashMap就會擴容,threshold = 容量 * 負載因子,threshold越大,容納的鍵值對越多
final float loadFactor; // 負載因子
transient int modCount; // 記錄HashMap發(fā)生變化的次數(shù),主要用來迭代的快速失???????
三、存儲結構


2059840-8386849414328b14.png

HashMap的存儲結構是數(shù)組+鏈表+紅黑樹(JDK8)實現(xiàn)的,哈希沖突導致的鏈表過長時,會嚴重影響HashMap的性能,如查找和刪除,JDK8引入紅黑樹,當鏈表過長時,將鏈表轉化為紅黑樹,利用紅黑樹增刪改查的高性能提高HashMap的性能,
一、構造函數(shù)

  1. public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    默認負載因子是0.75,默認容量16,必須是2的冪

  2. public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    指定HashMap的容量,負載因子是默認0.75

  3. 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);
    }
    指定HashMap的容量和負載因子,HashMap的最大容量MAXIMUM_CAPACITY = 1 << 30,即2的30次方
    二、數(shù)組
    transient Node<K,V>[] table;
    HashMap有一個內部靜態(tài)類:Node,實現(xiàn)了Map.Entry接口,本質是一個key-value,next表示是一個鏈表節(jié)點,用來處理哈希沖突,hash用來確定key-value保存在table數(shù)組的哪個位置。
    static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

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

    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;
    }
    }
    四、具體功能之如何根據key值計算table數(shù)組的索引
    static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    key == null的時候,key的hash值是0,key != null時,hash值是key.hashCode與key.hashCode >>>16異或的值(無符號右移,高位補0)
    hash = hash(key);
    i = (table.length - 1) & hash
    索引等于hash按位與table.length-1,選擇table.length-1這樣做的好處是因為table.length總是2的N次冪,計算的到i總是在數(shù)組length范圍內
    五、具體功能之put方法
    1.根據key計算table位置索引i
    2.如果該位置table[i] == null,new Node直接保存
    3.如果該位置table[i] != null,如果是Node節(jié)點,遍歷鏈表,如果鏈表中沒有匹配的key,則new Node插入到鏈表的尾部,如果鏈表長度大于8,將鏈表轉為紅黑樹,如果鏈表中有匹配的key,覆蓋原來的value
    4.如果該位置table[i] != null,如果是TreeNode紅黑樹節(jié)點,則new TreeNode插入到紅黑樹
    5.插入成功后,判斷實際存在的鍵值對size是否大于threshold,如果大于,進行擴容
    六、具體功能之get方法
    public V get(Object key)
    1.根據key的hash,計算位置索引i=(table.length-1) & hash
    2.首先取該位置處的第一個Node first
    3.如果fist是紅黑樹節(jié)點,則查找紅黑樹中匹配的key
    4.如果是鏈表節(jié)點,則遍歷鏈表,找到匹配的key
    final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
    (first = tab[(n - 1) & hash]) != null) {
    if (first.hash == hash && // always check first node
    ((k = first.key) == key || (key != null && key.equals(k))))
    return first;
    if ((e = first.next) != null) {
    if (first instanceof TreeNode)
    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
    do {
    if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    return e;
    } while ((e = e.next) != null);
    }
    }
    return null;
    }
    七、具體功能之擴容
    當HashMap內部不能裝載更多數(shù)據時,需要擴大HashMap容量,將擴大為原來的兩倍,原來的數(shù)據將根據新的hash值放在新的位置,出發(fā)條件時數(shù)據數(shù)量大于threshold
    1.計算新的capacity,threshold,capitcity為原來容量的兩倍,如果初始容量為0,則為16
    2.創(chuàng)建新的數(shù)組,容量為原capacity的兩倍
    3.將原來數(shù)組的數(shù)據放到新的數(shù)組中
    ???????
    原來的key-value會放在原來的位置和原來的位置+舊capacity中
    八、面試問題
    1.key和value可以為null嗎?
    key=null時,計算得到的索引為0,key為null的鍵值放在table[0]為頭節(jié)點的鏈表中,value可以為null
    2.key可以使用自定義類型嗎
    key一般使用String,Integer類型,因為這些類型時不可變的,并且實現(xiàn)了hashCode(),equals()方法,如果使用可變類型作為key,當key發(fā)生變化后,hashCode會發(fā)生變化,導致無法找到該鍵值
    3.怎么實現(xiàn)線程安全
    一是使用HashTable,但是效率很低,二是使用Collections包:Collections.synchronizeMap(hashMap);三是使用ConcurrentHashMap

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一、基本數(shù)據類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對于byte類型而言...
    龍貓小爺閱讀 4,448評論 0 16
  • 前言 HashMap是Map集合的一種實現(xiàn),提供了一種簡單實用的數(shù)據存儲和讀取方式。Map接口不同于List接口,...
    帶娃兒先走閱讀 258評論 0 0
  • IFLA 國際圖書館協(xié)會聯(lián)合會(International Federation of Library Assoc...
    Kemr閱讀 333評論 0 1
  • 說起親子閱讀,有的家長會和我聊說:“家里買了很多書,孩子不看有什么辦法,不是不給買,是一大堆擺在那,不看?!?..
    東芝親子讀書閱讀 162評論 0 0
  • 投射:1投射81190039陳生幫我業(yè)績打到50萬 theo並一直長打贏錢!2投射有刷卡錢回傭1萬元! 3投射最好...
    謝奕鋒閱讀 86評論 0 0

友情鏈接更多精彩內容