一.概述
- 源码2397行(java8)
- 继承了AbstractMap<K,V>,实现了Map<K,V>, Cloneable, Serializable接口
- 在jdk1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值一次查找的效率较低。而在jdk1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
二.属性
- private static final long serialVersionUID = 362498820763181265L;
- static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始化大小为16
- static final int MAXIMUM_CAPACITY = 1 << 30; //集合大小的最大值,非常大…
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
- static final int TREEIFY_THRESHOLD = 8; //相同hash值的元素大于8,则链表转化成红黑树
- static final int UNTREEIFY_THRESHOLD = 6; //相同hash值的元素小于6,则红黑树转化成链表
- static final int MIN_TREEIFY_CAPACITY = 64;
- transient Node<K,V>[] table; //transient表示不用被序列化,一般是临时数据
- transient Set<Map.Entry<K,V>> entrySet;
- transient int size;
- transient int modCount;
- int threshold;
- final float loadFactor;
三.方法
- hash(Object key)
- comparableClassFor(Object x)
- compareComparables(Class<?> kc, Object k, Object x)
- tableSizeFor(int cap)
- HashMap(int initialCapacity, float loadFactor)
- HashMap()
- HashMap(int initialCapacity)
- HashMap(Map<? extends K, ? extends V> m)
- putMapEntries(Map<? extends K, ? extends V> m, boolean evict)
- size()
- isEmpty()
- get(Object key)
- getNode(int hash, Object key)
- containsKey(Object key)
- put(K key, V value)
- putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
- resize()
- treeifyBin(Node<K,V>[] tab, int hash)
- putAll(Map<? extends K, ? extends V> m)
- remove(Object key)
- removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)
- clear()
- containsValue(Object value)
- keySet()
- values()
- entrySet()
- getOrDefault(Object key, V defaultValue)
- putIfAbsent(K key, V value)
- remove(Object key, Object value)
- replace(K key, V oldValue, V newValue)
- replace(K key, V value)
- computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
- computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
- compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
- merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
- forEach(BiConsumer<? super K, ? super V> action)
- replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
- clone()
- loadFactor()
- capacity()
- writeObject(java.io.ObjectOutputStream s)
- readObject(java.io.ObjectInputStream s)
- newNode(int hash, K key, V value, Node<K,V> next)
- replacementNode(Node<K,V> p, Node<K,V> next)
- newTreeNode(int hash, K key, V value, Node<K,V> next)
- replacementTreeNode(Node<K,V> p, Node<K,V> next)
- reinitialize()
- afterNodeAccess(Node<K,V> p)
- afterNodeInsertion(boolean evict)
- afterNodeRemoval(Node<K,V> p)
- internalWriteEntries(java.io.ObjectOutputStream s)
内部类
- final class KeySet extends AbstractSet
- final class Values extends AbstractCollection
- final class EntrySet extends AbstractSet<Map.Entry<K,V>>
- abstract class HashIterator
- final class KeyIterator extends HashIterator implements Iterator
- final class ValueIterator extends HashIterator implements Iterator
- final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>>
- static class HashMapSpliterator<K,V>
- static final class KeySpliterator<K,V> extends HashMapSpliterator<K,V> implements Spliterator
- static final class ValueSpliterator<K,V> extends HashMapSpliterator<K,V> implements Spliterator
- static final class EntrySpliterator<K,V> extends HashMapSpliterator<K,V> implements Spliterator<Map.Entry<K,V>>
- static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>
四.HashMap工作原理
当我们把键值对传递给put方法时,他调用键对象的hashCode()方法来计算哈希值,然后找到bucket位置来存储对象。当hashcode一样时,即出现hash冲突时,HashMap使用linikedList来解决碰撞问题,对象会存储在linkedList的下一个节点中。在获取对象时,如果同一hashcode的怎么知道要取哪个键值对呢?这时候,就要通过键对象的equals()方法来判断了。
五.HashMap的数据结构
HashMap底层主要是基于数组和单链表来实现的。哈希数组的每一个元素存储的都是一个单链表的头节点。它之所以有相当快的查询速度,主要是因为它通过计算散列码来决定存储的位置。如果散列码相同,即出现hash冲突。解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。
六.HashMap的存储实现
- HashMap如何保证key值的唯一的,通过equals()方法来判断吗?不是的,如果这样,时间复杂度是O(n)。其实是先用hash方法(HashTable使用hashcode方法)来判断,如果hash值一样,再用equals方法判断,因为如果hash值(hashcode值)不同,那么一定不是同一个key,如果相同,那么不一定是同一个key,需要再比较。所以说,HashMap很少用到equals()。
- HashMap在存储过程中,并没有将key,value分开存储,而是当成一个整体key-value来处理的,这个整体就是Entry对象。value只是key的一个附属而已。
七.tips
- HashMap是线程不安全的实现,而HashTable是线程安全的实现,但HashTable效率较低。如果需要线程安全,可以使用ConcurrentHashMap,为什么这个效率就会高些呢,因为它采用来分段锁但技术,维护了一个segment数组,调用put时,先通过key获取到对应的segment,获取某个segment的锁时,不会影响其他分段内的put调用,理论上能同时支持和segment大小相同的线程并发调用。但jdk1.8抛弃了segment分段锁,而是采用了CAS+synchronized来保证并发安全性。
- 什么时候进行rehash?当HashMap的容量达到threshold(这个值有HashMap的容量*设置的一个影响因子得到)时就需要进行扩容,这个时候就要进行ReHash操作了,会先调用risize进行扩容。在扩容的时候需要进行rehash操作,非常耗时。