一、HashMap、Hashtable
1.HashMap的key和value可以为null,而Hashtable则不可以。
2.HashMap是非synchronized,而Hashtable是synchronized的。也就是说Hashtable是线程安全的,多个线程可以共享同个Hashtable。如果没有正确同步的话,多个线程不能共享HashMap。Java5之后提供ConcurrentHashMap,替代Hashtable,比Hashtable更好的扩展性。
3.在单线程下,HashMap的性能要优于Hashtable。因为Hashtable是使用synchronized的,所有线程竞争同一把锁。
二、HashMap的工作原理
HashMap实际上是链表散列的数据结构,即数组和链表的结合体。我们可以理解为HashMap底层是数组结构,数组中的每一项是一个链表。
1、HashMap的存储
public V put(K key, V value) {
//HashMap允许存储一个null键和多个null值
//空数组的话,初始化数组容量,初始化长度为16
//static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 当key为null,将它放在数组的第一个位置
if (key == null)
return putForNullKey(value);
// 根据key的keyCode重新计算hash值。
int hash = hash(key);
// 搜索指定hash值在对应table中的索引。
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
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;
}
}
// 如果i索引处的Entry为null,表明此处还没有Entry。
modCount++;
// 将key、value添加到i索引处。
addEntry(hash, key, value, i);
return null;
}
从上面代码我们可以看出当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
2、HashMap的读取
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> 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;
}
从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
3、HashMap的扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 初始化HashMap的长度
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
当HashMap的元素个数大于16*0.75=12的时候,就把数组的大小扩展为2*16=32,及扩容一倍。然后重新计算每个元素在数组中的位置,这是非常消耗性能的操作。所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。