内容简介:get(key)首先获取key的hashcode,计算hash&(n-1)得到在数组中位置,得到位置之后,先判断与第一个元素是否相等,如果不等就遍历该数组位置上的链表后面的元素,找到返回value,找不到返回null。put(key,value)先判断节点数组是否为空或者null,如果为空,就resize为默认大小。根据key计算hashcode,如果根据hashcode值,在数组中找不到相同hashcode的元素,则直接新建一个节点,即数组中节点+1,否则判断当前数组中处理hash冲突的方式是链表还是红黑
JDK1.8中HashMap采用桶+链表+红黑树实现,当链表长度超过阈值(默认8)时,将链表转换为红黑树,以减少搜索时间。
首先每个元素都是链表(可能表述不准确)的数组,每添加一个元素(key-value)时,首先计算key的hashcode,以此确定元素的插入位置,有可能hashcode相同,那么存放在相同hashcode的元素的后面,他们在数组的同一位置的链表上。当链表长度超过阈值(默认8)时,则会转换为红黑树结构,减少搜索时间,提高查找效率。
以上为HashMap的原理图
2. JDK1.8版本中HashMap涉及到的数据结构
/** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) * 【table,在首次使用的时候初始化,并且必须重新调整大小。当被分配的时候,长度为2的幂。(我们也允许长度为0,来允许当前不必要的引导程序结构/引导机制)】 */ transient Node<K,V>[] table; 复制代码
/** * Basic hash bin node, used for most entries. (See below for * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) * 【基础哈希二进制节点,用于大多数条目】 */ static class Node<K,V> implements Map.Entry<K,V> { //单项列表,实现了Map.Entry接口 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; } //判断两个node是否相等,key-value都相等返回true,与自身比较也返回true 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; } } 复制代码
/** * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn * extends Node) so can be used as extension of either regular or * linked node. * 【二叉树条目。继承自LinkedHashMap.Map,所以可以作为常规胡总和linked node的扩展】 */ static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } /** * Returns root of tree containing this node. * 【返回当前节点的根节点】 */ final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } } . . . } 复制代码
3. HashMap的构造函数
/** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public HashMap(int initialCapacity, float loadFactor) { //initialCapacity:初始容量;loadFactor:记载因子/填充比,默认0.75 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); } /** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty <tt>HashMap</tt> with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * Constructs a new <tt>HashMap</tt> with the same mappings as the * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified <tt>Map</tt>. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } 复制代码
4. HashMap的存取
get
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } /** * Implements Map.get and related methods * * @param hash hash for key * @param key the key * @return the node, or null if none */ 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; } 复制代码
get(key)首先获取key的hashcode,计算hash&(n-1)得到在数组中位置,得到位置之后,先判断与第一个元素是否相等,如果不等就遍历该数组位置上的链表后面的元素,找到返回value,找不到返回null。
put
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } /** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } 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; } 复制代码
put(key,value)先判断节点数组是否为空或者null,如果为空,就resize为默认大小。根据key计算hashcode,如果根据hashcode值,在数组中找不到相同hashcode的元素,则直接新建一个节点,即数组中节点+1,否则判断当前数组中处理hash冲突的方式是链表还是红黑树。
5. 扩容机制
构建Hash表时,如不指明初始大小,则默认大小16,如果Node数组的大小达到【填充比*length】,则扩容大小为原来的2倍。
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; } 复制代码
6. 关于JDK1.8中红黑树的使用
JDK中使用链表,查询时间是O(n),在JDK1.8中当链表长度大于阈值(默认8)时,采用红黑树(查询时间是n(logN)),有效提高查找效率。
当超过阈值后,采用二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶,较大的会插入到右子树中。如果哈希值相等,HashMap希望key值最好实现了Comparable接口,这样就可以按顺序插入,当然这不是必要的,但如果发生严重的哈希碰撞,这能有效解决哈希碰撞带来的性能问题。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 一文读懂区块链链上扩容和链下扩容
- golang内存扩容
- Sharding扩容方案-2(实现)
- golang 切片扩容的探讨
- VirtualBox 虚拟机磁盘扩容
- VirtualBox中Ubuntu扩容
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web Form Design
Luke Wroblewski / Rosenfeld Media / 2008-5-2 / GBP 25.00
Forms make or break the most crucial online interactions: checkout, registration, and any task requiring information entry. In Web Form Design, Luke Wroblewski draws on original research, his consider......一起来看看 《Web Form Design》 这本书的介绍吧!