TreeMap、HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap 这几个都是Map的实现类,但是都有各自的特点:
- HashMap是基于Hash算法实现
- Hashtable具有线性安全的特点
- TreeMap取出来的键值对是排序后
- LinkedHashMap是按照插入顺序取出来的键值对
- ConcurrentHashMap并发包下Map,也具有线性安全的特点
HashMap是基于hash算法(hash算法具体思想请自行查询,Hash算法可以这么理解:key通过hash函数后会得到index),HashMap保存键值对的对象是Entry。HashMap是基于Entry来保存键值对的,接下来根据源代码解析(代码有删减)。
HashMap使用Entry数组来保存数据。而每一个Entry是以链表的形式链接一起,也就是相同根据hash值计算出来的数组位置索引(index)会出现重复,如果两个位置重复的,但是key不相等的话,会以链表的形势连接起来。源代码如下
static class Entry
implements Map.Entry { final K key; V value; Entry next; int hash; }
public class HashMap
extends AbstractMap implements Map , 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 [] table = (Entry []) 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
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的主要特点是保持着键值对put进来的顺序。LinkedHashMap是继承的HashMap的,并且没有重写put方法。但是重写了Entry对象,在每个Entry对象里面都有两个引用:前、后(当前元素的前面的元素和后面的元素,也就是双向链表),所以存放的元素的结果跟HashMap一模一样,不同的是每一个元素有指向前面元素和后面元素的引用(理解为关系)。LinkedHashMap持有第一个键值对(也就是链表的头),这样在put元素的时候,维持好前后关系,也就相当于保持了put进来元素的顺序。
/** * LinkedHashMap entry. */ private static class Entry
extends HashMap.Entry { // These fields comprise the doubly linked list used for iteration. Entry before, after; }
public class LinkedHashMap
extends HashMap implements Map { /** * The head of the doubly linked list. */ private transient Entry 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
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 old = table[bucketIndex]; Entry 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
existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; }
TreeMap每个put的元素都会根据key的值进行排序。TreeMap的只持有了一个根元素,TreeMap的Entry是采用二叉树的方式保存数据,很容易就想到二分查找算法来进行插入。TreeMap有比较器,在put方法里面可以看到如果在new TreeMap的时候传人比较器,那么使用比较器比较大小,否则 Key 实现 Comparable接口来比较大小。
public class TreeMap
extends AbstractMap implements NavigableMap , 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 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
implements Map.Entry { K key; V value; Entry left = null; Entry right = null; Entry parent; boolean color = BLACK; }
当调用 put() 方法存放数据的时候,首先判断根是否为空,如果为空则把第一个元素存放在根的位置;如果不为空,则判断比较器是否为空,如果不为空则使用比较器,如果为空则将 key 强转成Compareble接口进行比较;比较从根开始比较,如果二叉树是左小右大(二叉树相关知识,请自行学习)。拿当前节点的 key 和要存放的 key 进行比较,如果比当前节点的 key 大,则拿右子树比较;如果比当前节点的 key 小,则拿左子树比较,并依此递归,直到找到子树为空的位置,然后把要存放的元素插入此位置(二叉树遍历,请自行学习)public V put(K key, V value) { Entry
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 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 e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
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
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 e = tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; return null; }
ConcurrentHashMap的实现也跟HashMap相似,不同的是ConcurrentHashMap不是 Entry 数组,而是 Segment 数组。Segment 继承 ReentrantLock,在调用 ConcurrentHashMap 的 put 方法的时候,最后调用的是 Segment 的 put 方法。而在调用 Segment 方法的时候,Segment 会先 tryLock,最后在 finally 里面调用 unlock 释放锁。
public class ConcurrentHashMap
extends AbstractMap implements ConcurrentMap , Serializable { /** * The segments, each of which is a specialized hash table. */ final Segment [] segments; public V put(K key, V value) { Segment s; if (value == null) throw new NullPointerException(); int hash = hash(key); int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment )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
extends ReentrantLock implements Serializable { final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry node = tryLock() ? null : scanAndLockForPut(key, hash, value); V oldValue; try { HashEntry [] tab = table; int index = (tab.length - 1) & hash; HashEntry first = entryAt(tab, index); for (HashEntry 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 (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; } }
文章有不对的地方,还望不吝指教