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