说说 List,Set,Map 三者的区别?
- List:存储的数据是有序的、可重复的。
- Set:存储的数据是独一无二的、无序的、不可重复的。
- Map:使用键值对(kye-value)存储,Key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
List
- Arraylist: Object[]数组
- Vector:Object[]数组
- LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
ArrayList
扩容
以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
int newCapacity = oldCapacity + (oldCapacity >> 1)
ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右,奇偶不同。
多线程访问出现的问题
- 抛出数组越界异常
- 多个线程可能对同一个位置赋值
解决方案:
- Vector(不推荐)
- synchronizedList()
- CopyOnWriteArrayList(适合读多写少的场景)
LinkedList
多线程环境下出现的问题:节点覆盖
初探LinkedList线程安全问题(一)
解决方案:synchronizedList() & ConcurrentLinkedQueue(无锁,适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景)
Arraylist 与 LinkedList 区别?
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环),ArrayList 插入和删除元素需要移动位置,LinkedList 不需要。LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
- 内存空间占用: ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
Set
- HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
- LinkedHashSet:LinkedHashSet 是 HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
- TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)
Map
- HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
- LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》
- Hashtable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- TreeMap: 红黑树(自平衡的排序二叉树)
HashMap
HashMap多线程情况下出现的问题
漫画:高并发下的HashMap
扩容时会导致环形链表
get() 一个不存在的key,恰好是hash到那个entry上面,就会死循环。