首页 > 编程知识 正文

HashMap的底层原理,hashtable不允许null

时间:2023-05-04 02:43:59 阅读:51039 作者:2698

Map是java广义集合框架的另一部分,其中HashMap是使用频率最高的类型之一。 所以和HashMap本身相关的类型也经常在面试中被提问。

一般能回答的内容主要是基本特征、数据结构、混洗图的设计,其他问题可以更多,多是混洗图源代码、混洗算法等。

典型答案: Hashtable、HashMap、TreeMap都实现了Map接口,以键值对的形式存储数据和操作数据。 Hashtable是java早期提供的,方法是添加同步的同步。 key和value都不能为空值。 HashMap方法支持key和value为null的情况,而不是同步。 行动上基本上与Hashtable一致。 由于Hashtable已同步,因此性能开销很大,一般不建议使用Hashtable。 通常选择使用混洗贴图。 HashMap进行put和get操作,基本上可以达到一定时间的性能。 流映射是提供基于红黑树的顺序访问的映射,与混列映射不同,get或put操作的时间复杂度为o(log(n ) )。 具体顺序由指定的Comparator决定,或者由密钥密钥的具体顺序决定。 首先,让我们在两幅图中看到Map的相关性。

Hashtable类似于Vector和stack的初始相关集合,继承了Dictionary。 类结构上与HashMap明显不同。

公共class hashtable k,V extends DictionaryK,V implements MapK,v,Cloneable,Java.io.serializable { } publiclasshashmable 可克隆,可序列化{ } publicclasstreemapk,V extends AbstractMapK,V implements NavigableMapK,v,v

我们先来看看HashMap的构造函数。

/默认加载因子staticfinalfloatdefault _ load _ factor=0.75 f; /默认构造函数,加载系数为默认的公共散列映射((this.load factor=default _ load _ factor; }/指定容量大小的构造函数public hashmap (intinitial capacity ) /指定容量大小和负载因子的构造函数public hashmap (intinitial capacity ) Extendsvm}{}首先,从构造函数中可以看到以下内容:

容量容量容量加载因子(加载因子/扩展因子)加载因子容量容量是哈希表的容量。 HashMap的初始容量为16.staticfinalintdefault _ initial _ capacity=14;/aka 16加载因子是指哈希表在其容量自动增加之前能达到多少的度量(threshold=capacity * load factor )。 如果哈希表的数量超过了加载系数和容量的乘积,则对哈希表执行resize操作。 哈希表大约是两倍的桶数。

thenextsizevalueatwhichtoresize (capacity * load factor ).int threshold; 让我们来看看HashMap的数据结构。 HashMap可以被认为是数组[]和链表的复合结构。 数组分为一个桶(bucket )。 哈希值决定此数组中键值的寻址。 如果存在具有相同散列值的键-值对,则将其另存为链表。 如果链表大小超过阈值(TREEIFY_THRESHOLD=8)。 链表将被改造成木结构。 图:

用put值的代码看看数组和链表的保存。

publicvput(kkey,V value )//key对应的hashreturnputval (hash ) key )、key、value、false、true ); }//java8中的混列算法staticfinalinthash(objectkey ) inth; //key的hashCode和移位操作后进行异或运算(这个处理可以有效避免类似情况下的散列冲突) return(key==null )? 0:(h=key.hashcode () ) ) ) ) ) ) ) ); }finalvputval(inthash,K key,V value,bo

olean onlyIfAbsent, boolean evict) { //node数组 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; //如果hash值相同则放入链表 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) //如果哈希表数量大于threshold则resize resize(); afterNodeInsertion(evict); return null;}

其中树化改造如下:

final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; //如果数量小于64,直接扩容 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); //如果大于,则进行树化 else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> 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为什么要树化呢?
本质上是个安全问题。因为在元素放置过程中,如果一个对象hash冲突,都会被放置到同一个桶中,则会形成一个链表。链表的查询是线性的,会严重影响存取的性能。

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;}

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。