成员变量
这里的table,就是所谓的 “桶” 的概念,存储 键/值 对。它的长度必须为2的次幂,原因不知。
具体一点,table的初始长度是 DEFAULT_INITIAL_CAPACITY(16),当存储数量的元素大于 16*0.75 时,会进行扩充容量,长度变为 1 << 4 (32)。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 空的桶
static final Entry<?,?>[] EMPTY_TABLE = {};
// 桶,按需调整大小,长度为2的次幂(长度的限制为何如此?)
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
transient int size;
// 负载值,下次扩容的临界值,为:(capacity * load factor)
int threshold;
// 加载因子
final float loadFactor;
transient int modCount;
/**
* 哈希因子,避免哈希值重复;
* 在initHashSeedAsNeeded 初始化,并用于 final int hash(Object k) 方法中,算key的哈希值
*/
transient int hashSeed = 0;
Holder
下面的代码还不太理解,据说是:
“ALTERNATIVE_HASHING_THRESHOLD_DEFAULT 针对与字符串的key,提供一个新的hash算法会提供更好的hashcode分布减少冲突,如果想启用尝鲜这个特性,你需要设置jdk.map.althashing.threshold这个系统属性的值为一个非负数(默认是-1)这个值代表了一个集合大小的threshold,超过这个值,就会使用新的hash算法。需要注意的一点,只有当re-hash的时候,新的hash算法才会起作用。
而Holder本身只是加载获取这个配置参数而已。”
而且在JDK8里面,这个方法已经被移除了,改用其他方式实现。
至于 java.security.AccessController.doPrivileged 可以参考 Spring源码–关于AccessController.doPrivileged
// 备选的负载值,以String作为Key时的备选负载值。
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
private static class Holder {
/**
* Table capacity above which to switch to use alternative hashing.
*/
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;
}
}
构造方法
- 大多数情况下,我们都会使用不带参数的构造方法,这时的 负载值 和 加载因子 都是默认的。如果要设置自己的 负载值 和 加载因子,可以调用相应的构造方法。
- init()初始方法,是一个空的函数。
public HashMap(int initialCapacity, float loadFactor) {
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;
threshold = initialCapacity;
// 这个函数是个空的
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
// 构造一个映射关系与指定 Map 相同的新 HashMap。
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
初始化操作
先看一下 Integer.highestOneBit 这个方法的作用:
将一个整数(二进制)设置最高位为1,其它位为0,然后返回改变后的值;
如果这个整数是0返回0。roundUpToPowerOf2 这个函数返回最接近 number 的2的次幂。如果 number 大于最大容量,则返回最大容量;如果 number 小于1,返回1;
例如:3 -> 4, 4 -> 4, 5 -> 8。
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
inflateTable 这个方法,在调用put方法的时候也会用到,主要工作是创建了一个存放元素的Entry类。
private void inflateTable(int toSize) {
// 获取容量为:最小的 >= toSize 的2次幂
int capacity = roundUpToPowerOf2(toSize);
// 计算出负载值
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
// 创建大小为capacity的Entry
table = new Entry[capacity];
// 根据capacity计算hashSeed的值
initHashSeedAsNeeded(capacity);
}
// 空的方法,子类可以重写该方法
void init() {}
// 这个方法不太理解。。。大意是依据capacity值计算出hashSeed,这个hashSeed用于以后计算hashCode
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
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;
}