Java作业-2
描述Java 合集框架。列出Collection 接口下面的接口、便利抽象类以及具体类
使用什么方法来从迭代器得到合集中的一个元素?
ArrayList与LinkedList 之间的区别是什么?应该使用哪种线性表在一个线性表的起始位置插入和删除元素
······
描述Java 合集框架。列出Collection 接口下面的接口、便利抽象类以及具体类
Collection定义了各种数据结构和算法的标准API
| 接口 | 说明 | 举例 |
|---|---|---|
| List | 有序集合,可以包含重复元素 | ArrayList/LinkedList/Vector/Stack |
| Set | 无序集合,不包含重复元素 | HashSet/LinkedHashSet/TreeSet |
| Queue | 元素按特定方式进行排列的集合 | LinkedList/PriorityQueue |
| Deque | 双端队列接口 | ArrayDeque/LinkedList |
便利抽象类用于简化新合集类型的创建
| 便利抽象类 | 说明 |
|---|---|
| AbstractCollection | 实现Collection接口的基础抽象类 |
| AbstractList | 实现List接口的抽象类 |
| AbstractSet | 实现Set接口的抽象类 |
| AbstractQueue | 实现Queue接口的抽象类 |
| AbstractSequentialList | 用于继承链表结构的抽象类 |
| AbstractMap | 实现Map接口的抽象类(虽然Map不是Collection接口的一部分,但通常也被认为是合集框架的一部分) |
| 具体类 | 说明 |
|---|---|
| ArrayList | 动态数组实现的列表。 |
| LinkedList | 双向链表实现的列表和双端队列。 |
| HashSet | 基于哈希表的集合。 |
| LinkedHashSet | 基于哈希表和链表的集合。 |
| TreeSet | 基于红黑树的排序集合。 |
| PriorityQueue | 基于堆结构的优先队列。 |
| ArrayDeque | 动态数组实现的双端队列。 |
| Vector | 线程安全的动态数组。 |
| Stack | Vector的一个子类,实现了一个后进先出(LIFO)的栈。 |
使用什么方法来从迭代器得到合集中的一个元素?
// 获取迭代器对象:首先使用合集的 iterator() 方法获取一个迭代器对象。
Iterator<String> iterator = myCollection.iterator();
// 检查是否有下一个元素:使用 hasNext() 方法检查迭代器中是否还有更多的元素。
if (iterator.hasNext()) {
// 获取下一个元素:如果有下一个元素,使用 next() 方法获取它。
String element = iterator.next();
}
ArrayList与LinkedList 之间的区别是什么?应该使用哪种线性表在一个线性表的起始位置插入和删除元素
| 区别 | ArrayList | LinkedList |
|---|---|---|
| 底层数据结构 | 基于动态数组实现。 | 基于双向链表实现。 |
| 随机访问 | 提供快速的随机访问,时间复杂度是O(1) | 随机访问相对较慢,时间复杂度是 O(n) |
| 插入和删除 | 在列表的中间或起始位置插入和删除元素需要移动元素,时间复杂度是 O(n)。但在末尾的插入和删除是快速的,时间复杂度是 O(1) | 在列表的起始位置、中间或末尾插入和删除元素都很快,时间复杂度是 O(1) |
| 内存开销 | 由于是数组-based,内存利用率相对较高 | 需要额外的内存来存储链表的前后关系(节点的前驱和后继),所以内存开销更大 |
| 扩容 | 当数组达到容量上限时需要进行扩容,这是一个时间复杂度为 O(n) 的操作 | 由于是链表结构,不需要像数组那样进行扩容 |
在起始位置插入和删除元素时,LinkedList 通常是更好的选择,因为这些操作的时间复杂度是 O(1)。ArrayList 需要移动所有其他元素来填补空位或创建空位,所以这些操作的时间复杂度是 O(n)。
假设list1是一个包含字符串 red、yellow、green的线性表,list2是一个包含字符串red、yellow、blue的线性表
- 执行完list1.addAll(list2)方法之后,线性表 list1和list2分别变成了什么?
[red, yellow, green, red, yellow, blue]
[red, yellow, blue] - 执行完list1.add(list2)方法之后,线性表list1和list2分别变成了什么?
The method add(String) in the type ArrayList<String> is not applicable for the arguments (ArrayList<String>)Java(67108979)
这参数不对吧-_- - 执行完list1.removeAll(list2)方法之后,线性表list1和list2分别变成了什么?
[green]
[red, yellow, blue] - 执行完list1.remove(list2)方法之后,线性表list1和list2分别变成了什么?
Unlikely argument type ArrayList<String> for remove(Object) on a Collection<String>Java(1200)
[red, yellow, green]
[red, yellow, blue] - 执行完list1.retainAll(list2)方法之后,线性表list1和list2分别变成了什么?
[red, yellow]
[red, yellow, blue] - 执行完list1.clear()方法之后,线性表list1变成了什么?
[]
什么时候一个方法应该抛出 UnsupportedOperationException 异常?
用于标明“这个操作在这个上下文中没有意义或不被支持”
常见情况:
- 不可修改的集合:试图修改一个不可修改(unmodifiable)或只读的集合时,通常会抛出这种异常。例如,Collections.unmodifiableList() 返回一个不可修改的列表,对这个列表调用任何修改操作(如 add, remove 等)都会抛出 UnsupportedOperationException。
- 部分实现接口:有些类只部分实现某个接口,并不提供接口中所有方法的完整实现。在这种情况下,不支持的方法通常会抛出 UnsupportedOperationException。
- 不支持的操作:某些数据结构有固有的限制,不能执行某些操作。例如,在一个队列(Queue)实现中,如果某个特定操作(比如在固定大小队列中添加元素)是不被支持的,那么尝试执行该操作将抛出此异常。
- 抽象类和接口:当一个抽象类或接口定义了一个方法,但没有提供默认实现,并且该方法对于该类或接口没有意义时,通常会在该方法体中抛出 UnsupportedOperationException。
- 为了明确性:在某些情况下,即使技术上可以实现某个操作,但为了避免误导,也会选择抛出这个异常。例如,在一个不可变对象的 setter 方法中。
(按字母序的升序显示单词)编写一个程序,从文本文件读取单词,并按字母的升序显示所有的单词(可以重复)。单词必须以字母开始。文本文件作为命令行参数传递
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Scanner;
public class ReadSort {
public static void main(String[] args) throws FileNotFoundException {
if (args.length != 1) {
System.out.println("请输入要读取的文件路径");
} else {
String fileName = args[0];
System.out.println("读取的文件为:" + fileName);
ArrayList<String> words = new ArrayList<>();
try (Scanner sc = new Scanner(
new InputStreamReader(new FileInputStream(fileName), StandardCharsets.UTF_8))) {
// while (sc.hasNextLine()) { // 按行读取字符串
// String line = sc.nextLine();
// System.out.println(line);
// }
sc.useDelimiter("\\s+"); // 使用空白作为默认分隔符
while (sc.hasNext()) {
String word = sc.next();
if (word.matches("^[A-z]+.*")) {
words.add(word);
}
}
}
System.out.println(words);
words.sort(null);
System.out.println(words);
}
}
}
(在链表上使用遍历器)编写一个测试程序,在一个链表上存储 500 万个整数,测试分别使用iterator 和使用 get(index)方法的遍历时间
使用get(index)遍历链表(特别是LinkedList)通常比使用迭代器要慢得多,因为每次调用get(index)都需要从链表头部或尾部遍历到指定的索引位置。这会使得遍历整个链表的时间复杂度变为O(n^2)。与此同时,使用迭代器只需要O(n)的时间。
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class LinkedListTest {
public static void main(String[] args) {
// 创建一个包含500万个整数的链表
List<Integer> list = new LinkedList<>();
for (int i = 0; i < 5_000_000; i++) {
list.add(i);
}
// 使用迭代器遍历链表,并计算所需时间
long startTime = System.currentTimeMillis();
ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
iterator.next();
}
long endTime = System.currentTimeMillis();
System.out.println("使用迭代器遍历链表所需时间: " + (endTime - startTime) + "ms");
// 使用 get(index) 遍历链表,并计算所需时间
startTime = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
endTime = System.currentTimeMillis();
System.out.println("使用 get(index) 遍历链表所需时间: " + (endTime - startTime) + "ms");
}
}
测试结果(ms)
| 数量 | iterator | get(index) |
|---|---|---|
| 5000 | ~0 | 15 |
| 50000 | 2 | 799 |
| 100000 | 2 | 3067 |