阅读 295

你真的懂「Hashtable/HashMap/TreeMap」了么

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

最普通的想法

  • Hashtable本身是同步的,不支持null的键和值
    • 由于本身支持synchronized那么同步就导致的了很多的性能上的开销

图片.png

  • HashMap是我们用的最广泛的哈希表实现,
    • 行为上大致上与HashTable一致,
    • HashMapHashtable最大的区别在于HashMap不是同步的,支持null`键和值。
    • 但是正常情况下 HashMap进行put或者get操作,可以达到常数时间O(1)的性能
    • 所以我们在绝大部分利用键值对存取场景的首选

图片.png

  • TreeMap是基于红黑树的一种提供顺序访问的Map
    • 由于本身是基于红黑树的数据结构所以和HashMap不 同
    • TreeMapget、put、remove操作都是O(log(n))的时间复杂度,
    • 具体顺序可以由指定的Comparator来决定,或者根据键的自然顺序来判断。

图片.png > 在面试中也会经常被问到,HashMap出现无限循环占用CPU等问题bugs.java.com/bugdatabase…

有序的Map

  • LinkedHashMap属于 遍历的顺序就是插入的顺序
    • LinkedHashMap本身内部是由一个双向链表构成的
    • 我们可以用它来实现一个LRU算法

public class test {
    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>() {
        @Override
        protected boolean removeEldestEntry (Map.Entry<String, String> eldest){
            return size() > 2;
        }};
        map.put("a","1");
        map.put("b","2");
        System.out.println("第一次遍历:");
        map.forEach((k,v) -> {
            System.out.println(k + " : " + v);
        });
        // 访问前第一个元素
        map.get("b");      
        System.out.println("第二次遍历:");

        // 第二次输出
        map.forEach((k,v) -> {
            System.out.println(k + " : " + v);
        });
        // 此时会触发删除c-3
        map.put("c","3");
        System.out.println("第三次遍历:");

        // 第三次输出
        map.forEach((k,v) -> {
            System.out.println(k + " : " + v);
        });

    }

}
复制代码
  • 输出如下

图片.png

TreeMap

  • put源码解析
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();
       @SuppressWarnings("unchecked")
           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;
}
复制代码
  • 其中可以看到hashcodeequals是保持一个约定的

HashMap

  • put源码解析

图片.png

  • 可以看到重点在putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
复制代码
  • 首先是判空,如果为null
    • 那么就会resize()进行初始化,主要是扩容的工作
    • 在放置新的键值对的过程中就会发生扩容
    if (++size > threshold)
            resize();
    复制代码
    • 放在哈希表的位置汇中使用的是位运算去定位
    tab[i = (n - 1) & hash]
    复制代码
    • hash()方法
      • 是将高位数据移位到低位进行疑惑运算
      • 因为这些数据计算出的哈希值差异主要在高位
      • 而其中的哈希寻址是忽略容量以上的高位的,有效的避免了哈希碰撞
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h =        key.hashCode()) ^ (h >>> 16);
    }
    复制代码
    • 小谈resize()

图片.png

  • resize()最大容量是1<<30也就是 2^30
    • newThr = oldThr << 1其中限定值是以倍数增长的
    • 当元素个数超过了限定值,那么久调整Map的大小
文章分类
后端
文章标签