【集合】1、集合常见问题

101 阅读4分钟

说说 List,Set,Map 三者的区别?三者底层的数据结构?

  • List
    • 存储元素是有序的可重复的
    • ArrayList:Object数组
    • LinkedList:双向链表
    • Vector:Object数组(线程安全)
  • Set
    • 存储的元素不可重复,顺序不一定
    • HashSet(无序):基于HashMap实现
    • TreeSet(有序):基于红黑树实现
    • LinkedHashSet:(有序)HashSet子类,其内部是通过 LinkedHashMap 来实现的,双向链表记录了元素插入顺序
  • Map
    • 存储键值对映射,键是无序的不重复的
    • HashMap:数组+链表/红黑树
    • TreeMap:红黑树
    • LinkedHashMap:在HashMap的基础上增加了节点之间的双向链表,实现顺序访问
    • HashTable:数组+链表。(线程安全)

有哪些集合是线程安全的?

Map: ConcurrentHashMap, HashTable(粗锁),synchronizedMap(粗锁)

List: CopyOnWriteArrayList, Vector(粗锁), synchronizedList

ConcurrentLinkedQueue

集合顶层是什么 以及各个接口的实现类

顶层Collection接口

Set接口:Hashset、Treeset、Linkedhashset

List接口:Arraylist、Linkedlist、vector(线程安全)、stack(继承vector)

Queue接口:priorityQueue(堆实现的优先队列)、ArrayDeque(双端队列数组实现)、Linkedlist(链表实现)

Map接口:Hashmap、Treemap(对键进行排序,基于红黑树)、LinkedHashMap、Hashtable(线程安全)

多看看这张图

List

ArrayList 与 LinkedList 区别?

三个方面

数据结构: ArrayList 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构

插入/删除元素:LinkedList在首尾插入元素的时间复杂度为O(1) (在指定位置 i 插入和删除元素的话时间复杂度就为 O(n)) ,而ArrayList因为要移动前后元素所以平均修改复杂度为O(n)

查找元素:ArrayList可以以O(1) 的复杂度随机访问元素,LinkedList查找为O(n)

List集合的遍历相关问题?

编程坑太多,光一个List使用,就踩了5个

  • 使用foreach遍历时,list.remove方法会报错。因为看字节码 foreach编译后其实是使用iterator进行遍历,iterator遍历时要使用it.remove()方法进行移除。
  • 或者使用list.removeIf( item->item.xxx )方法进行集合中元素过滤移除。

List可以遍历删元素吗?(云智)

可以,但是

  1. 增强for循环遍历时删会报错(增强for循环字节码层还是迭代器遍历)
    1. 要使用迭代器遍历,通过iterator.remove删除
    2. 或者removeIf 删除

ArrayList 可以添加 null 值吗?

ArrayList 中可以存储任何类型的对象,包括 null 。不过,不建议向ArrayList 中添加 null 值, null 值无意义,会让代码难以维护比如忘记做判空处理就会导致空指针异常。

ArrayList如何删除某个元素?(云智)

  1. list.remove根据索引删
  2. list.remove根据对象删
  3. 迭代器遍历的时候通过it.remove删
  4. 用removeIf()删list.removeIf(element -> element.equals("Banana"));

ArrayList的扩容

四个点,初始容量0第一次add元素时分配为10,扩容因子1.5,扩容创建新数组并复制,每次添加前检查

ArrayList 源码分析

juejin.cn/post/728896…

  • 初始容量:只有在添加第一个元素的时候才会分配,默认为10
  • 增长因子:1.5.
  • 扩容触发条件:当ArrayList的size超过当前容量时,就会触发扩容操作。
  • 扩容策略:ArrayList在扩容时,会创建一个新的更大容量的数组,并将原有元素复制到新数组中

具体的扩容流程如下:

  1. 当向ArrayList添加add元素时,每次add会先检查当前容量是否足够。如果不足,则进行扩容操作。

  2. 扩容时,根据增长因子计算新的容量,并创建一个新的数组

  3. 将原有数组中的元素复制到新数组中

  4. 更新ArrayList内部的引用,指向新数组。

  5. 添加新元素到新数组中。

如何实现线程安全的List?

  1. Vector(粗锁)
  2. Collections.synchronizedList()
  3. CopyOnWriteArrayList

CopyOnWriteArrayList介绍

CopyOnWriteArrayList 源码分析

  1. 采用写时复制的思想,达到读写的分离。读的是旧数据,写的是数据的副本。
  2. 只有写写的时候才加锁, 读写不互斥。
  3. 缺点:不能满足强一致性。因为读取的时候读的是旧数据。(类似于MVCC也不是强一致性的)(性能和强一致通常是一种权衡