HashMap ConcurrentHashMap Hashtable 工作中经常会用到, HashMap用的最多, ConcurrentHashMap次之,hashTable用的最少. 简单看了下源码,其实原因还是挺明显的.从JDK的发展历程来看,hashTable是1.0就发布的,属于最早的key-value形式的存储, 到了1.2才有hashMap, ConcurrentHashMap是1.5中发布的,它们的实现有很多类同,也有很多细节上的区别.这里主要通过查看源码来比较下它们在写入与读取对象之间的一些区别:
它们都是key-value形式写入对象, 具体实现还是通过数组来完成,key表示数组的下标,value就是这个下标对应的值.
先来看看它们各自的put()与get()方法源码:
1.Hashtable
put方法
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; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } } modCount++; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. Entry<K,V> e = tab[index]; tab[index] = new Entry<K,V>(hash, key, value, e); count++; return null; }
get方法
public synchronized V get(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return e.value; } } return null; }
Hashtable无论是put(),get()方法都采用了synchronized进行加锁处理,是线程安全的, 还有remove()等方法也是一样
2.HashMap
put()方法
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); 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; } } modCount++; addEntry(hash, key, value, i); return null; }
get()方法
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); 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.equals(k))) return e.value; } return null; }
HashMap 中的put(),get()等方法都没有采用锁机制,所以是线程不安全的.
3.ConcurrentHashMap
put()方法
V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { int c = count; if (c++ > threshold) // ensure capacity rehash(); HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue; if (e != null) { oldValue = e.value; if (!onlyIfAbsent) e.value = value; } else { oldValue = null; ++modCount; tab[index] = new HashEntry<K,V>(key, hash, first, value); count = c; // write-volatile } return oldValue; } finally { unlock(); } }
get()方法
V get(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K,V> e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) { V v = e.value; if (v != null) return v; return readValueUnderLock(e); // recheck } e = e.next; } } return null; }
ConCurrentHashMap 虽然也采用锁机制,但是与Hashtable不同的是它是一种分段锁, hashTable中的数组只有一把锁,只要一个线程锁住(无论是读操作还是写操作),其他线程就只能等待,高并发情况下性能比较差.ConCurrentHashMap中将数组切分成为若干小的数组,每个数组维护自己的锁,这样如果两个线程同时操作两个不同的数组,就不用等待,同时
conCurrentHashMap读取操作没有加锁,高并发情况下性能较好.
除了HashMap,Hashtable,ConCurrentHashMap的put(),get()方法之外,我们也能通过它们的序列化方法来看出一点端倪.
Hashtable的writeObject()方法
private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException { // Write out the length, threshold, loadfactor s.defaultWriteObject(); // Write out length, count of elements and then the key/value objects s.writeInt(table.length); s.writeInt(count); for (int index = table.length-1; index >= 0; index--) { Entry entry = table[index]; while (entry != null) { s.writeObject(entry.key); s.writeObject(entry.value); entry = entry.next; } } }
HashMap的writeObject()方法
private void writeObject(java.io.ObjectOutputStream s) throws IOException { Iterator<Map.Entry<K,V>> i = (size > 0) ? entrySet0().iterator() : null; // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); // Write out number of buckets s.writeInt(table.length); // Write out size (number of Mappings) s.writeInt(size); // Write out keys and values (alternating) if (i != null) { while (i.hasNext()) { Map.Entry<K,V> e = i.next(); s.writeObject(e.getKey()); s.writeObject(e.getValue()); } } }
ConCurrentHashMap的writeObject()方法
private void writeObject(java.io.ObjectOutputStream s) throws IOException { s.defaultWriteObject(); for (int k = 0; k < segments.length; ++k) { Segment<K,V> seg = segments[k]; seg.lock(); try { HashEntry<K,V>[] tab = seg.table; for (int i = 0; i < tab.length; ++i) { for (HashEntry<K,V> e = tab[i]; e != null; e = e.next) { s.writeObject(e.key); s.writeObject(e.value); } } } finally { seg.unlock(); } } s.writeObject(null); s.writeObject(null); }
总结一点: 开发需求中如果遇到高并发的情况不多,HashMap就能足够应付了, 高并发如果比较频繁建议还是用ConCurrentHashMap比较靠谱, Hashtable没有特别需求还是少用为妙. ConCurrentHashMap 有一段英文解释比较经典(Like Hashtable but unlike HashMap, this class does not allow null to be used as a key or value)