Java总结--集合(Map)

168 阅读3分钟

Java的集合包括两个接口:Collection和Map

下面只列出我接触过的Map

SortedMap

TreeMap

和SortedSet一样,SortedMap接口也实现了一个TreeMap。TreeMap和TreeSet有很多共同之处。

比如:

  • 数据结构:红黑树
  • 支持自然排序和定制排序

小结:要对一个 key 集合进行有序的遍历,则应该使用TreeMap。

HashMap

HashMap

  • 数据结构:entry数组(每一个entry元素又是一个链表的头结点,相当于数组+链表的结构)
  • 实现原理:
    • put:HashMap首先通过对key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 entry元素里,当计算出的 hash 值相同时,称为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value(jdk1.7用的是头插法。1.8改成尾插法,因为多个线程同时插入新元素时头插法可能会出现环形链表)。当 hash 冲突的个数比较少时,使用链表否则使用红黑树。
    • get:get(key)来获取
    • 扩容机制:初始化一个HashMap的entry数组长度是16,负载因子是0.75,当put的长度>HashMap数组长度x负载因子时扩容,创建一个新的Entry空数组,长度是原数组的2倍,然后重新计算哈希值存入新的数组。 Hash公式为index = HashCode(Key) & (Length - 1)

HashTable vs HashMap

线程安全性性能
HashTable加了sychronized ,线程安全
HashMap线程不安全

小结:如果多个线程同时访问应该使用线程安全的HashTable,如果追求性能应该使用HashMap

如果想要HashMap也实现线程安全怎么办?

  • 法1:Collections.synchronizedMap(hashMap)来进行处理
  • 法2:使用线程安全的ConcurrentHashMap,性能和效率强于法1

ConcurrentHashMap

ConcurrenHashMap是在java.util.concurrent里的,jdk1.7和jdk1.8对ConcurrenHashMap的实现有很大的区别,但都是数组+链表的结构:

jdk1.7:

  • 底层数据结构:Segment数组+HashEntry实现的,每一个HashEntry元素都是一个链表的头结点,和HashMap的entry元素很像,区别HashEntry元素是使用volatile去修饰了数据Value还有下一个节点next。
  • 实现原理:
    • 实现并发度高的原因:分段锁。对每个Segment元素都可以加synchronized ,而且可以同时对整个Segment数组的每一个元素都加锁。
    • put: 先Hash一次定位到对应Segment元素中,获得锁,再Hash一次定位到对应的HashEntry遍历链表,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。否则创建一个新的结点存储。
    • get:两次Hash定位到相应的HashEntry,通过遍历链表获取值。HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

jdk1.8

  • 底层数据结构:Node数组+链表/红黑树
  • 实现原理:synchronized+CAS实现优化
    • put:Hash一次定位到相应的Node元素,通过CAS自旋保证获得锁,获得锁之后,存储值,最后释放锁。
    • get:Hash一次定位到相应大的Node元素,如果直接在桶里直接返回值,否则遍历链表或红黑树。
    • 扩容机制:在Hash的时候,也就是定位Node元素的时候,如果计算出的hashcode值为-1则需要对Node扩容
    • 优化点:
      • 1.抛弃Segment数组:少了一次Hash,把“二级HashMap”降为一级
      • 2.对Node数组元素精准加锁,由于jdk优化了synchronized实现了锁升级,能保证线程一定能拿到锁,避免了线程获取锁失败而挂起
      • 3.链表结点超过8(默认)则转化成红黑树,避免hash冲突多了=>链表结点多=>查询性能下降。用了红黑树可以保证遍历的时间复杂度为O(logN)