HashMap和Hashtable,ConcurrentHashMap区别
1.类定义
源码中可以看出:
Hashtable继承Dictionary,而HashMap 继承自 AbstractMap,ConcurrentHashMap继承自 AbstractMap。
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {};
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable{};
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable {}
2.线程安全性
HashTable源码:底层数组+链表实现,大部分方法被synchronized修饰
public synchronized int size() { return count; }
public synchronized boolean isEmpty() { return count == 0;}
public synchronized V get(Object key){}
public synchronized boolean contains(Object value) {}
......
HashMap源码:底层数组+链表实现 ,没有被synchronized修饰
public boolean isEmpty() { }
public V get(Object key) { }
public boolean containsKey(Object key) { }
public V put(K key, V value){ }
......
ConcurrentHashMap源码:底层采用分段的数组+链表实现 ,底层被synchronized修饰
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
因此:Hashtable 在很多方法定义时都会加上 synchronized关键字,说明 Hashtable 是线程安全的,而 HashMap 并不能保证线程安全;ConcurrentHashMap的put方法返回调用 putVal()方法,而此方法底层被synchronized修饰,因此线程安全。
3. key 和 value 是否允许 null
HashTable源码:在 Hashtable 添加元素源码中,我们可以发现,如果添加元素的 value 为 null 时,会抛出 NullPointerException。在程序内部,有这样一行代码 int hash = key.hashCode
,如果添加的 key 为 null 时,此时也会抛出空指针异常,因此,在 Hashtable 中,是不允许 key 和 value 为 null 的。
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源码:而在 HashMap 的 put 方法中,调用了 putVal
方法(1.8 版本中),该方法需要有一个 int 类型的 hash
值,这个值是利用内部的 hash
方法产生的。从下面的源代码可以看出,当 key 为 null 时,返回的 hash 值为 0,说明在 HashMap 中是允许 key=null 的情况存在的。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict){
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
4.是否提供contains方法
从 HashMap 的 API 可以看出,它只包含 containsKey 和 containsValue 方法。而 Hashtable 还包含了 contains 方法。
HashMap 中把 contains 方法去掉的原因主要它容易引起混淆,不如 containsKey 和 containsValue 表达的准确。
而 Hashtable 中 contains 方法也是调用 containsKey 方法来实现的。
public boolean containsValue(Object value) {
return contains(value);
}
5. 初始容量
Hashtable 初始容量为 11,默认的负载因子为 0.75。HashMap 定义了两个常量在对容器进行初始化会用到,可以看到其初始容量为 16,默认的负载因子也是为 0.75。
//---------------------Hashtable-----------------------------
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
// 这里对桶进行初始化
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
//---------------------HashMap-----------------------------
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
ConcurrentHashMap扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
6.扩容程度
Hashtable 扩容时调用 rehash
方法,增加容器容量的代码在下面。从中可以看出,最终的容量是 newCapacity
,如果该变量在没有大于 MAX_ARRAY_SIZE
(静态变量,内部定义为 Integer.MAX_VALUE - 8) 之前,都是按照 oldCapacity*2 + 1 的速度增加的。
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
在 HashMap 中,扩容主要是通过 resize
方法实现的,其扩容的代码是这样的 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
,可见是跟 newCap 变量有关,在正常情况下,newCapa 是按照 oldCap<<1
的速度,即每次长度变为原来的两倍增长的。
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
总结:
HashMap和HashTable区别:
1.HashMap线程非安全,HashTable,ConcurrentHashMap底层方法被synchronized修饰,线程安全;
2.HashMap允许键值为空,HashTable,不允许键值为空,为空时报NullPointerException异常;
3.Hashtable继承Dictionary,而HashMap ,ConcurrentHashMap继承自 AbstractMap。
4.Hashtable 初始容量为 11,默认的负载因子为 0,.75。HashMap 定义了两个常量在对容器进行初始化会用到,可以看到其初始容量为 16,默认的负载因子也是为 0.75.;
5.Hashtable 扩容时调用 rehash
方法,增加容器容量的代码在下面。从中可以看出,最终的容量是 newCapacity
,如果该变量在没有大于 MAX_ARRAY_SIZE
(静态变量,内部定义为 Integer.MAX_VALUE - 8) 之前,都是按照 oldCapacity*2 + 1 的速度增加的;
在 HashMap 中,扩容主要是通过 resize
方法实现的,其扩容的代码是这样的 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
,可见是跟 newCap 变量有关,在正常情况下,newCapa 是按照 oldCap<<1
的速度,即每次长度变为原来的两倍增长的。
以上总结是我结合字节的理解和转载他人部分写的一个面试题,因为最近在找工作所问到的东西。
推荐阅读
-
Java中HashMap和Hashtable及HashSet的区别
-
浅析Java中Map与HashMap,Hashtable,HashSet的区别
-
浅析java中ArrayList与Vector的区别以及HashMap与Hashtable的区别
-
Java中HashMap和Hashtable及HashSet的区别
-
浅析Java中Map与HashMap,Hashtable,HashSet的区别
-
浅析java中ArrayList与Vector的区别以及HashMap与Hashtable的区别
-
java中Hashtable和HashMap的区别分析
-
Java中HashMap和TreeMap的区别深入理解
-
java中Hashtable和HashMap的区别分析
-
HashTable、HashSet和Dictionary的区别点总结