浅析Hashmap和Hashtable
一、Hashmap不是线程安全的,而Hashtable是线程安全的
通过查看源码可以发现,hashmap类中的方法无synchronized关键字,而hashtable类中的方法有synchronized关键字修饰。
二、Hashmap允许key和value为null,Hashtable则不允许
hashmap允许key和value为null,当key为null时会将其置于table[0]的链表中进行存储;而hashtable则会抛出异常。
1 以下代码及注释来自java.util.HashTable 2 3 public synchronized V put(K key, V value) { 4 5 // 如果value为null,抛出NullPointerException 6 if (value == null) { 7 throw new NullPointerException(); 8 } 9 10 // 如果key为null,在调用key.hashCode()时抛出NullPointerException 11 12 // ... 13 } 14 15 16 以下代码及注释来自java.util.HasMap 17 18 public V put(K key, V value) { 19 if (table == EMPTY_TABLE) { 20 inflateTable(threshold); 21 } 22 // 当key为null时,调用putForNullKey特殊处理 23 if (key == null) 24 return putForNullKey(value); 25 // ... 26 } 27 28 private V putForNullKey(V value) { 29 // key为null时,放到table[0]也就是第0个bucket中 30 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 31 if (e.key == null) { 32 V oldValue = e.value; 33 e.value = value; 34 e.recordAccess(this); 35 return oldValue; 36 } 37 } 38 modCount++; 39 addEntry(0, null, value, 0); 40 return null; 41 }
三、索引的计算方式不同
hashmap采用“与运算”,而hashtable采用取模运算,相比较而言hashmap的计算效率更高。
/*---------hashmap---------*/ public V put(K key, V value) { //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(24=16) if (table == EMPTY_TABLE) { inflateTable(threshold); } //如果key为null,存储位置为table[0]或table[0]的冲突链上 if (key == null) return putForNullKey(value); int hash = hash(key);//对key的hashcode进一步计算,确保散列均匀 int i = indexFor(hash, table.length);//获取在table中的实际位置 for (Entry<K,V> e = table[i]; e != null; e = e.next) { //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value 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++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败 addEntry(hash, key, value, i);//新增一个entry return null; } /*------hashtable--------------*/ public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
四、初始容量及扩容算法不同
hashmap的初始容量是16,而hashtable的初始容量为11;hashmap采用的算法是:2*oldSize,而hashtable采用的算法是2*oldSize+1;
以下代码及注释来自java.util.HashTable // 哈希表默认初始大小为11 public Hashtable() { this(11, 0.75f); } protected void rehash() { int oldCapacity = table.length; Entry<K,V>[] oldMap = table; // 每次扩容为原来的2n+1 int newCapacity = (oldCapacity << 1) + 1; // ... } 以下代码及注释来自java.util.HashMap // 哈希表默认初始大小为2^4=16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 void addEntry(int hash, K key, V value, int bucketIndex) { // 每次扩充为原来的2n if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); }
the ending!