复习周开始了,关于集合的总结来了

1、集合

1、List、Set、HashMap

说一说它们三者的区别?

List存储的元素是有序的并且是可以重复的

Set存储的元素是无序的并且不可以重复,并且元素不能为空

HashMap存储元素是以Key-Value键值对的形式存储的,键和值都可以为空

它们下面的实现类都有什么?

先来说一下List,它下面的实现类包括:

  • ArrayList:它的底层数据结构是数组,并且是线程不安全的,它的扩容机制:扩容为原来的1.5倍,声明一个1.5倍空间的位置,将原数组的内容拷贝到这个更大空间里
  • LinkedList:它的底层数据结构是双向链表,并且是线程不安全的。
  • vector:它的底层数据结构是数组,但是它是线程安全的

再来说一下Set,它下面的实现类包括:

  • HashSet:它底层是继承HashMap,它的元素存储在底层这个HashMap的键中,也就是为什么它的值要求是非重复的,遇到重复值就是覆盖。
  • LinkedHashSet:LinkedHashSet是HashSet的子类,底层是通过LinkedHashMap实现的。
  • TreeSet:红黑树(是一种自平衡的排序二叉树)。

最后说一下Map,它下面的实现类包括:

  • HashMap:它其实就是一个散列表,在jdk1.8之前底层采用数组加链表的形式来保存元素,数组是HashMap的主题,链表更多的是解决Hash冲突而存在的(采用拉链法解决哈希冲突),在jdk1.8,底层是数组加链表加红黑树的形式来保存元素,引入红黑树的原因是防止链表过长而导致检索效率下降的问题。
  • TreeMap:红黑树,一种自平衡的排序二叉树
  • LinkedHashMap:LinkedHashMap继承自HashMap,所以它的底层依然是基于拉链式散列表的数组加链表结构,不同的是,它在以上的基础上加上了一个双向链表,这个双向链表主要维护元素的插入顺序。
  • HashTable:这个继承Dictionary字典类,它是数组加链表组成的,HashTable是线程不安全的

那你给我说下HashMap吧?

HashMap其实就是一个散列表,在jdk1.8之前,这个散列表是由数组+链表组成的,数组是HashMap的主题,链表主要解决哈希冲突的,在jdk1.8的时候,这个散列表新增了红黑树的结构,在链表长度大于8的时候就会转为红黑树来提升元素的查询效率。它有四个构造器,包括无参构造器、指定初始容量的构造器、指定初始容量和负载因子的构造器、包装Map的构造器,其实在指定初始容量的时候并不是指定多少容量就是多少,它会进入到一个tableForSize方法里经过处理来返回一个比当前指定容量大的离自己最近的一个2的幂次方数来作为整个HashMap的初始容量。再者比较终要的就是put方法和get方法还有一个resize方法,说这些方法之前,再提一个函数,也就是hash方法,在这里面主要做了与运算,HashMap的key的hashcode值的高16位与低16位做与运算得出的值,将这个值返回,这个就作为Node对象里的hash属性的值。再回来说get方法, 通过传入key计算它的hash值,然后根据Node数组的长度计算出索引,找到这个索引对应的数组头元素的key的hash以及对象是否与传入的key一致,如果一致,就将这个元素返回,不一致就判断这个 头元素是否有下一个节点,如果有节点就向下遍历,根据头节点实例的不同,采用不同的遍历方法,在遍历的过程中如果没有这个元素就返回空,如果有的话就返回这个节点值。再来说下put方法,它是插入元素的方法,它也是根据hash值和数组长度计算出索引下标,找到这个下标,如果没有值,就直接插入,如果有值的话,就判断这个头元素的key和待插入元素的key是否一致,如果一致就直接将这个元素覆盖,如果不同的话,就判断这个元素的实例,如果是树实例,就按照树实例插入方法插入元素,如果是链表,就遍历这个链表,途中如果判断有和这个元素一致的,就将值进行覆盖,否则就将待插入元素插入到链表尾部。再来说说扩容的这个resize方法吧,这个方法不仅用来扩容,还能初始化容量。在初始化容量的时候就给它的荣狼和负载因子附上默认值。在扩容的时候,分析它的源码,发现它并没有重新hash,而是用原来的hash值做判断,判断它的某一位是0还是1,如果是0就放到低位链表中,是1就放到高位链表中。遍历完成后,就将这个高位链表放到新数组的高位索引下,低位链表放到新数组的低位索引下。最后再说下HashMap的扩容在jdk1.7和1.8是不同的,1.7采用头插法,1.8采用尾插法,1.7的头插法在扩容时可能会导致链表死循环。1.8线程不安全的场景体现在插入元素时可能会出现元素丢失的情况。

有哪些集合是线程安全的?哪些不是安全的,怎么解决?

我们常用的ArrayList、LinkedList、HashSet、HashMap等等都不是线程安全的,可以用线程安全的集合来代替它们,也可以用Collections工具类下的方法将这些集合包装起来保证线程安全。但是这些都是synchronized修饰的,性能会变得低很多。

再者也可以用J.U.C包下的也有较多的容器,包括ConcurrentHashMap、CopyOnWriteArrayList等等。ConcurrentHashMap可以看作线程安全的HashMap,CopyOnWriteArrayList可以看作线程安全的ArrayList,它性能远高于vector,适合读多写少的场合。

前面你提到ConcurrentHashMap,你来讲讲它吧?

ConcurrentHashMap它底层是数组+链表+红黑树的一种结构,它是线程安全的,它的线程安全在jdk1.8之前通过分段锁来实现的。读不加锁,写加锁,它内部维护了一个segment数组,它锁住的是这个数组某一个索引下的数据。而不是锁住整个hash表,这一点就比HashTable性能好了很多,在jdk1.8时进行了更改,将segment去掉了,并做了优化,通过synchronized锁住头节点+CAS自旋使锁更细粒度化,又进一步提升了性能。

顺便也讲一下CopyOnWriteArrayList吧

它底层维护了一个ReentrantLock在插入元素时,通过显示的加锁解锁来保证线程安全的。

2、什么是快速失败、安全失败

快速失败(fast-fail):它是Java集合的一种错误检测机制。我们在是迭代器进行遍历集合的时候,在多线程下操作那些非安全失败的集合时可能会触发fast-fail,抛出**ConcurrentModificationException**异常。

安全失败(safe-fail):在上面的情景中不会抛出该异常。

猜你喜欢

转载自blog.csdn.net/MarkusZhang/article/details/107918808
今日推荐