从源代码看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。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
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

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 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 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 k = (Comparable) 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的线性安全

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的线性安全
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;
        }
}

  • 最后
文章有不对的地方,还望不吝指教





你可能感兴趣的:(jdk,源码)