Java中HashMap详解
一、HashMap的数据结构
二、HashMap源码分析
1、构造函数
DEFAULT_LOAD_FACTOR是默认的负载因子=0.75;
MAXIMUM_CAPACITY是最大HashMap容量;
threshold为一个阈值大小是(capacity * load factor)即容量*负载因子;
1)方法一: public HashMap()
无参构造函数会默认构造出一个初始容量为16,负载因子为0.75的空HashMap;
2) 方法二:public HashMap(int initialCapacity, float loadFactor)
第二种构造方法会构造出一个初始容量大于或者等于initialCapacity的2的幂次方,负载因子为loadFactor空HashMap;
3)方法三:public HashMap(int initialCapacity)
方法三调用方法二,默认负载因子为0.75;
4)方法四:public HashMap(Map<? extends K, ? extends V> m)
方法四构造一个和指定的Map有相同的Mappings的HashMap,默认的负载因子为0.75;
2、构造方法二中的tableSizeFor(initialCapacity);
源码:
tableSizeFor方法返回大于initialCapacity的最近的2的幂次方值。
从源码中我们看到n得到的是cap-1,这步操作主要是为了防止cap已经是2的幂次方的情况,
后面一系列n |= n>>> 1、2、4、5、16;无符号右移操作,使得n的二进制低位全为1,
最后判断n是否大于MAXIMUM_CAPACITY,大于则返回MAXIMUM_CAPACITY,否则返回n+1即为2的幂次方。
3、构造方法三中的putMapEntries(m,false)
源码:
putMapEntries()的功能为将传入的Map初始化为一个HashMap结构,
转化前先判断m的大小,如果为0则没必要初始化,否则将Map中的元素一次将其放进HashMap,
此时分为两种情况:
<1>table没有初始化
在这种情况下,我们需要检查map的大小是否超过MAXIMUM_CAPACITY,之后初始化threshold为相应的大小。
<2>table已经初始化
这种情况下需要检查s是否大于当前的threshold阈值,如果大于则resize(),否则不需扩容,只需将m中每个元素放进HashMap即可。
putMapEntries中的resize()
resize就是重新将原来HashMap内的数据放进一个新扩容的HashMap中,
而旧容器中的数据存放位置有两种情况,
<1>在原来的位置上
<2>在原来的位置+oldCap的位置上
当我们HashMap中的数据量达到threshold时,即可进行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>当两个节点的key值相同时,此时一定发生哈希冲突,
此时将key相应的value值换成新值
<2>当两个节点key值不同,但因为哈希函数的运算导致最后的哈希值相同导致的冲突,
此时将value值放进此哈希值内单链表或者红黑树中
<3>两节点key值不同,哈希值不同,但是对数组长度取模后得到的值相同
遵循情况2的处理方式
三、HashMap和HashTable的不同
首先从实现的接口来看HashMap和Hashtable都实现了Map、Cloneable(可复制)、Serializable(可序列化)这三个接口,
而HashMap是继承自AbstractMap类,HashTable是继承自Dictionary类。
补充:
1.sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。
2.HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应Value值为null。
3.因为HashTable本身有synchronized所以意味着一次只能有一个线程改变HashTable
4.HashMap首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。
Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。