集合容器概述
集合框架的组成:
- 集合框架:用于存储数据的容器。
- 接口:表示集合的抽象数据类型,规范集合框架应该实现的功能。
- 实现:集合接口的具体实现,是重用性很高的数据结构。
- 算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。事实上,算法是可复用的函数。
集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而不是为了让程序能正常运转而将注意力于低层设计上。 通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以便联合这些API而去写大量的代码。 它提高了程序速度和质量。
集合和数组的区别
- 数组是固定长度的,集合是可变长度的。
- 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
- 数组可以存储基本数据类型和引用数据类型;集合只能存储引用数据类型。
使用集合框架的好处
- 容量自增长。(例如ArrayList自动扩容)
- 提供了高性能的数据结构和算法,是编码更加轻松,提高了程序速度和质量。(支持各种排序算法)
- 允许不同API之间的操作,API之间可以来回传递集合。(例如ArrayList的addAll方法的参数就可以是任何实现了Collection接口的集合)
- 可以很方便的扩展或改写集合,提高代码复用性和可操作性。(例如LinkedList就可以很方便地实现LRU算法)。
Collection接口
迭代器Iterator是什么
迭代器Iterator是一个接口,提供遍历任何集合的功能。
迭代器Iterator怎么使用,有什么特点
使用代码:
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
Iterator只能单向遍历,但是更加安全,因为它可以确保在当前遍历的集合元素被修改的时候,就会抛出ConcurrentModificationException 异常。
如何边遍历边移除 Collection 中的元素?
正确方式:
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
*// do something*
it.remove();
}
错误方式:
for(Integer i : list){
list.remove(i)
}
上面的错误方式会抛出ConcurrentModificationException 异常。
Iterator 和 ListIterator 有什么区别?
- Iterator可以遍历所有集合,ListIterator只能遍历List。
- Iterator只能单向遍历,ListIterator可以双向遍历。
- ListIterator 实现了 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?
- for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
- 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
- foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。
说一下 ArrayList 的优缺点
优点:
- ArrayList底层以数组实现,支持随机访问。查找非常快。
- ArrayList在顺序添加一个元素时非常方便。
缺点:
- 插入删除元素的时候,需要做一次元素复制操作,性能消耗大。
ArrayList适合顺序添加,随机访问的场景。
ArrayList 和 LinkedList 的区别是什么?
- 数据结构:ArrayList是动态扩容的数组,LinkedList是双向链表。
- 随机访问效率:ArrayList的效率高。
- 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
- 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
- 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全。
多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
为什么 ArrayList 的 elementData 加上 transient 修饰?
ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
*// Write out element count, and any hidden stuff*
int expectedModCount = modCount;
s.defaultWriteObject();
*// Write out array length*
s.writeInt(elementData.length);
*// Write out all elements in the proper order.*
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。