zoukankan      html  css  js  c++  java
  • hashMap&hashtable&ConcurrentMap的区别

    每次面试几乎都被问到hashmap相关的东西,本来以为知道它的底层原理就可以了,然后远远不够,每次被问到特别懵逼,昨天面试完男朋友(ps:男朋友是知名985毕业的,我是个三流211毕业的)给我讲了一下所有可能遇到的问题,今天特意将这些整理出来。

    1.HashTable


    (1)继承于Dictionary,实现了Map,Cloneable,Java.io.Serializable接口

    (2)底层数组+链表实现,无论key还是value都不能为null,同步线程安全,实现线程安全的方式是锁住整个hashtable,效率低,concurrentMap做了相关优化。


    (3)初始容量为11 扩容:newsize=oldsize*2+1


    (4)两个参数影响性能:初始容量,加载因子(默认0.75)


    (5)计算index方法:index=(hash&0x7FFFFFFF)%tab.length

    hashtable的底层源码:

    (1)HashTable继承于Dictionary,实现了Map,Cloneable,Java.io.Serializable接口

    public
    class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable{
    (2)HashTable两个参数影响性能:初始容量,加载因子(默认0.75)
    public
    Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); }
    
    
    (3)HashTable初始容量为11 扩容:newsize=oldsize*2+1

    public
    Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /** * Constructs a new, empty hashtable with a default initial capacity (11) * and load factor (0.75). */ public Hashtable() { this(11, 0.75f); }

     (4)初始容量为11 扩容:newsize=oldsize*2+1

    public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }

       (5) 计算index方法:index=(hash&0x7FFFFFFF)%tab.length

    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 = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }

    }

    2.HashMap

    (1)底层数组+链表+红黑树实现,可以存在null键和null值,线程不安全


    (2)初始size为16 扩容:newsize=oldsize*2,size一定为2的n次幂


    (3)扩容针对整个map,每次和扩容时,原数组的元素重新计算存放位置,并重新插入。


    (4)插入元素后才判断是否需要扩容,若再无插入,无效扩容


    (5)加载因子:默认0.75


    (6)计算index方法:index=hash&(tab.length-1)


    (7)空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。


     注意:

    (1)JDK1.8以后,hashmap的底层结构,由原来单纯的数组+链表,改为链表长度为8时,开始由链表转变为红黑树

     (2)至于为什么要将链表在长度为8时转变为红黑树呢?          

       原因是链表的时间复杂度是O(n),红黑树的时间复杂度O(logn),很显然,红黑树的复杂度是优于链表的
    
       因为树节点所占空间是普通节点的两倍,所以只有当节点足够多的时候,才会使用树节点。也就是说,节点少的时候,尽管时间复杂度上,红黑树比链表好

    一点,但是红黑树所占空间比较大,综合考虑,认为只能在节点太多的时候,红黑树占空间大这一劣势不太明显的时候,才会舍弃链表,使用红黑树。

    也就是大部分情况下,hashmap还是使用的链表,如果是理想的均匀分布,节点数不到8,hashmap就自动扩容了。

    hashmap的底层原理:

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable {
    hashmap是如何计算键值对的存储位置的?
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    如果键值对的key为null,则将它存储在bucket下标为0的位置,如果key不为null,则通过key.hashCode()) ^ (h >>> 16计算。
    static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
    }

    hashmap如何存储的?

       HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来

    计算hashcode,让后找到
    bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表

    来解决碰撞问题,当发生碰撞了,对象将会储存在链表
    的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

    注意:JDK1.8以后,hashmap的底层结构,由原来单纯的数组+链表,改为链表长度为8时,开始由链表转变为红黑树。

    3.ConcurrentMap

    (1)底层采用分段的数组+链表实现,线程安全。


    (2)通过把整个map分为N个Segment,可以提供相同的线程安全效率提升N倍,默认16倍。


    (3)读操作不加锁,修改操作加分段锁,允许多个修改操作并行发生。


    (4)扩容:段内扩容(段内元素超过该段对应的Entry数组的0.75,触发扩容,而不是整段扩容),插入前检测是否需要扩容,避免无效扩容。


    (有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁)。


    (5)存储结构中ConcurrentHashMap比HashMap多出了一个类Segment,而Segment是一个可重入锁。


    (6)ConcurrentHashMap是使用了锁分段技术来保证线程安全的。

    锁分段技术是什么呢?

          锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    ConcurrentHashMap和Hashtable的不同点:

    (1)ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。

    (2)Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。

    (3)ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有

    16个写线程执行,并发性能的提升是显而易见的。
     

  • 相关阅读:
    Unique Binary Search Trees——LeetCode
    Binary Tree Inorder Traversal ——LeetCode
    Maximum Product Subarray——LeetCode
    Remove Linked List Elements——LeetCode
    Maximum Subarray——LeetCode
    Validate Binary Search Tree——LeetCode
    Swap Nodes in Pairs——LeetCode
    Find Minimum in Rotated Sorted Array——LeetCode
    Linked List Cycle——LeetCode
    VR AR MR
  • 原文地址:https://www.cnblogs.com/liqinzhen/p/12928698.html
Copyright © 2011-2022 走看看