java集合

1. hashmap

1.1 jdk1.7 hashmap

        1.7中数据结构位数组+链表,使用头插法

     put:

        1)将key的哈希值通过类似取余操作计算得到数组下标

        2)遍历链表,如果没有找到相同的key,则new一个entry,将其next指向table[i],把entry放置到table[i](即头插法);如果找到key则修改其value

        3)如果key为null,则entry放置到table[0]

        hashmap为何数组大小需要是2的幂次

        在计算数组索引的时候,key.hashcode()&数组长度-1可以起到取余效果,将结果低位保留,进而将结果限制在2^n内,同时取余操作比&慢

        同时在计算索引的时候,不仅仅&,还进行了位移,是因为将高位也参入计算,使得结果更加散列

        扩容:

        扩容扩容两倍,新生成一个新数组,遍历老table和链表,重新计算下标值,方法为hash&(新数组长度-1),然后使用头插法转移到新数组

1.2  jdk1.7 ConcurrentHashmap

        使用分段锁的机制,操作segement的时候加速,segement维护部分entry,因此相当于对部分entry加锁

        初始化:

        计算参数值,包括segement数组长度、每个segement内部长度,初始化segement数组,并且new一个segement放置到segement[0],以这个对象为蓝本进行后续操作

        

        put:

        先计算segement下标,如果segement[i]未生成,则new一个segement,cas放置到segement中

        对segement加锁,segement继承reentrantlock,首先使用trylock非阻塞加锁,在获取锁的循环中遍历链表,找到对应的key,如果trylock过多,则lock阻塞加锁

        加锁后,获得对应的entry,修改其value,如果未找到则头插法

        扩容:

        扩容是在put方法中进行,此时已经加锁,不会线程不安全,因此直接对segement扩容,方法同1.7hashmap

1.3  jdk1.8 hashmap

        1.8中hashmap使用数组+链表+红黑树

        为何使用红黑树?

         避免出现长链表,保证查询速度在lgn

        红黑树是查找树的一种,为何不直接使用二叉查找树(搜索树、平衡树),因为查找树容易失衡,退变成长链表;为何不使用平衡二叉树,平衡二叉树追求绝对平衡,使得增删时调整树的次数多。红黑树允许局部不平衡(即保证黑节点平衡即可),减少调整次数

        红黑树定义:

        1)一个节点要么红色要么黑色

        2)根节点和叶子节点一定是黑色

        3)某个节点到其所有子孙节点的路径上黑节点个数一致

        

        put:

        首先计算数组下标i

        如果table[i]为null,则初始化node并放置到table[i]

        如果不为空,则判断table[i]是否为链表节点,如果是则遍历链表,找到key或者尾插法

        如果table[i]为树节点,则遍历树

        遍历或者新增后,判断链表长度,如果长度大于8则转化成红黑树,转化过程为:将头节点置为树根节点,然后遍历链表,不断插入,维持树平衡

        

        扩容:

        如果table[i]为链表,则将哈希值&旧数组长度,得到0或者1,根据0、1拆分出两个链表,将两个链表头节点移到新数组高低位(1.7为一个个转移)

        如果为树节点,则遍历树,也是拆分为两个链表,放到新数组后判断是否需要树化

1.4 jdk1.8 concurrenthashmap

        采用对数组头节点加锁的实现方案,并且使用sychornized

        初始化:

        使用cas将标志位sizeCtl-1,如果成功-1,则初始化数组,其他线程判断sizeCtl<0,则thead.yield等待

        put:

        首先计算下标,找到table[i],如果为空,则cas个新节点

        取table[i]的头节点,并sychornized加锁

        如果table[i]为链表,则遍历并尾插

        如果为树,则遍历并插入

        扩容:

        首先得判断元素个数是否超过阈值

        元素数量计算方法:每次put都cas将countCells中某个元素加一,最后将整个countCellls加起即可得到元素数量

          扩容采用的多个线程帮助扩容机制

        1. 首先计算步长,根据步长计算每个线程需要扩容的区域

        2. 扩容时对头节点加锁,因此无法put

        3. 扩容具体操作同1.8 hashmap

        

        问题:

1. Hashmap为何线程不安全

        Jdk1.7为头插法(头插速度快,不用遍历找到尾节点),在扩容resize的时候,多个线程同时resize,会造成循环链表

        Jdk1.8put时会出现问题,即一个线程已经计算位置准备put时,被其他线程抢占位置,随后该线程运行,将前线程数据覆盖

2. 为何hashmap1.7用头插、1.8用尾插

        在resize的时候,头插速度更快、不用遍历链表,找到尾节点;但是头插法在多线程条件下会造成循环链表,所以1.8改为尾插,同时1.8使用红黑树,需要遍历链表判断是否达到转换位红黑树的阈值,既然已经遍历了,也就可以找到尾节点了

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值