HashMap详解

Posted 默默的看雨下

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap详解相关的知识,希望对你有一定的参考价值。

HashMap详解

HashMap


注:由于HashMap的实现机制过于复杂,内部由哈希数组+链表+红黑树实现,并且其中涉及到Hash算法哈希数组扩容机制负载因子红黑树等等一系列较为复杂的问题,具体可以去看美团网技术团队的关于HashMap的文章。在我自己实现的HashMap中,我把许多东西难度都降低了,我主要想把HashMap的实现机制说清楚,而具体怎么实现的,我自己许多东西不会,以后再来写清楚吧。

1 概述

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

  • HashMap内部是哈希数组+链表+红黑树实现
  • HashMap遍历无序,但其有很快的访问速度
  • HashMap中key不能有重复元素,可以有一个null;value可以有重复元素多个null对象
  • HashMap是线程不安全的

2 实现机制

2.1 原理

HashMap的实现如下图:
enter description here

HashMap是使用哈希表存放元素,使用了链地址法(数组+链表)解决哈希冲突。JDK1.8后当链表长度大于8时使用了红黑树解决哈希冲突。

首先通过key的hashCode()方法找到key的哈希值,通过Hash算法算出该key存放的位置(哈希数组的下标),在这里可能发生哈希冲突,然后插入到链表或红黑树中存放。

HashMap解决哈希冲突主要有两点:1.好的Hash算法使key-value对均匀分布在哈希数组中。2.key-value对过多时,扩充哈希数组的容量。

2.2 具体实现

HashMap的几个字段

1 transient Node<K,V>[] table;  // 哈希数组
2 transient int size;  // HashMap存放元素个数
3 int threshold;  // key-value对的最大容量(哈希数组长度Capacity*负载因子loadFactor)
4 final float loadFactor;  // 负载因子

(1).Node[] table是哈希桶数组。Node实现了Map.Entry接口(Map.Entry就是键值对)的链表。

 1 static class Node implements Map.Entry {
 2     final int hash;  // 经过hash运算后得出的数组下标
 3     final K key;
 4     V value;
 5     Node next;
 6     Node(int hash, K key, V value, Node next) {
 7         this.hash = hash;
 8         this.key = key;
 9         this.value = value;
10         this.next = next;
11     }
12     public final K getKey()        { return key; }
13     public final V getValue()      { return value; }
14     public final String toString() { return key + "=" + value; }
15     public final int hashCode() {
16         return Objects.hashCode(key) ^ Objects.hashCode(value);
17     }
18     public final V setValue(V newValue) {
19         V oldValue = value;
20         value = newValue;
21         return oldValue;
22     }
23     public final boolean equals(Object o) {
24         if (o == this)
25             return true;
26         if (o instanceof Map.Entry) {
27             Map.Entry e = (Map.Entry)o;
28             if (Objects.equals(key, e.getKey()) &&
29                     Objects.equals(value, e.getValue()))
30                 return true;
31         }
32         return false;

(2).threshold表示该哈希数组存放的最大存放key-value对数,threshold = loadFactory(负载因子) * Capatity(哈希数组长度)。
loadFactoy默认0.75,Capatity默认16。在每次存放key-value对时检查threshold,若其不足则需要扩容(哈希数组长度)。
扩容方法,我不太懂就不写了,源码见HashMap中的resize()方法。
HashMap中put方法
注:美团上面的摘抄
enter description here

 1 public V put(K key, V value){
 2     // 对key的hashCode()做hash
 3     return putVal(hash(key), key, value, false, true);
 4 }
 5 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
 6     Node<K,V>[] tab; Node<K,V> p; int n, i;
 7     // 步骤①:tab为空则创建
 8     if ((tab = table) == null || (n = tab.length) == 0)
 9         n = (tab = resize()).length;
10     // 步骤②:查看该链表或者红黑树中是否有元素
11     if ((p = tab[i = (n - 1) & hash]) == null)
12         // 没有元素就创建头元素
13         tab[i] = newNode(hash, key, value, null);
14     else {
15         Node<K,V> e; K k;
16         // 步骤③:判断table[i]中首个元素是否是该key
17         if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))){
18             // 是-->替换
19             e = p;
20         }
21         // 步骤④:判断是否是红黑树
22         else if (p instanceof TreeNode){
23             // 是-->直接在树中插入键值对
24             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
25         }
26         else {
27             // 遍历链表
28             for (int binCount = 0; ; ++binCount) {
29                 if ((e = p.next) == null) {
30                     // 步骤⑤:判断链表长度是否大于8,若大于转换成红黑树处理
31                     p.next = newNode(hash, key, value, null);
32                     // 转换成红黑树处理 
33                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
34                         treeifyBin(tab, hash);
35                     break;
36                 }
37                 if (e.hash == hash &&
38                     ((k = e.key) == key || (key != null && key.equals(k))))
39                     break;
40                 p = e;
41             }
42         }
43         if (e != null) { // existing mapping for key
44             V oldValue = e.value;
45             if (!onlyIfAbsent || oldValue == null)
46                 e.value = value;
47             afterNodeAccess(e);
48             return oldValue;
49         }
50     }
51     ++modCount;
52     // 步骤⑥:插入成功,判断是否超过最大容量
53     if (++size > threshold){
54         // 扩容
55         resize();
56     }
57     afterNodeInsertion(evict);
58     return null;
59 }

(3).扩容机制:不会以后来写。

3.自己写的HashMap

  1 class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>{
  2     private static final int DEFAULT_CAPACITY = 16;  // 哈希数组默认大小
  3     private int size;  // HashMap存放元素个数
  4     private Node[] table;  // 哈希数组
  5 
  6     HashMap(){
  7         table = new Node[DEFAULT_CAPACITY];
  8     }
  9 
 10     @Override
 11     public V put(K key, V value) {
 12         // 找到该key对应的哈希数组下标
 13         int index = hash(key);
 14         // 该链表没有元素
 15         if(table[index] == null){
 16             table[index] = new Node<>(index, key, value, null);
 17             size++;
 18             return null;
 19         }
 20         @SuppressWarnings("unchecked")
 21         Node<K, V> node = table[index];
 22         Node<K, V> prev = node;
 23         // 遍历该链表
 24         while(node != null){
 25             // 找到key,则替换value并返回原value
 26             if(Objects.equals(node.getKey(),key)){
 27                 V oldValue = node.getValue();
 28                 node.setValue(value);
 29                 return oldValue;
 30             }
 31             prev = node;
 32             node = node.next;
 33         }
 34         // 未找到,则加入链表
 35         prev.next = new Node<>(index, key, value, null);
 36         size++;
 37         return null;
 38     }
 39 
 40     @Override
 41     public V get(Object key) {
 42         // 找到该key对应的哈希数组下标
 43         int index = hash(key);
 44         // 该链表没有元素
 45         if(table[index] == null){
 46             return null;
 47         }
 48         @SuppressWarnings("unchecked")
 49         Node<K, V> node = table[index];
 50         // 遍历该链表
 51         do {
 52             if(Objects.equals(node.getKey(),key)){
 53                 return node.getValue();
 54             }
 55             node = node.next;
 56         } while (node != null);
 57         return null;
 58     }
 59 
 60     @Override
 61     public V remove(Object key) {
 62         // 找到该key对应的哈希数组下标
 63         int index = hash(key);
 64         @SuppressWarnings("unchecked")
 65         Node<K, V> node = table[index];
 66         // 该链表没有元素
 67         if(node == null){
 68             return null;
 69         }
 70         // 该链表只有一个元素
 71         if(node.next == null){
 72             if(Objects.equals(node.getKey(),key)){
 73                 V oldValue = node.getValue();
 74                 table[index] = null;
 75                 size--;
 76                 return oldValue;
 77             }
 78             return null;
 79         }
 80         // 遍历该链表
 81         Node<K, V> prev = node;
 82         node = node.next;
 83         while(node != null){
 84             if(Objects.equals(node.getKey(),key)){
 85                 prev.next = node.next;
 86                 size--;
 87                 return node.getValue();
 88             }
 89             node = node.next;
 90             prev = prev.next;
 91         }
 92         return null;
 93     }
 94 
 95     @Override
 96     @SuppressWarnings("unchecked")
 97     public Set<Entry<K, V>> entrySet() {
 98         Set<Entry<K, V>> set = new HashSet<>();
 99         for(Node<K, V> node: table ){
100             while(node != null){
101                 set.add(node);
102                 node = node.next;
103             }
104         }
105         return set;
106     }
107 
108     @Override
109     public int size() {
110         return size;
111     }
112 
113     /**
114      * 计算key的哈希值,然后Hash算法(取模)求出该key对应的数组下标
115      */
116     public int hash(Object key){
117         return Objects.hashCode(key) % table.length;
118     }
119 
120     static class Node<K, V> implements Map.Entry<K, V>{
121         final int hash;  // 经过hash运算得出的数组下标
122         final K key;
123         V value;
124         Node<K, V> next;
125 
126         Node(int hash, K key, V value, Node<K, V> next) {
127             this.hash = hash;
128             this.key = key;
129             this.value = value;
130             this.next = next;
131         }
132 
133         @Override
134         public K getKey() {
135             return this.key;
136         }
137 
138         @Override
139         public V getValue() {
140             return this.value;
141         }
142 
143         @Override
144         public V setValue(V value) {
145             V oldValue = this.value;
146             this.value = value;
147             return oldValue;
148         }
149 
150         @Override
151         public boolean equals(Object obj) {
152             if(obj == this){
153                 return true;
154             }
155             if(obj instanceof Map.Entry<?, ?>){
156                 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
157                 return Objects.equals(entry.getKey(),this.key) && Objects.equals(entry.getValue(),this.value);
158             }
159             return false;
160         }
161 
162         @Override
163         public int hashCode() {
164             return Objects.hashCode(this.key) ^ Objects.hashCode(this.value);
165         }
166 
167         @Override
168         public String toString() {
169             return this.key + "=" + this.value;
170         }
171     }
172 }

4.Reference

美团点评技术团队:http://tech.meituan.com/java-hashmap.html

以上是关于HashMap详解的主要内容,如果未能解决你的问题,请参考以下文章

java中HashMap详解

HashMap 和 ConcurrentHashMap 的区别

Java集合详解4:HashMap和HashTable

HashMap原理:哈希函数的设计

(转) Java中的负数及基本类型的转型详解

HashMap深度解析