目录
Iterator 和 ListIterator 有什么区别?
阐述ArrayList、Vector、LinkedList 的存储性能和特性?
集合容器概述
什么是集合?
集合框架
:用于存储数据的容器。
集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。
任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
接口
:表示集合的抽象数据类型。接口允许我们操作集合时不必关注具体实现,从而达
到“多态”。在面向对象编程语言中,接口通常用来形成规范。
实现
:集合接口的具体实现,是复用性很高的数据结构。
算法
:在一个实现了某个集合框架中接口的对象身上完成某种有一定功能的计算的方法。
例如查找、排序等。这些算法通常是多态的,因为相同的方法在同一个接口被多个类实现
时有不同的表现。事实上,算法是可复用的函数。
集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而
不是为了让程序能正常运转而将注意力于低层设计上。它使我们可以毫不费力地将各类API整合在一起,大大提高了编写程序的效率,也保证了代码的质量。
集合的特点?
主要有以下两点:
对象封装数据,对象多了也需要存储。集合用于存储对象。
对象的个数确定可以使用数组,对象的个数不确定的可以用集合(因为集合的长度可变)。
集合和数组的区别 ?
属性 | 数组 | 集合 |
长度 | 固定 | 可变 |
存储数据类型类型 | 基本,引用 | 引用 |
存储元素 | 同一数据类型 | 不同数据类型 |
加餐小tips:
数据结构:就是容器中存储数据的方式。
集合容器的数据结构有很多种。在不断向上抽取过程中,出现了集合体系。
使用一个集合体系的原则:参阅顶层内容,建立底层对象。
如何理解此原则呢?
在Java中,集合体系是根据一组抽象的顶层接口来设计的,而这些顶层接口定义了所有集合类应遵循的规范和应提供的方法。
当我们说"参阅顶层内容"时,实际上是指要参考这些顶层接口,了解它们定义的方法和规范。这是理解和使用集合框架的基础。
而"建立底层对象"则是指在理解了顶层接口之后,根据实际需求选择合适的底层实现类来创建集合对象。
在Java中,集合体系是根据一组抽象的顶层接口来设计的,而这些顶层接口定义了所有集合类应遵循的规范和应提供的方法。
当我们说"参阅顶层内容"时,实际上是指要参考这些顶层接口,了解它们定义的方法和规范。这是理解和使用集合框架的基础。
而"建立底层对象"则是指在理解了顶层接口之后,根据实际需求选择合适的底层实现类来创建集合对象。
使用集合框架的好处?
1. 容量自增长;
2. 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
3. 允许不同 API 之间的互操作,API之间可以来回传递集合;
4. 可以方便地扩展或改写集合,提高代码复用性和可操作性;
5. 通过使用JDK自带的集合类,降低代码维护和学习新API成本。
常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口:
1. Collection接口的子接口包括:Set接口和List接口
2. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
3. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
4. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、
ConcurrentHashMap以及Properties等
List,Set,Map三者的区别?
先看一张图:
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue
三种子接口。注意:我们常用的Set、List,Map接口不是collection的子接口。
List | Set | Map | |
有序性 | 有序 | 无序 | 无序 |
重复性 | 可重复 | 不可重复 | key不可重复,value可重复 |
存储方式 | 以列表形式存储元素 | 以集合形式存储元素 | 以键值对形式存储元素 |
主要操作 | 插入、删除、查找元素 | 添加、删除元素 | 添加、删除键值对 |
常用实现类 | ArrayList(Object数组)、LinkedList(双向循环链表)、Vector(Object数组) | HashSet
(无序,唯一)
、LinkedHashSet、TreeSet
(有序,唯一,红黑树)
|
HashMap、TreeMap、
HashTable、LinkedHashMap、
ConcurrentHashMap
|
应用场景 | 需要有序存储元素,经常进行元素查找操作 | 需要去重,不需要有序存储元素 | 需要存储键值对,对键进行快速查找 |
空键/空值 | 允许空键和空值 | 不允许空键,允许空值 | 允许空键和空值 |
迭代器 | ListIterator | Iterator | KeySet、Values、EntrySet |
排序 | 可以对元素进行排序 | 可以对元素进行排序 | 可以对键进行排序 |
加餐小tips:
集合框架底层数据结构:
Collection
List
Arraylist:
Object数组
Vector:
Object数组
LinkedList:
双向循环链表
Set
HashSet(无序,唯一):
基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet:
LinkedHashSet 继承与 HashSet,且其内部是通过LinkedHashMap
来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
TreeSet(有序,唯一):
红黑树(自平衡的排序二叉树。)
Map
HashMap:
JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表 则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8后在 解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将 链表转化为红黑树,以减少搜索时间。
LinkedHashMap:
LinkedHashMap 继承自 HashMap,所以它的底层仍然基于拉链式
散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在 上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持 键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺 序相关逻辑。
HashTable:
数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈 希冲突而存在的。
TreeMap:
红黑树(自平衡的排序二叉树)。
哪些集合类是线程安全的?
statck:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议
使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
Java集合的快速失败机制 “fail-fast”是什么?
“fail-fast”是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生的机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator遍历集合A中的元素, 在某个时间点线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount变量。如果集合在被遍历期间如果内容发生变化,则modCount的值会发生改变。而在迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历结果;否则抛出异常,终止遍历。
解决办法:
由于它的异常抛出是在检测到并发修改时立即发生的,可能会打断程序的正常流程。因此,如果程序需要在不中断执行的情况下处理这种异常,或者需要更加精细地控制并发访问,那么就需要规避这种机制。
1. 在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
2. 使用CopyOnWriteArrayList来替换ArrayList。
怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
示例代码如下:
List list = new ArrayList<>();
list.add("x");
Collection clist = Collections.unmodifiableCollection(list);
clist.add("y"); // 运行时此行报错
System.out.println(list.size());
Collection接口
List接口
迭代器 Iterator 是什么?
Iterator 接口提供了遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
Iterator 怎么使用?有什么特点?
示例代码如下:
List list = new ArrayList<>();
Iterator it = list.iterator();
while (it.hasNext()) {
String obj = it.next();
System.out.println(obj);
}
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保在当前遍历的集合元
素被更改的时候,就会抛出 ConcurrentModificationException 异常。
如何边遍历边移除 Collection 中的元素?
边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,示例代码如下:
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
// do something
it.remove();
}
一种最常见的错误代码如下:
for(Integer i : list){
list.remove(i)
}
运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时,另一个线程修改它。
Iterator 和 ListIterator 有什么区别?
1、Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
2、Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
3、ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一
个元素、获取前面或后面元素的索引位置。
遍历一个 List 有哪些不同的方式?
1. for 循环遍历:基于计数器。在集合外部维护一个计数器,然后依次读取每一个位
置的元素,当读取到最后一个元素后停止。
2. 迭代器遍历(Iterator):Iterator 是面向对象的一个设计模式,目的是屏蔽不同数
据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模
式。
3. foreach 循环遍历:foreach 内部也是采用了 Iterator 的方式实现,使用时不需要
显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍
历,不能在遍历过程中操作数据集合,例如删除、替换。
Java 中 List 遍历的最佳实践是什么?
最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实
现是否支持 Random Access。
如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平
均时间复杂度为 O(1),如ArrayList。
如果没有实现该接口,表示不支持 Random Access,如LinkedList。
推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,不支持则建议用 Iterator
或 foreach 遍历。
说一下 ArrayList 的优缺点?
优点如下:
ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。ArrayList 在顺序添加一个元素的时候非常方便。
缺点如下:
插入和删除元素的时候,都需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
ArrayList 比较适合顺序添加、随机访问的场景。
如何实现数组和 List 之间的转换?
数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。
示例代码如下:
// 将列表转换为ArrayList
List<String> list = new ArrayList<>();
list.add("123");
list.add("456");
String[] array = list.toArray(new String[0]); // 将List.toArray()的结果转换为String数组
// 将数组转换为列表
String[] array2 = new String[]{"123","456"};
List<String> listFromArray = Arrays.asList(array2);
ArrayList 和 LinkedList 的区别是什么?
对比项 | ArrayList | LinkedList |
数据结构实现 | 动态数组 | 双向链表 |
非首尾的增加和删除的效率 | 低 | 高 |
随机访问效率 | 高 | 低 |
内存空间占用 | 少 | 多 |
线程安全 | 不保证 | 不保证 |
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
加餐tips:
数据结构基础之双向链表:
双向链表也叫双链表,是链表的一种。它的每个数据结点中都有两个指针,分别指向直接
后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前
驱结点和后继结点。
ArrayList 和 Vector 的区别是什么?
特性 | Vector | ArrayList |
接口实现 | List(继承自Collection) | List(继承自Collection) |
有序集合 | 是 | 是 |
线程安全 | 是(使用Synchronized) | 否 |
性能 | 较低 | 较高 |
扩容 | 每次增加1倍 | 每次增加50% |
同步方法 | 所有方法同步 | 非同步 |
线程访问建议 | 两个线程安全访问 | 单线程访问效率更高 |
使用场景 | 需要线程安全 | 不需要线程安全,追求性能 |
阐述ArrayList、Vector、LinkedList 的存储性能和特性?
ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector 中的方法由于加了 synchronized 修饰,因此 Vector是线程安全容器,但性能上较ArrayList差。
LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 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));
}