HashMap是一个常用的类。它与HashTable类似,只不过它可以设置key和value为null,且它不是线程安全的。
它有两个参数影响性能,一个是initial capacity,一个是load factor
initial capacity 默认值是4,load factor的默认值是0.75
看下HashMap的构造方法
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
这个DEFAULT_INITIAL_CAPACITY
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 4;
默认是4,必须为2的n次方
再看DEFAULT_LOAD_FACTOR
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认是0.75f
再看构造方法的具体实现
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
} else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {
initialCapacity = DEFAULT_INITIAL_CAPACITY;
}
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Android-Note: We always use the default load factor of 0.75f.
// This might appear wrong but it's just awkward design. We always call
// inflateTable() when table == EMPTY_TABLE. That method will take "threshold"
// to mean "capacity" and then replace it with the real threshold (i.e, multiplied with
// the load factor).
threshold = initialCapacity;
init();
}
可以看到这里把initalCapacity赋值给了threshold成员
向HashMap中添加key和value用到的是put方法
看下put方法的源码
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);
int i = indexFor(hash, table.length);
for (HashMapEntry 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;
}
第一句,如果table为空table,就加载一个table
看inflateTable方法
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
// Android-changed: Replace usage of Math.min() here because this method is
// called from the of runtime, at which point the native libraries
// needed by Float.* might not be loaded.
float thresholdFloat = capacity * loadFactor;
if (thresholdFloat > MAXIMUM_CAPACITY + 1) {
thresholdFloat = MAXIMUM_CAPACITY + 1;
}
threshold = (int) thresholdFloat;
table = new HashMapEntry[capacity];
}
toSize就是threshold,在构造方法里,被赋值为4
那么这里table为一个4个元素的HashMapEntry数组
这个有个方法roundUpToPowerOf2(toSize),这个方法是找到最接近toSize的2的n次方的数
这个方法的原理后面细看
继续看put方法
如果key==null,则执行putForNullKey,说明HashMap是允许key为null的
看这个putForNullKey
private V putForNullKey(V value) {
for (HashMapEntry e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
这里看到,从table的第0个位置开始遍历数组
如果找到第一个key为null的位置,替换原来的value,并返回原有的value
如果遍历完数组都没有key为null的情况,则执行addEntry(0,null,value,0);
看这个addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
if里暂时不去看,看这个createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMapEntry e = table[bucketIndex];
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}
这里会在buketIndex的位置创建一个HashMapEntry对象,并且size++
看下这个HashMapEntry的构造方法
HashMapEntry(int h, K k, V v, HashMapEntry n) {
value = v;
next = n;
key = k;
hash = h;
}
HashMapEntry是HashMap的一个内部类,它有4个从成员,分别是value,next,key,hash
在createEntry方法里,可以看到,如果在table的bucketIndex位置原来就HashMapEntry对象的,那么会将新创建的HashMapEntry插入原有HashMapEntry的头部,构成一个链表
继续看put方法
如果key不是null的情况,则会通过sun.misc.Hashing.singleWordWangJenkinsHash(key)这个方法算出一个int 类型hash值
然后通过indexFor(hash,table.length)方法算出index
看这个indexFor方法
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
算法很简洁,就这么与一下就出来了,至于上面那个hash值是怎么算的,这要另外深入研究了
算出索引后,就要往table里插入数据了
for (HashMapEntry 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;
}
}
看这里代码,从i位置开始遍历,如果找到hash值相同,key也相同的HashMapEntry,则认为是同一个key-value,那么替换原有的value为新的value,并返回原有的value,插入完成。
如果key相同,hash值肯定相同,但是反过来不一定,即hash值相同,但key不一定相同。
如果遍历完都没有找到符号以上条件的,则执行addEntry方法
回过头来继续看addEntry方法
addEntry第一行就是if语句,它这里判断当前的table的空间是否够用,
如果不够用,则要扩充table空间,在原来基础上增加一倍的空间
看resize方法
void resize(int newCapacity) {
HashMapEntry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
HashMapEntry[] newTable = new HashMapEntry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
这里就是创建了一个新的数组,并且原有数组中元素搬到新的数组中,看transfer方法
void transfer(HashMapEntry[] newTable) {
int newCapacity = newTable.length;
for (HashMapEntry e : table) {
while(null != e) {
HashMapEntry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
transfer方法会遍历原有数组,然后重新计算在新数组中的index,将HashMapEntry对象放入新的数组中
判断是否扩容后,然后就是createEntry,createEntry方法上面已经分析了
下面分析get方法
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
如果key是null,就调用getForNullKey()
private V getForNullKey() {
if (size == 0) {
return null;
}
for (HashMapEntry e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
可以看到,从table[0]位置取值,如果key为null则返回value
当key不为null的情况,调用getEntry
final Entry getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
for (HashMapEntry e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
首先算出hash值,然后再算出索引,再index出去判断,如果hash值相同且key相同的情况,就取出value
如果key相同,遍历链表,找出key相同的情况,如果遍历完了还找不到,则返回null