浅谈HashMap
什么是HashMap
HashMap 是Java中一种集合的实现,他实现了Map、Collection接口。主要用于key、value键值对的存储。
HashMap的结构
Hashmap中有一个个的Bucket,在每个Bucket中又分别有一个链表。我在图中还画了一个红黑树的结构,那是因为当满足一定条件后[当数组长度大于等于64,链表长度大于等于8],链表结构会转化为红黑树的结构。
/**
* 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.)
*/
transient Node<K,V>[] table;
通过阅读源码,我们可以看到HashMap是通过数组去维护每一个Bucket,而数组中的元素则是链表的起始结点。
HashMap的性能
碰撞
Hashmap通过Hash运算可以快速找到该元素所在的桶,在不发生碰撞的情况下,可以认为它的插入和查询速率是O(1)。但是在实际应用中,碰撞是难免的。在发生碰撞后,同一个桶内的多个元素就会形成链表或红黑树的结构,这个时候插入和查询的效率就取决于链表/树的大小,或者说碰撞的程度。但总的来说,Hashmap的性能还是相当可观的。
HashMap在发生碰撞后,性能就会有所下降,碰撞的程度越剧烈,性能就会变得越低。那么在使用的过程中能否对碰撞程度进行优化呢?答案是肯定的。这里首先要先介绍下负载因子(load factor)
负载因子表示一个散列空间的使用程度,负载因子越大,说明散列的使用程度使用越高,这个时候碰撞的可能性也就越大。所以我们可以通过设置负载因子的大小来调整HashMap的碰撞程度。要注意的是,并不是碰撞因子越小越好,如果碰撞因子过小,那么散列空间的利用率会变得很低,这样会造成严重的空间浪费。
动态扩容
刚刚说的了HashMap的负载因子和元素的碰撞,现在再来说说它的扩容机制。HashMap中的实际可用容量是
容量*负载因子。在HashMap中,它被定义为threshold。 当元素个数大于等于阈值时,即size>=threshold时,为了将低碰撞的概率,HashMap则会发生扩容。
HashMap使用时的注意事项
1、在必要的时候根据实际需求调整负载因子的大小
2、尽可能的设置初始化HashMap的容量
为什么设置负载因子的大小,上面已经说得很清楚了。这里再说下为什么要设置初始化容量。刚刚也已经说到了,当size>=threshold时,HashMap会发生扩容。我们来看下它的相关源码:
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The next size value at which to resize (capacity * load factor).
* @serial
*/
int threshold;
/**
* The load factor for the hash table.
* @serial
*/
final float loadFactor;
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
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;
}
通过resize()函数我们可以看出,在发生扩容时,HashMap需要重新计算每个元素的hash值,并插入到相应的位置。这是个相对比较损耗性能的过程,尤其是存储的数据量比较大的时候,扩容的成本是相对较高的。
Java中HashMap的原理与使用注意
本文介绍了Java中HashMap的相关知识。它是实现Map、Collection接口的集合,用于存储键值对。其结构包含Bucket、链表和红黑树。性能方面,碰撞会影响效率,可通过调整负载因子优化;当元素个数超阈值会动态扩容。使用时要按需调整负载因子和设置初始容量。

115

被折叠的 条评论
为什么被折叠?



