一 HashTable
- 底层数组+链表实现
- key和value都不允许为空
- 线程安全:实现的方式为修改数据锁住整个HashTable,不过效率较低。在这基础上,concurrentHashmap做了优化,采用分段锁机制。
hashtable实现线程安全主要是在方法加synchronized关键字。
如:
- 而且hashtable含有contains(),containsvalue()方法,而hashmap只有containsvalue()方法
- hashtable的初始容量initiaCapacity=11,负载因子loadFactor=0.75,扩容方式为2initiaCapacity+1倍扩容,扩容时机为,当前的数据size达到initiaCapacityloadFactor=8.25(约等于9)时就会扩容,扩容后capacity=11*2+1=23
- 计算index的办法,index=(hash&0x7FFFFFFF)%tab.length,先根据hash值与0x7FFFFFFF做&运算,再对桶的长度进行取模,得到在桶中的索引号。
二 HashMap
- java1.8之前底层数组+链表,1.8之后底层数组+链表+红黑树
- key和value允许为空,但是只允许一个key为空,多个value可以为空
- 线程不安全,在多线程情况下会出现数据不一致,死循环问题。可以用concurrentHashmap解决
- 初始容量initialCapacity=16,每次扩容2initiacapacity。负载因子loadFactor=0.75,扩容时机为当map中元素数量达到initiaCapacityloadFactor=12时发生扩容,最大为2的30次方
- 计算index的方法:index=hash&(tab.lenth-1),相当于(hash&0x7FFFFFFF)%tab.length作位运算,效率比hashtable高
HashMap基于哈希思想,实现对数据的读写。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来存储值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞时,对象将会储存在链表的下一个节点中。HashMap在每个链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时,它们会储存在同一个bucket位置的链表中,可通过键对象的equals()方法来找到键值对。如果链表大小超过阈值(TREEIFY_THRESHOLD,8),链表就会被改造为树形结构。
在HashMap中,null可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该key,也可以表示该key所对应的value为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个key,应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null。
HashMap的初始值还要考虑加载因子:
哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。
加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。
空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。
三 ConccurentHashMap:
- 底层采用分段数组+链表
- 通过把整个Map分为N个segment,提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是volatile的,也能保证读到最新的值)
- Hashtable的synchronized是针对整个表的,即每次锁住整个表让线程独占。Concurrenthashmap允许多个修改操作并发进行,关键在于锁分离技术
- 有些方法需要跨段,比如size(),containsValue(),这些方法会把所有段都顺序锁住,当完成操作后再释放锁
- 扩容:段内扩容,当每个段的元素超过该段的75%,该段会进行扩容,而不会对整个map扩容。插入前检测需不需要扩容,避免无效扩容。
从类图中可以看出来在存储结构中ConcurrentHashMap比HashMap多出了一个类Segment,而Segment是一个可重入锁。
ConcurrentHashMap是使用了锁分段技术来保证线程安全的。
锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。
ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。