HashMap如何添加元素詳解


  Map接口結構

  map接口是一個雙邊隊列,擁有key,value兩個屬性,其中key在存儲的集合中不允許重復,value可以重復。

  MapHashMapLinkedHashMapHashtable實現map接口實現map接口繼承HashMap實現map接口MapHashMapLinkedHashMapHashtable

  HashMap特點

  存儲結構在jdk1.7當中是數組加鏈表的結構,在jdk1.8當中改為了數組加鏈表加紅黑樹的結構。

  HashMap在多線程的環境下是不安全的,沒有進行加鎖措施,所以執行效率快。如果我么需要有一個線程安全的HashMap,可以使用Collections.synchronizedMap(Map m)方法獲得線程安全的HashMap,也可以使用ConcurrentHashMap類創建線程安全的map。

  存儲的元素在jdk1.7當中是Entry作為存儲的節點,在jdk1.8當中用Node作為存儲的節點,Node實現了Map.Entry接口。在map中其實是將key和value節點組合起來成為一個Node節點作為存儲。

  // jdk1.8Node節點

  static class Node implements Map.Entry {

  final int hash;

  final K key;

  V value;

  Node next;

  Node(int hash, K key, V value, Node next) {

  this.hash = hash;

  this.key = key;

  this.value = value;

  this.next = next;

  }

  // 下面省略代碼

  }

  在HashMap中key不允許重復,value可以允許重復,並且key和value都允許是null值,因為他們是另外存儲的。

  當我們使用自定義類作為HashMap的key時我們需要重寫object類的,hashCode()和equals()方法。

  在HashMap中鏈表部分在jdk1.7中新的節點總是在鏈表的頭部,舊的節點在新節點的next域當中,而在jdk1.8中是新的節點總是在鏈表的尾部,他們的指向都是由舊節點指向新的節點,由此我們可以記成七上八下。

  HashMap當中常見的名詞

  DEFAULT_INITIAL_CAPACITY 默認數組容量大小,16

  MAXIMUM_CAPACITY 最大數組容量大小,2^30

  DEFAULT_LOAD_FACTOR 默認的負載因子,0.75

  TREEIFY_THRESHOLD 鏈表改成紅黑樹存儲的最小長度

  MIN_TREEIFY_CAPACITY 鏈表改成紅黑樹存儲,map數組最小容量

  HashMap存儲元素過程源碼解析(jdk1.8)

  // 存儲元素的數組,加上transient關鍵字代表不可以被序列化

  transient Node[] table;

  // 這個方法是HashMap對外公開的添加元素方法

  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[] tab; Node p; int n, i;

  // 在jdk1.7當中,創建一個HashMap實例是直接分配一個長度為16的數組,

  // 在jdk1.8當中,創建HashMap時是分配了一個長度為0的數組,然后調用put方法時,如果數組長

  // 度為0時,則調用數組擴容方法,擴容數組到長度為16。當長度不為0時,擴容操作是擴大到原來

  // 長度的兩倍

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

  // 調用擴容方法,並利用n變量,記錄數組的長度

  n = (tab = resize()).length;

  // 通過計算哈希散列,得出該key應該放在數組的哪一個位置上,用變量p存儲該位置上的元素

  // 判斷該位置上是否已經存在元素,如果不存在元素則直接將該元素放入數組中,存放

  if ((p = tab[i = (n - 1) & hash]) == null)

  tab[i] = newNode(hash, key, value, null);

  // 如果數組的該位置已經存在了元素

  else {

  Node e; K k;

  // 比較需要加入的元素,於原來的元素hash值進行比較,如果hash值和equals都為true那么

  // 判定兩個元素為相同元素,用變量e來記錄原來的元素,方便下面進行替換

  if (p.hash == hash &&

  ((k = p.key) == key || (key != null && key.equals(k))))

  e = p;

  // 如果上述條件不滿足,但是是一個TreeNode類型,則當作樹節點插入

  else if (p instanceof TreeNode)

  e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

  else {鄭州哪家人流醫院好 http://www.zzzyrl120.com/

  // 當hash值不同,或者equals為false,也不是TreeNode時將新元素插入數組對應位置的

  // 鏈表中,遍歷對應數組位置的鏈表

  for (int binCount = 0; ; ++binCount) {

  // 用變量e存儲鏈表的下一個節點

  if ((e = p.next) == null) {

  // 如果該鏈表只有一個元素,則直接將舊節點的next域指向新的節點

  p.next = newNode(hash, key, value, null);

  // 當單條鏈表上元素數量大於最大數量時則按照紅黑樹存儲8,在進行紅黑樹時

  // 又會進行判斷數組容量是否到達紅黑樹最小容量64,兩個條件同時滿足,則該

  // 條鏈表改造成紅黑樹存儲

  if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

  treeifyBin(tab, hash);

  break;

  }

  // 比較鏈表的下一個節點的hash值和equals

  if (e.hash == hash &&

  ((k = e.key) == key || (key != null && key.equals(k))))

  break;

  p = e;

  }

  }

  // 如果e不等於null則證明,在鏈表中存在相同的元素,則進行替換

  if (e != null) { // existing mapping for key

  V oldValue = e.value;

  if (!onlyIfAbsent || oldValue == null)

  e.value = value;

  afterNodeAccess(e);

  return oldValue;

  }

  }

  ++modCount;

  if (++size > threshold)

  resize();

  afterNodeInsertion(evict);

  return null;

  }

  // 將鏈表改造成紅黑樹

  final void treeifyBin(Node[] tab, int hash) {

  int n, index; Node e;

  // 判斷是否滿足條件,數組大小達到64,沒有達到則做擴容操作,達到則改成紅黑樹

  if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)

  resize();

  else if ((e = tab[index = (n - 1) & hash]) != null) {

  TreeNode hd = null, tl = null;

  do {

  TreeNode p = replacementTreeNode(e, null);

  if (tl == null)

  hd = p;

  else {

  p.prev = tl;

  tl.next = p;

  }

  tl = p;

  } while ((e = e.next) != null);

  if ((tab[index] = hd) != null)

  hd.treeify(tab);

  }

  }

  HashMap常見的問題

  為什么在使用自定義類型作為key需要我們重寫hashCode()和equals()方法

  因為map的存值的時候是先計算hash值,然后再判斷equals,通過這兩個值是否都為true來判斷該元素是否再map中已經存在。如果不重寫這兩個方法,可能會存在我們認為相同,但是他們的hash或者equals不同的元素也能存進map中,從而達不到key唯一的效果。

  在HashMap中為什么要存在DEFAULT_LOAD_FACTOR負載因子這個概念

  因為我們map的存儲數據結構是數組加鏈表加紅黑樹結構,如果沒有負載因子,map是不可能滿的,所以加上一個負載因子的概念來判斷數組是否擴容,減少鏈表的負擔。


免責聲明!

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



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