引言
1、常见集合有哪些?
Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue,因此Java集合大致也可分成List、Set、Queue、Map四种接口体系。
- List代表了有序可重复集合,可直接根据元素的索引来访问;
- Set代表无序不可重复集合,只能根据元素本身来访问;
- Queue是队列集合。
- Map代表的是存储key-value对的集合,可根据元素的key来访问value。
2、线程安全的集合有哪些?线程不安全的呢?
线程安全的:
- Hashtable:比HashMap多了个线程安全。
- ConcurrentHashMap:是一种高效但是线程安全的集合。
- Vector:比Arraylist多了个同步化机制。
- Stack:栈,也是线程安全的,继承于Vector。
线性不安全的:
- HashMap
- Arraylist
- LinkedList
- HashSet
- TreeSet
- TreeMap
List
1、Set与List区别?
- List,Set都是继承自Collection接口
- List特点:添加的元素是有序,可重复,有索引 。Set特点:添加的元素是无序,不重复,无索引的,重复元素会覆盖掉。
- List支持for循环遍历,也可以用迭代器,但是set没有for循环遍历方法,因为set是无索引的,无法用下标来取得想要的值。
- Set和List效率对比:
- Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
- List:和数组类似,查找元素效率高,插入和删除元素效率低,因为会引起其他元素位置改变。
2、讲一讲ArrayList?
- ArrayList 是容量可变的非线程安全列表,使用数组实现,集合扩容时会创建更大的数组,把原有数组复制到新数组。支持对元素的快速随机访问,即可以通过元素的序号快速获取元素对象,但是插入与删除速度很慢。
- 构造函数: 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量,即向数组中添加第一个元素时,数组容量扩为 10。
- 扩容: 看下一个问题
- 删除元素: 需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,在旧数组上操作,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。
- 序列化:ArrayList的序列化不太一样,它使用transient修饰存储元素的elementData的数组,transient关键字的作用是让被修饰的成员属性不被序列化。
- Fail-Fast:快速失败,modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,改变了则抛出 ConcurrentModificationException(并发修改)异常。
3、ArrayList的扩容机制了解吗?
当容量不够时,需要使用grow()
方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1)
,oldCapacity >> 1
需要取整,所以新容量大约是旧容量的 1.5 倍左右,即 oldCapacity+oldCapacity/2
。
扩容操作需要调用 Arrays.copyOf()
(底层 System.arraycopy()
)需要把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
4、为什么ArrayList不直接序列化元素数组呢?
出于效率的考虑,数组可能长度100,但实际只用了50,剩下的50其实不用序列化,这样可以提高序列化和反序列化的效率,还可以节省内存空间。
5、ArrayList 与Vector区别?
- 实现:都实现了List接口,底层都使用
Object[]
数组存储。 - 线程安全: Vector使用了Synchronized 来实现线程同步,是线程安全的,而 ArrayList是非线程安全的。
- 扩容: Vector 每次扩容请求其大小的 2 倍(也可以通过构造函数设置增长的容量),而 ArrayList 是 1.5 倍。
- 性能: ArrayList在性能方面要优于Vector。
6、为什么要用Arraylist取代Vector?
Vector 是同步的,开销比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,是因为同步操作完全可以由程序来控制。
7、讲讲LinkedList?
- LinkedList 是一个实现了 List 接口的双端链表,支持高效的插入和删除操作,另外也实现了 Deque 接口,使得 LinkedList 类也具有队列的特性。
- LinkedList 包含三个重要的成员:size、first 和 last。size 是双向链表中节点的个数,first 和 last 分别指向首尾节点的引用。
- LinkedList 的优点在于可以将零散的内存单元通过附加引用的方式关联起来,形成按链路顺序查找的线性结构,内存利用率较高。
8、ArrayList和LinkedList的区别?
- 是否保证线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全
- 底层数据结构:
- Arraylist 底层使用的是
Object
数组,默认初始大小为10,插入元素超出则会动态扩容为原来的1.5倍。 - LinkedList 底层使用的是双向链表数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
- 插入和删除是否受元素位置的影响:
- ArrayList 采用数组存储,所以插入和删除元素受元素位置的影响
- LinkedList采用链表存储,所以插入和删除元素不受元素位置的影响
- 是否支持快速随机访问:
- LinkedList 不支持高效的随机元素访问,ArrayList 支持
- 快速随机访问就是通过元素的序号快速获取元素对象(对应于
get(int index)
方法)。
- 内存空间占用:
- ArrayList是预先定义好的数组,可能会有空的内存空间,存在一定空间浪费。
- LinkedList每个节点,需要存储前驱和后继,所以每个节点会占用更多的空间