hashMap&hashtable&ConcurrentMap的区别

时间:2020-05-21
本文章向大家介绍hashMap&hashtable&ConcurrentMap的区别,主要包括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个写线程执行,并发性能的提升是显而易见的。
 

原文地址:https://www.cnblogs.com/liqinzhen/p/12928698.html