序言
- 是基于jdk1.8版本分析的
1.HashMap的特性
-
HashMap是一对key-value的数据结构,其规定key要是独一的,其中key和value是允许null值的
-
HashMap是线程不同步的,所以如果要使得HashMap同步,可以使用两种方法
第一:就是保证调用HashMap的方法块同步
第二:调用
Collections.synchronizedMap()
来实现该同步Map m = Collections.synchronizedMap(new HashMap()) //最好在一开始创建HashMap的时候就调用该函数
-
从collection view返回的iterators是fail-fast,fail-fast的特点是一旦在遍历的过程中,一旦发现错误,就立即终止执行接下来的程序,立即抛出异常,而在HashMap的过程中,该iterator一旦发现modcount(在HashMapclass内部维持的一个字段,其在每次HashMap发生structural modification的时候(即,put,remove)就是加一),与初始的modcount不一致,就会抛出
CorrentModificationException
,中止执行
2.HashMap.class的结构
2.1HashMap的常量
-
说明:这时HashMap设置的一些默认的常量,如果我们在创建HashMap的时候不指定,则会使用默认常量
-
变量名(static fianl) 值 解释 DEFAULT_INITIAL_CAPACITY 1 << 4 默认的capacity([].length),如果在create HashMap时没有指定,则默认使用该值来生成比该值大一倍的threhold字段的值 MAXIMUM_CAPACITY 1<<30 最大的capacity的值 TREEIFY_THRESHOLD 8 如果一个node[i]链表中的node的个数大于8,则数组链表就会变成红黑树 UNTREEIFY_THRESHOLD 6 目前不清楚什么意思 MIN_TREEIFY_CAPACITY 64 目前不清楚什么意思 DEFAULT_LOAD_FACTOR 0.75f 使得key的hash计算出的索引跟平均的分布在数组中
2.2 HashMap的常用的操作方法
-
构造函数
函数命和参数列表 参数解释 返回值 返回值解释 HashMap() 无参数,使用默认的DEFAULT_LOAD_FACTOR HashMap对象 HashMap(int initialCapacity) 指定initCapacity,默认使用DEFAULT_LOAD_FACTOR HashMap(int initialCapacity, floatloadFactor ) 指定initailCapacity,loadFactor HashMap(Map<? extends K,? extends V> m) 通过一个Map实现类的实例,来生成一个HashMap -
常规操作
函数和参数列表 参数解释 返回值 返回值解释 put(K key, V value) 由putVal()函数实现,可以通过把设置onlyifabsent=true来对已存在的key的value不进行修改 previous value or null 插入已存在的key的时候,就会返回之前的值,如果不存在该key,则插入成功后,就会返回null putAll(Map<? extends K,? extends V> m) 本质上是调用put()来遍历的插入 无返回值 remove(Object key) previous value or null 如果由该key,则删除并返回value,如果没有value,则返回null remove(Object key, Object value) boolean 如果该值被删除,则返回true,如果没有删除成功,则false clear() 删除该map所有的node节点 无返回值 replace(K key, V value) previous value or null repalce已存在的key的时候,就会返回之前的值,如果不存在该key,则返回null replace(K key, V oldValue, V newValue) boolean 如果该值被取代,则返回true,如果没有取代成功,则false get(Object key) value or null 如果由该key,则返回该value,如果没有该key,则返回null getOrDefault(Object key, V defaultValue) value 如果由key,则返回该key的值,如果没有,则返回默认值 clone() 为shallow copy isEmpty() 是否存在node节点 boolean 存在:true 不存在:false size() 由多少个node int containsKey(Object key) boolean containsValue(Object value) boolean -
collection view
values() 返回value的collection Colletion 的实现类 注意,该 Collextion
的实现是HashMap自己实现的keySet() 返回key的Set Set的实现类 该Set的实现类由HashMap自己实现 entrySet() 返回的是node的set,用该类的iterator Set的实现类 该Set的实现类由HashMap自己实现 forEach(BiConsumer action) 可以用来遍历HashMap,action是可以对每一entry的操作,action可以是lambar表达式 -
额外
3.HashMap.Class的基本原理
-
原理,HahMap底层的数据结构是数组列表或者是红黑树(balance tree),在插入node节点的时候,通过key的hash与capacity-1来位与来计算出在数组中的索引,如果没有遇到hash 冲突,则直接插入到数组中,如果hash 冲突,则插入到该index链的末尾,如果发该index链上的node的个数大于TREEIFY_THREHOLD(8),则转化位红黑树,如果发现node的个数大于threhold,则进行HashMap进行大一倍的扩充
-
具体的数据结构的实现
在HashMap中有字段
transient Node<K,V>[] table;
来实现该数据结构,注意该元素为NodeNode 类的源码
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; } }
4.基本源码分析
4.1 putVal()
-
源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //判断,Node数据是否被创建,如没有,则创建 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //是发生hash 冲突 if ((p = tab[i = (n - 1) & hash]) == null) //如果没有,则直接插入该值 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //判断是否跟要插入的key相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //如果相等,则赋值给e e = p; //判断是否是否属于TreeNode else if (p instanceof TreeNode) //调用红黑树的插入函数来进行插入操作 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //遍历index 链表 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { //不存在,则插入到链表尾 p.next = newNode(hash, key, value, null); //检测node节点上(不包括正在index上的节点上数)是否>=8, 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; } } //判断key是否已存在 if (e != null) { // existing mapping for key V oldValue = e.value; //是否允许进行值的覆盖 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); //返回oldValue return oldValue; } } //添加成功,modcount+1 ++modCount; //判断size是否大于threshole,如果有,则进行容量的扩充 if (++size > threshold) resize(); afterNodeInsertion(evict); //返回null return null; }
-
自然语言分析
-
判断node array()是否进行初始化,如果没有,则调用resize()进行初始化
-
判断是否发生hash 冲突,如果没有冲突,则直接插入该node
-
判断是否treenode,如果是,则调用红黑树的插入函数进行该结点的插入
-
如果发生发生hash 冲突,判断该index的链表中是否存在相同的key
如果存在,进行值的修改(是否进行值的赋写,有onlyifabsent值来确定),直接return
如果不存在,则直接在插入在链表尾,判断是否index的node节点是否大于等于treeify_node,如果是,则转 变成红黑树
-
插入成功后,进行modcount++,以及size++,如果size>threshold,则调用resize()进行比之前大一倍的扩容
-
4.2 resize()
-
源码
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } 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]; table = newTab; 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) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 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; 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) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
-
自然语言
4.3 removeNode()
-
源码
final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
-
自然语言
4.4 getNode()
-
源码
final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //判断是否有该实例以及在该index上时候有node if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //判断在index上的node是否等于key if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; //判断next是否位空 if ((e = first.next) != null) { //判断是否为treenode if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { //在node list中查找,是否含有相等的 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } //没有,则直接返回null return null; }
-
自然语言
5.面试题
-
.请解释一下HashMap的参数loadFactor,它的作用是什么?
loadFactor表示HashMap的拥挤程度,影响hash操作到同一个数组位置的概率。默认loadFactor等于0.75,当HashMap里面容纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,需要扩容,在HashMap的构造器中可以定制loadFactor。
-
传统hashMap的缺点(为什么引入红黑树?):
JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。
-
如果两个键的hashcode相同,你如何获取值对象?
HashCode相同,通过equals比较内容获取值对象
-
什么必须是2的幂?如果输入值不是2的幂比如10会怎么样?
一:可以增快哈希的计算程度,即可以key的hash通过capacity进行位与(&),可以快读的得出index
二:在进行扩容的时候,不用直接重新计算hash值,直接根据看key的hash值多出来的一个bit是0,还是1,如果是0,则是原来的index,如果是1,则原来的index+原来的capacity
三:可以使得key在数组中均匀分布
6.HashMap与HashTable的区别
7.different between HashMap,TreeMap,LinkedMap
7.参考的资料
- https://www.cnblogs.com/zengcongcong/p/11295349.html
- https://segmentfault.com/a/1190000023188711
- https://www.baeldung.com/java-fail-safe-vs-fail-fast-iterator
- https://www.baeldung.com/java-hashcode
- https://dzone.com/articles/how-to-use-java-hashmap-effectively
- https://docs.oracle.com/javase/8/docs/api/index.html
- https://www.geeksforgeeks.org/differences-treemap-hashmap-linkedhashmap-java/