hashmap、hashtable和concurrenthashmap

hashmap、hashtable和concurrenthashmap

hashMap

  1. 继承自AbstarctMap类
  2. 底层数据结构是:数组+链表+红黑树
  3. key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。
  4. 初始容量为16,扩容是乘以2。
  5. 线程不安全的键值对存储结构,当有多线程同时修改的时候可能会形成环结构,从而造成死锁问题。

为什么容量要总是2的次幂

  1. 用与运算代替取模运算,速度快
  2. 为了散列更均匀,利用0&1 = 0, 1&1 = 1的特性
  3. 易于计算扩容后元素在数组索引位置,不是在原下标就是在原下标+扩容前的容量

hashtable

  1. 继承自Dictionary类
  2. key和value都不支持为null
  3. 初始容量为11,扩容乘以2+1
  4. 线程安全,每个方法都添加了synchronized
  5. 与hashmap的hashcode计算方法不同,hashtable直接使用hash函数计算的值,而hashmap还需要对计算得到的hashcode再取模运算
  6. 除迭代器外,还支持elements()用于枚举其value的值,多了一个contains()方法,与containsValue()的功能类似

concurrentmap

hashmap的线程不安全性
hashtable线程安全但是效率低

JDK1.7

hsahmap在JDK1.7的数据结构是:数组+链表
  • ConcurrentHashMap使用分段锁,它是由segment数组结构和HashEntry数组结构构成的,其中,segment是一种可重入锁,而HashEntry是键值对的数据存储结构。
    • Segment结构:每个segment包含一个HashEntry数组,它是链表结构的元素,segment就是这个hashentry的锁。
  1. 初始化segment数组
    segment数组的长度ssize是通过concurrencyLevel计算的,是大于等于concurrencyLevel(默认为16)的最小的2的n次幂。这是为了能够通过按位与计算出segments数组的下标索引。
    sshift是ssize从1向左移位的次数,默认为4。segmentshift用于定位参与散列运算的位数,等于32-sshift;而segmentMask是散列运算的掩码,等于ssize-1;
  2. 初始化每个segment
    inittial是ConcurrentHashMap的初始化容量,loadfactor是每个segment的负载因子,在初始化Segment时,也定义了一个中间变量cap,其等于initialCapacity除以ssize的倍数c,如果c大于1,则取大于等于c的2的N次方,cap表示Segment中HashEntry数组的长度;loadFactor表示了Segment的加载因子,通过cap*loadFactor获得每个Segment的阈值threshold.
    默认情况下,initialCapacity等于16,loadFactor等于0.75,concurrencyLevel等于16,threshold等于0.
  3. 定位segment
    concurrentMap对元素的hashcode进行再散列,旨在散列更均匀来减少散列冲突。如果元素散列后都在同一个segment上面,其实也就失去了分段锁的意义。
  4. 分段锁的操作:
    1. get()操作:经过再散列,定位Segment –> 然后根据该散列值通过散列运算定位HashEntry –> 通过getObjectVolatile()方法获取指定偏移量上的HashEntry –> 通过循环遍历链表获取对应值。
      get是不加锁的,因为concurrenthashmap上get方法用到的共享变量都是volatile变量,保证了多线程间的可见性。
    2. put()操作:需要加锁。首先定位到segment,然后在segment里进行插入操作。插入操作分为两个步骤:
      1)判断是否需要扩容:插入元素前进行扩容判断,分段式扩容,扩容乘以2。
      2)定位需要添加元素的位置,然后将其放在hashEntry数组里。
    3. size()操作:每个segment的count是一个volatile变量,但是在累加的过程中,前面的segment的count可能还是发生了变化。ConcurrentHashMap先尝试两次不加锁的累计统计,定义一个modcount的变量,如果有put,remove或者clean的操作时,会对这个参数进行加1,通过比较modcount累加前和累加后的值有误变化来判断size()是否发生了变化,如果两次尝试失败,就会对所有的put、remove和clean方法加锁再统计容器的size()。

JDK1.8

jdk1.8之后的hashmap的底层数据结构是:数组+链表+红黑树
  • concurrentMap使用CAS+synchronized关键字实现。
  1. concurrentHashMap的初始化
    通过指定的初始容量initialCapacity,加载因子loadFactor和预估并发度concurrencyLevel三个参数计算table数组的初始大小sizeCtl的值。
    sizeCtl是一个控制标识符,不同的值代表不同的意思:其为0时,表示hash表还未初始化,而为正数时这个数值表示初始化或下一次扩容的大小,相当于一个阈值;即如果hash表的实际大小>=sizeCtl,则进行扩容,默认情况下其是当前ConcurrentHashMap容量的0.75倍;如果为-1,表示正在进行初始化操作;为-N时,则表示有N-1个线程正在进行扩容。
    在构造ConcurrentHashMap时,并不会对hash表(Node<K, V>[] table)进行初始化,hash表的初始化是在插入第一个元素时进行的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值