从源码看TreeMap、HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap特性
TreeMap、HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap 这几个都是Map的实现类,但是都有各自的特点:
- HashMap是基于Hash算法实现
- Hashtable具有线性安全的特点
- TreeMap取出来的键值对是排序后
- LinkedHashMap是按照插入顺序取出来的键值对
- ConcurrentHashMap并发包下Map,也具有线性安全的特点
- 基于hash算法的HashMap
HashMap是基于hash算法(hash算法具体思想请自行查询,Hash算法可以这么理解:key通过hash函数后会得到index),HashMap保存键值对的对象是Entry<K,V>。HashMap是基于Entry来保存键值对的,接下来根据源代码解析(代码有删减)。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
}
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
/**
* An empty table instance to share when the table is not inflated.
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
}
现在以put方法说明。在调用 put(K key,V val) 方法时 ,首先判断Map是否初始化,如果没有初始化先初始化;然后算出key的hash值,根据hash计算出key在数组的位置,接着顺着这条链进行比较,有相同的key,则替换并返回原来的值;如果不存在,则新添加key到链里面去。(这里面还涉及到HashMap的动态扩容,这里就不说明了)
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
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;
}
- 保持插入顺序的LinkedHashMap
LinkedHashMap的主要特点是保持着键值对put进来的顺序。LinkedHashMap是继承的HashMap的,并且没有重写put方法。但是重写了Entry对象,在每个Entry对象里面都有两个引用:前、后(当前元素的前面的元素和后面的元素,也就是双向链表),所以存放的元素的结果跟HashMap一模一样,不同的是每一个元素有指向前面元素和后面元素的引用(理解为关系)。LinkedHashMap持有第一个键值对(也就是链表的头),这样在put元素的时候,维持好前后关系,也就相当于保持了put进来元素的顺序。
/**
* LinkedHashMap entry.
*/
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
}
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
/**
* The head of the doubly linked list.
*/
private transient Entry<K,V> header;
}
由于LinkedHashMap没有重写父类的put方法,所以调用的时候跟HashMap的一摸一样,但是真的把Entry放到某个位置,调用的是 addEntry() 方法(上面没有仔细说),并且LinkedHashMap重写了 addEntry() 方法。而且在 addEntry() 里面调用的 HashMap 的 addEntry。但是 HashMap 的 addEntry 方法调用的 createEntry 方法 LinkedHashMap 也重写,并且在 createEntry 里面调用了 LinkedHashMap 里面的 Entry 的 addBefore 方法来维护关系(理解不清晰时,画图)。代码如下
/**
* This override alters behavior of superclass put method. It causes newly
* allocated entry to get inserted at the end of the linked list and
* removes the eldest entry if appropriate.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
/**
* This override differs from addEntry in that it doesn't resize the
* table or remove the eldest entry.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
- 自动排序的TreeMap
TreeMap每个put的元素都会根据key的值进行排序。TreeMap的只持有了一个根元素,TreeMap的Entry是采用二叉树的方式保存数据,很容易就想到二分查找算法来进行插入。TreeMap有比较器,在put方法里面可以看到如果在new TreeMap的时候传人比较器,那么使用比较器比较大小,否则 Key 实现 Comparable接口来比较大小。
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
/**
* The comparator used to maintain order in this tree map, or
* null if it uses the natural ordering of its keys.
*
* @serial
*/
private final Comparator<? super K> comparator;
private transient Entry<K,V> root = null;
}
/**
* Node in the Tree. Doubles as a means to pass key-value pairs back to
* user (see Map.Entry).
*/
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
boolean color = BLACK;
}
当调用 put() 方法存放数据的时候,首先判断根是否为空,如果为空则把第一个元素存放在根的位置;如果不为空,则判断比较器是否为空,如果不为空则使用比较器,如果为空则将 key 强转成Compareble接口进行比较;比较从根开始比较,如果二叉树是左小右大(二叉树相关知识,请自行学习)。拿当前节点的 key 和要存放的 key 进行比较,如果比当前节点的 key 大,则拿右子树比较;如果比当前节点的 key 小,则拿左子树比较,并依此递归,直到找到子树为空的位置,然后把要存放的元素插入此位置(二叉树遍历,请自行学习)
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
- Hashtable的线性安全
Hashtable实现大体和HashMap一样,Hashtable的特点是线性安全,线性安全主要是为了解决多线程操作的场景,最简单的方式就是同步(使用synchronized关键字)。
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 = hash(key);
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;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
- ConcurrentHashMap的线性安全
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable {
/**
* The segments, each of which is a specialized hash table.
*/
final Segment<K,V>[] segments;
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
}
static final class Segment<K,V> extends ReentrantLock implements Serializable {
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
}

浙公网安备 33010602011771号