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个写线程执行,并发性能的提升是显而易见的。
     

  • 相关阅读:
    ES6:Iterator遍历器
    前端:对BFC的理解
    前端:性能优化之防抖与节流
    ES6新增数据类型Symbol
    ajax和fetch、aixos的区别
    我对js数据类型的理解和深浅(copy)的应用场景
    egg的基本使用
    前端:css3的过渡与动画的基础知识
    Java基础篇之类
    JAVA基础篇之Scanner
  • 原文地址:https://www.cnblogs.com/liqinzhen/p/12928698.html
Copyright © 2011-2022 走看看