java集合总结

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbTBfNTU3MzI4MDY=,size_20,color_FFFFFF,t_70,g_se,x_16

Collection

Collection集合的子接口有List,Queue,Set,它包含了这些集合的共性的方法,是单列集合父接口

List接口下的集合是有序的,允许重复,拥有索引;Set接口下的集合是无序的,不运行重复,只能有一个null值,没有索引

List接口下主要集合有ArrayList,Vector,LinkedList
Set接口下主要集合有HashSet,TreeSet,LinkedHashSet

ArrayList集合是List接口的实现类,它继承了AbstractList抽象类,实现了Iterator, RandomAccess,Cloneable,Serializable接口。它的底层是数组,数组长度可变,默认初始容量为10,扩容为原来的1.5倍,增加了0.5,是一个线程不安全的集合

ArrayList优缺点:
ArrayList底层实现了RandomArrayList接口,所以查询快
适合顺序添加元素,逆序删除元素。对于指定位置(不是末尾)添加元素性能低,因为底层添加时首先判断是否超过阈值(加载因子0.75),如果超过就扩容(相当消耗性能),然后添加时将指定位置以后的元素copy到新数组(全部向后移动一位,耗性能),删除元素时也是消耗性能。
因此ArrayList适合查询,适合顺序添加,不适合增删

ArrayList和LinkedList的区别:

  1. ArrayList基于数组实现,LinkedList基于链表实现
  2. ArrayList检索索引快,查询效率高;LinkedList要从头或从尾遍历,查询效率低
  3. ArrayList顺序添加快,但指定位置添加要移动元素位置,增删效率低;LinkedList添加元素不会改变元素位置,将指针指向即可,增删效率高
  4. ArrayList占用内存小;LinkedList占用内存大,不仅要存数据,还要存两个指针

    相同:都是非线程安全的

ArrayList和Vector的区别:
相同:都实现了List接口,都是有序的,允许重复,有索引
不同:ArrayList底层没有锁,非线程安全;Vector底层用了同步锁,线程安全
ArrayList 效率高,Vector效率低
扩容方面:ArrayList增加50%,Vector增加100%

数组与集合List可以互相转换
数组转集合List:Arrays.asList(array)
集合List转数组:list.toArray()

 

Map

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbTBfNTU3MzI4MDY=,size_20,color_FFFFFF,t_70,g_se,x_16
Map接口是双列集合父接口,key无序且唯一,value可以重复
Map接口下的集合主要有HashMap,LinkedHashMap,TreeMap,HashTable,concurrentHashMap

HashSet是基于HashMap实现的,HashSet的值为HashMap的key值,传入的value都是相同的虚值。在HashMap中,key值相同,后传入的value会将之前的value值覆盖,从而确保了key唯一(比较key是否相等先比较hashcode,再比较equals)

线程安全的集合类:Vector,statck,hashTable,enumeration

iterate
iterate只能单项遍历,更加安全
遍历集合时只能用iterate的remove方法,用集合的会抛concurrentModificationException异常
原因:iterate遍历集合时此时是iterate线程在执行,java不允许一个线程遍历Collection时另开辟一条线程修改它

iterate与listIterate之间的区别

  1. iterate可以遍历list和set接口下的集合;listiterate只能遍历list集合
  2. iterate只能单项遍历;listiterate可以双向遍历
  3. listiterate在iterate基础上增加了功能,如获取前一个元素或后一个元素索引位置,增加或替换元素
     

Queue

poll()和remove()区别

相同:poll()和remove()都会返回第一个元素,并将其删除

不同:如果没有该元素,poll()会返回null,remove()会抛出NoSushElementException异常

 

HashMap底层
对于hashcode与hash算法
hash=(h=key.hashcode)^(h>>>16)
将hashcode值与hashcode右移16位进行异或(总长度32位,右移16位正好取其半,这里非常巧妙,可以最大程度避免极端情况使其散列均匀)。这样做高16位与右移16位的全为0的高16位异或时,高16位保持不变,让低16位与右移16位后的原本高16位进行异或,目的是减少碰撞(即高16位不变,低16位与高16位进行异或结果保存在低16位中)

tab[(n-1)&hash]
将上面计算的hash值与数组长度-1进行与运算得到数组索引,相当于两者之间进行取余操作

为什么n要-1?
默认数组长度16,减一后为15二进制1111,这样与hash进行与操作时,后四位为hash本身,可以最大程度保证散列均匀(如果不减一,10000与操作,只有第五位可变,大多情况为0,并没有最大程度保证散列均匀,引起严重的hash冲突)

既然是取余,为什么要用&,而不用%
在为2的幂次方的数时,a%b=(b-1)&a恒成立,即&运算与取余操作相等(这也解释了数组长度为什么是2的倍数)。在计算机中,都是以二进制存储,&运算效率高

为什么数组长度为2的幂次方?

1.因为2的幂次方减一之后二进制数为多个1,这样与hash进行与运算时可保证最大散列均匀,例如:10000&10110=10000

            01111&10110=00110

很明显,如果不减一,无论hash值为多少,后面四位永远为0(与运算,两数都为1结果才为1,否则为0)这样大多数都是相同值(只有最前面一位是可变的,后面四位固定为0),hash冲突严重,不是我们想要的                                       而减1后,后面四位都是1,这样得到的结果取决于hash值,从而减少了碰撞

2.在为2的幂次方的数时,a%b=(b-1)&a恒成立,等价于取余操作

为什么要扰动2次?
加大hash值低位的随机性,使得分布更均匀,从而提高数组下标位置的随机性和均匀性,最终减少hash冲突

HashMap的put操作

  1. 判断键值对数组大小是否为空,是就扩容
  2. 根据key计算hash,对应数组索引是否没有值,没有就添加数据
  3. 如果有值,判断hashcode是否相等,key是否相等,如果相等,将旧的值进行替换
  4. 如果不相等,判断是否为红黑树类型,如果为红黑树类型,直接添加
  5. 不是红黑树类型,判断链表长度是否大于8,是转为红黑树类型,不是在链表添加数据,并在遍历过程中判断key是否相等,相等就替换
  6. 判断键值对数量是否大于容量,大于就要进行扩容
     

ConcurrentHashMap底层原理
分为1.7版本和1.8版本
1.7版本
用segment和HashEntry组成,每一个segment内部有一个HashEntry数组,每个HashEntry是一个链表结构的元素,segment要守护HashEntry数组,要对HashEntry进行修改,必须获得segment锁。
segment是分段后的小数组,因为segment继承了ReentrantLock,所以它是可重入锁。适用于多线程环境下,segment默认为16,可同时实现16个线程并发访问。
数组+链表结构                                                         

1.8版本
抛弃了segment分段锁,改为CAS+synchronized锁(更细粒度)
数组+链表/红黑树结构                                           

为什么将ReentrantLock锁替换为synchronized锁?

  1. 在JDK1.6版本中对synchronized做了优化,会从无锁—>偏向锁—>轻量级锁—>重量级锁一步步转换
  2. 节省内存空间。用重入锁获取同步支持,每一个节点都要继承AQS获取同步,而正真需要的应该是头节点/根节点,这样大大浪费空间

get时为什么不加锁?
在HashEntry中,有hash,key,value,next组成,其中value和next有volatile修饰,可以保证可见性。因此get时候是可见的,而且不加锁提高了效率

get不加锁与volatile的桶数有关吗?
没有,加锁是为了扩容时保证可见性

ConcurrentHashMap的key和value为什么不能为null?
ConcurrentHashMap适用于多线程环境下。首先来谈一下为什么value不能为null,主要是有二义性。比如我们有线程a,b。让线程a去获取key的value值,结果返回null,此时不知道是key不存在而为null,还是key的value值为null,当然我们可以用containsKey来判断key值是否存在,如果返回false,即不存在,那么这个null就是key不存在;如果返回true,表明key存在。但在a线程操作后,在用containsKey之前,如果b线程执行了put操作,设置了key,且值为null,那我们并不知道这个null是值为null还是key不存在。为了解决分歧,于是value不能为null。至于为什么key不能为null,主要是作者的意思。

ConcurrentHashMap的并发度是什么?
能同时执行且不产生锁竞争的最大线程数,在1.7中默认16,如果传入值,会变为2的幂次方
1.8中抛弃了segment,并发大小依赖于数组大小

ConcurrentHashMap的迭代器是强一致性还是弱一致性?
弱一致性。就是在遍历链表/红黑树时,被遍历过的地方如果被其它线程修改了,无法察觉到,还没有遍历到的地方被修改了,可以察觉
优点:这样迭代器线程可以使用原来老的数据,而写线程也可以并发的完成改变,更重要的,这保证了多个线程并发执行的连续性和扩展性,是性能提升的关键                                       

1.7版本与1.8版本之间的区别?

  1. 1.7中采用segment可重入锁,用数组+链表结构;1.8版本采用synchronized+CAS锁,用数组+链表/红黑树结构
  2. 锁的粒度:JDK1.7 是对需要进行数据操作的 Segment 加锁,JDK1.8 调整为对每个数组元素加锁(Node)。
  3. 查询时间复杂度:从 JDK1.7的遍历链表O(n), JDK1.8 变成遍历红黑树O(logN)。          

HashMap与ConcurrentHashMap区别

  1. HashMap非线程安全,ConcurrentHashMap线程安全
  2. HashMap允许null值null键,ConcurrentHashMap不允许null值null键              

HashMap与hashTable之间的区别

  1. HashMap非线程安全,没有锁,效率高,hashTable线程安全,有锁,效率低
  2. HashMap支持存null键null值,1个null键,多个null值;hashTable不支持,会抛异常
  3. 底层不同:HashMap默认容量16,扩容为原来2倍;hashTable默认长度11,扩容为原来的2n+1
  4. 如果给定数组大小,HashMap会变为2的幂次方,而hashTable给多少是多少
  5. HashMap在1.8版本超过8变红黑树,hashTable 没有                                                          

补充:

ConcurrentHashMap对整个桶进行了分割分段(segment),使用的是lock锁,比hashTable底层用的synchronized锁更加细粒度,并发性能更好                                                                       1.8版本后ConcurrentHashMap采用了CAS算法

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值