从源码看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的线性安全
ConcurrentHashMap的实现也跟HashMap相似,不同的是ConcurrentHashMap不是 Entry 数组,而是 Segment 数组。Segment 继承 ReentrantLock,在调用 ConcurrentHashMap 的 put 方法的时候,最后调用的是 Segment 的 put 方法。而在调用 Segment 方法的时候,Segment 会先 tryLock,最后在 finally 里面调用 unlock 释放锁。
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;
        }
}
posted @ 2016-04-25 12:36  風之殤  阅读(118)  评论(0)    收藏  举报