HashMap的详细介绍
—–本文只针对1.7版本的HashMap所讲解.
我们知道了HashMap的生成hash码的时候会设计到hashSeed的问题,
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
//对key生成hash码
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
这里对key进行生成hash码,它是怎么生成的呢?我们看下面的代码:
transient int hashSeed = 0;
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
我们这里一步一步来解释,首先hashSeed怎么来的?它是HashMap的属性,在HashMap初始化时已经为0,但是我们在执行inflateTable方法时执行了initHashSeedAsNeeded方法这里面对hashSeed又执行了一次赋值操作,我们来代码:
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
//hashSeed在这个方法里面又进行了一次赋值操作,为了让读者了解的更多,我尽量做的详细一点.
final boolean initHashSeedAsNeeded(int capacity) {
//当我们初始化的时候hashSeed为0,0!=0 这时为false.
boolean currentAltHashing = hashSeed != 0;
//isBooted()这个方法里面返回了一个boolean值,我们看下面的代码
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
sun.misc.VM类的代码如下:
private static volatile boolean booted = false;
public static boolean isBooted() {
return booted;
}
这里返回的是booted 的值,但是booted 默认为false,但是我们不知道VM启动的时候是否对它又赋了新值,怎么办呢?我们可以用一个土办法来测试如下:
我们可以看到在Map执行前booted的值为true,然而我们HashMap执行的时候并没有给它赋值,所以它为true,然后我们比较后面的条件看看,我们看见要拿初始容量比较Holder.ALTERNATIVE_HASHING_THRESHOLD,但是我们不知道这个为多少.看代码.
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;//这里为2147483647,它是HashMap的属性,初始化的时候就已赋值
//Holder这个类是HashMap的子类,
private static class Holder {
//这里定义了我们需要的常量,但是它没赋值,我们看看它是怎么赋值的?
static final int ALTERNATIVE_HASHING_THRESHOLD;
static {
String altThreshold = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"jdk.map.althashing.threshold"));
int threshold;
try {
threshold = (null != altThreshold)
? Integer.parseInt(altThreshold)
: ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;
// disable alternative hashing if -1
if (threshold == -1) {
threshold = Integer.MAX_VALUE;
}
if (threshold < 0) {
throw new IllegalArgumentException("value must be positive integer.");
}
} catch(IllegalArgumentException failed) {
throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
}
ALTERNATIVE_HASHING_THRESHOLD = threshold;
}
}
我们通过代码看到ALTERNATIVE_HASHING_THRESHOLD来自threshold,threshold哪里呢?看上面得知来自判断条件里面.那我们就来看看判断条件altThreshold,altThreshold来自一个本地方法,我们还是用老方法,看看它的值为什么.
@CallerSensitive
public static native <T> T doPrivileged(PrivilegedAction<T> action);
这里为null,我们就可以看上面的代码,最后可以看见我们的ALTERNATIVE_HASHING_THRESHOLD =2147483647,我们在返上面比较16>=2147483647为false,true && false返回false.
final boolean initHashSeedAsNeeded(int capacity) {
//通过上面的过程,我们知道了currentAltHashing =false
boolean currentAltHashing = hashSeed != 0;
//useAltHashing = false
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
// false ^ false 结果为false,switching为false
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
//返回false
return switching;
}
通过上面的流程以后,我们知道hashSeed 并没有重新赋值,最终hashSeed的值为0;
书山有路勤为径,学海无涯苦作舟。