前端视角 Java Web 入门手册 2.4.3:集合框架——List

73 阅读3分钟

List 接口 是 Java 集合框架中用于存储有序集合的接口,它继承自 Collection 接口。List 能够存储重复的元素,并且每个元素都有对应的整数索引,索引用于标识元素在集合中的位置。常见的实现类有 ArrayList、LinkedList、Vector、Stack,不过 Stack 已经不推荐使用了,应该用 Deque 代替。

ArrayList

ArrayList 和 TypeScript 中的数组比较接近,长度可以变化,支持使用 index 随机访问;容量不够时会自动扩容,默认初始容量为 10,扩容时按 1.5 倍增长。在数据量大、长度频繁变化场景效率较低

List<String> list = new ArrayList<String>();
list.add("一");
list.add("二");
list.add("三");

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

list.set(0, "1");
list.remove(1);

for (String s : list) {
    System.out.println(s);
}

如果 ArrayList 中的元素是经过排序的,就可以使用二分查找法,效率更快

List<String> list = new ArrayList<String>();
list.add("a");
list.add("c");
list.add("b");
list.add("d");

Collections.sort(list);
System.out.println(list); // [a, b, c, d]

int index = Collections.binarySearch(list, "b");
System.out.println(index); // 1

LinkedList

LinkedList 是 List 和 Deque 接口的双向链表实现,提供了高效的插入和删除操作。相比于 ArrayList,LinkedList 在列表中间位置插入或删除元素时更高效,但随机访问速度较慢

  • 只支持顺序访问,任意位置插入、删除效率较高
  • 需要存储前后节点的引用,占用空间比 ArrayList 多
List<String> list = new LinkedList<String>();
list.add("一");
list.add("二");
list.add("三");

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

list.set(0, "1");
list.remove(1);

for (String s : list) {
    System.out.println(s);
}

LinkedList 还有几个常用方法,在队列、栈等数据结构中非常有用

  • getFirst():获取第一个元素
  • getLast():获取最后一个元素
  • removeFirst():删除第一个元素
  • removeLast():删除最后一个元素
  • poll()/pollFirst():删除并返回第一个元素
  • pollLast():删除并返回最后一个元素
  • peekFirst():返回但不删除第一个元素
  • peekLast():返回但不删除最后一个元素
  • push():在头部插入元素,相当于 addFirst()
  • pop():删除头部元素,等价于 removeFirst()

ArrayList 与 LinkedList 的比较

ArrayList 和 LinkedList 是 List 接口最常用的两种实现类,它们在内部数据结构、性能特性和适用场景上各有不同。

  • ArrayList:基于动态数组实现,底层使用数组存储元素
  • LinkedList:基于双向链表实现,每个元素都是一个节点,包含前后节点的引用
操作ArrayListLinkedList
随机访问O(1)O(n)
插入/删除O(n)O(1)(在已知位置)
添加到末尾O(1)O(1)

Vector 与 Stack

Vector 和 Stack 虽然作为早期的集合类具有一定的历史意义,但由于其设计上的局限性和性能开销,在现代 Java 编程中已经逐渐被更高效、更灵活的集合类所取代。开发者应优先选择 ArrayList 替代 Vector,以及 Deque 的实现类(如 ArrayDeque)替代 Stack,以编写出更简洁、高效且可维护的代码

使用 ArrayList 替代 Vector

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ArrayListInsteadOfVector {
    public static void main(String[] args) {
        // 创建一个非线程安全的 ArrayList
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Python");
        list.add("C++");
        
        System.out.println("ArrayList: " + list);
        // 输出: ArrayList: [Java, Python, C++]
        
        // 如果需要线程安全,可以使用 synchronizedList
        List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
        synchronizedList.add("Java");
        synchronizedList.add("Python");
        synchronizedList.add("C++");
        
        System.out.println("同步后的 ArrayList: " + synchronizedList);
        // 输出: 同步后的 ArrayList: [Java, Python, C++]
    }
}

使用 Deque 替代 Stack

Deque(双端队列)接口继承自 Queue,允许在两端插入和移除元素。Deque 的实现类如 ArrayDeque 和 LinkedList 提供了比 Stack 更为灵活和高效的堆栈和队列操作

import java.util.ArrayDeque;
import java.util.Deque;

public class DequeInsteadOfStack {
    public static void main(String[] args) {
        // 使用 Deque 实现堆栈操作
        Deque<Integer> stack = new ArrayDeque<>();
        
        // 压栈
        stack.push(10);
        stack.push(20);
        stack.push(30);
        System.out.println("Deque Stack: " + stack);
        // 输出: Deque Stack: [30, 20, 10]
        
        // 弹栈
        int popped = stack.pop();
        System.out.println("弹出的元素: " + popped); // 输出: 弹出的元素: 30
        System.out.println("弹栈后 Deque: " + stack);
        // 输出: 弹栈后 Deque: [20, 10]
        
        // 查看栈顶元素
        int peek = stack.peek();
        System.out.println("栈顶元素: " + peek); // 输出: 栈顶元素: 20
    }
}