Java集合——List与Map详解

集合

一、迭代器、forEach、for

二、ArrayList和LinkedList底层原理

1.List集合是线程不安全的

经常出现java.util.ConcurrentModificationException(并发修改异常)
1.使用Vector类 效率低
2.使用Collections集合工具类,对集合进行同步处理:
List<String> list = Collections.synchronizedList(new ArrayList<>()); 
但是在多线程开发中,对其进行遍历,需要添加 synchronized 关键字,因为List的 add、index 等方法中都是带有synchronized 关键字,
但是在 iterator 中没有synchronized 关键字。
3.使用CopyOnWriteArrayList(写时复制,读写分离的思想) CopyOnWrite容器即写时复制的容器,往一个容器添加元素的时候,不直接往当前容器添加,
而是先将当前容器进行复制,然后往新的容器里添加元素,添加完元素之后,再将原容器引用指向新容器;这样做的好处是对容器进行并发的读,而不需要加锁,
因为当前容器不会添加任何元素,在add一些方法内部设置有Reentrantlock锁。

2.CopyOnWriteArrayList

CopyOnWriteArrayList如何保证的线程安全
请添加图片描述

三、Set集合及Tree结构

四、HashMap底层原理

1.优化

1.8之后hashmap对hash算法和寻址算法的优化
区别:jdk1.8中hash算法是让key的hash值的高16位和低16位进行异或运算(如果a、b两个值不相同,则异或结果为1。
如果a、b两个值相同,异或结果为0),寻址算法是取代之前的对hash进行取模运算,而是用hash&(数组长度-1),效果和
取模是一样的,但是要求数组的长度必须是2的n次方,这也是为什么hashmap一定是2倍扩容。
(1)改进优点在于,让高低16位都参与到寻址计算,可以减少之前低16位决定寻址结果的概率,因为数组长度一般都比较小,
数组长度-1的高16位大概率都是0,那么与出来的结果高16位也都是0,这样计算结果大多数都是靠着低16,寻址有较高的hash碰撞;
(2)与运算的性能要远高于取模运算。
查询链表复杂度O(n),红黑树查询复杂度O(logn)

hashmap扩容机制
当16位扩容到32位时候
对原来的key的hash值进行rehash,因为在二进制上,3216只多出来一个1,rehash的结果如果比原来的值多1,那么直接在
原来的位置加上一个数组扩容的长度,如果位数没有变,则这个key的位置也不变。

HsahMap扩容机制
请添加图片描述

2.Hash冲突及解决方法

  • 重哈希法,开放地址法,建立公共溢出,链地址法。

3.HashMap双链死循环

双链循环是JDK1.7及更早的版本之前才有的问题。在多线程扩容的情况下,一个线程执行到一半,还未扩容,而另一个线程却抢走先行扩容了,
这时候可能出现第一个线程的元素与第二个线程中的元素相互引用的情况,相互引用就会造成死锁。

比如一个数线长度为4,有两个数,一个为2,一个为10,那么这两个数都会在索引2上形成哈希桶结构,此时进行扩容,本来在新数组中是2指向10的,
结果但之前那个前程正好断在10指向新数组的中间,这就会导至10又重新指向2,最终导while判断中的e永远不会等于null,造成死循环。

JDK1.8版本避免了双链循环,但不是完全避免,看过一些测试文章,红黑树之间也可能出现死循环,只是比较1.7版本,几率降低

4.HashMap为什么数组长度始终是2的n次方

如果不是2的次幂的数的话 假设数组长度是一个奇数,那参与最后的&运算的肯定就是偶数,那偶数的话, 它二进制的最后一个低位肯定是00做完&运算得到的肯定也是0,那意味着&完后得到的数的最低位一定是0 最低位一定是0的话,那说明一定是一个偶数,换句话说就是:
&完得到的数一定是一个偶数,所以&完获取到的脚标永远是偶数位,那意味着奇数位的脚标永远都没值,有一半的空间是浪费的 奇数说完了,
来说一下偶数,假设数组长度是一个偶数,比如6,那参与&运算的就是5 5的二进制 00000000 00000000 00000000 00000101 发现
任何一个数&5,倒数第二低位永远是0, 那就意味着&完以后,最起码肯定得不出2或者3(这点刚开始不好理解,但是好好想一下就能明白)
 意味着第二和第三脚标位肯定不会有值 所以不是2的次幂的话,不管是奇数还是偶数,就肯定注定了某些脚标位没值,注定了某些脚标位永远是没有值的。

5.put的流程

首先对hash值的高16位和低16位进行异或运算,然后对数组长度-1进行与运算,计算出插入数组的位置,如果数组中没有元素,则直接插入,
如果存在则比较key值,如果相同则覆盖,如果不同则插入尾部,如果链表达到8则转为红黑树,最后判断是否达到扩容指标,如果达到则扩容。

6.Hashtable与HashMap有什么区别

7.ConcurrentHashMap,JDK7版本跟JDK8版本有什么不同?

ConcurrentHashMap1.7版本:
创建对象
1、默认创建一个长度16,加载因子为0.75的大数组,但这个大数组一但创建无法扩容,所以加载因子是给小数组用的。
2、还会创建一个长度为2的小数组,把地址值赋值给0索引处。其他索引位置的元素均为null。
 插入元素
第一次插入新元素时,会根据键的哈希值来计算出在大数组中应存入的位置。
•	如果为null,则按照模板创建小数组,大数组只用来存放地址值。
    o	创建完毕,会进行二次哈希,计算出在小数组中应存入的位置。
	o	直接存入。
•	如果不为null,就会根据记录的地址值找到小数组。
    o	二次哈希,计算出在小数组中应存入的位置。
    o	如果需要扩容,则先将小数组扩容2倍。
    o	如果不需要扩容,则判断小数组的这个位置有没有元素。
	如果没有元素,则直接存。
	如果有元素,就会调用equals方法,比较属性值
•	如果equals为true,相同则不存;
•	如果equals为false,新元素替换老元素,老元素挂在新元素下面,形成哈希桶结构(链表)。
/*线程安全
1.7采用Segment+HashEntry分段锁的机制,也就是一个Segmen的数组,Segment继承Reentarntlock,
Segment下面包含小数组HashEntry,HashEntry本身是一个链表的结构,实际上每一个segment都是一个
hashmap,默认长度为16,最多同时访问16个线程。*/

ConcurrentHashMap1.8版本:
区别1.7:
•	底层结构改变:
哈希表(数组会扩容)——【数组】+【链表】+【红黑树】
1.7是旧元素挂新元素下面(旧挂新——头插法);1.8是新元素挂在旧元素下面(新挂旧——尾插法)!
/*•	线程安全机制改变:结合CAS机制+synchronized同步代码块形式保证线程安全。进行put时候,当一个
数组的某一个位置是null,那么多个线程读到这个null,会执行CAS,同一时间,只有一个线程能执行成功;这时
别的线程就会基于链表或者红黑树+synchronized,是头节点进行加锁,把数据插入进去*/

    
如果该索引为null,则利用cas算法,将本结点添加到数组中。(第一次添加用CAS算法)
如果该索引不为null,则利用volatile关键字获得当前位置最新的结点地址,新元素挂在旧元素下面。(因为1.8后有了红黑树,
会自动进行排序调整,再调整链表头就浪费资源了,而旧版本需要后进先出)
(红黑树转化条件见HashMap底层)

有元素后,再对该元素进行操作时,会给头结点(第一个元素)做为锁对象,加上synchronized同步代码块(锁对象的方式),
保证线程安全。

•	加载机制改变:懒加载——第一次添加元素时初始化数组。(添加元素时,判断数组是否为空,或者长度为0,如果是,
就初始化数组)

/*CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。
整个比较并替换的操作是一个原子操作。
CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它
线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。   
概述
CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使
用了CAS技术
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,
否则什么都不做。通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用
 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功*/


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值