Java集合框架(二):List接口深度解析与ArrayList、LinkedList对比

一、List接口:有序集合的完整契约

1.1 List接口的特性

List是Java集合框架中最常用的接口之一,它具有以下核心特征:

  • 有序性:元素按照插入顺序保存,可以通过索引访问

  • 可重复性:允许存储相同的元素

  • 支持索引操作:提供了基于位置(索引)的各种方法

  • 允许null元素:大多数实现允许存储null值

1.2 List接口的完整方法体系

java 复制代码
import java.util.*;

public class ListInterfaceDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        
        // 基础Collection方法
        list.add("Java");
        list.add("Python");
        list.add("JavaScript");
        
        // List特有方法
        // 1. 按索引添加
        list.add(1, "C++");
        System.out.println("插入后: " + list);
        
        // 2. 按索引获取
        String element = list.get(2);
        System.out.println("索引2的元素: " + element);
        
        // 3. 按索引修改
        list.set(0, "Java SE");
        System.out.println("修改后: " + list);
        
        // 4. 按索引删除
        list.remove(3);
        System.out.println("删除后: " + list);
        
        // 5. 查找元素位置
        int index = list.indexOf("Python");
        System.out.println("Python的索引: " + index);
        
        // 6. 查找最后出现位置
        list.add("Java SE");
        int lastIndex = list.lastIndexOf("Java SE");
        System.out.println("Java SE最后出现位置: " + lastIndex);
        
        // 7. 子列表操作
        List<String> subList = list.subList(1, 3);
        System.out.println("子列表(1,3): " + subList);
        
        // 8. 列表迭代器(双向遍历)
        ListIterator<String> listIterator = list.listIterator();
        System.out.println("\n=== 双向遍历 ===");
        while (listIterator.hasNext()) {
            System.out.print(listIterator.next() + " ");
        }
        System.out.println("\n反向遍历:");
        while (listIterator.hasPrevious()) {
            System.out.print(listIterator.previous() + " ");
        }
    }
}

二、ArrayList:基于动态数组的实现

2.1 ArrayList的内部结构

java 复制代码
// ArrayList的简化内部结构示意
public class ArrayList<E> {
    // 核心:使用Object数组存储元素
    private Object[] elementData;
    
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    
    // 实际元素数量
    private int size;
    
    // 构造函数
    public ArrayList() {
        this.elementData = new Object[DEFAULT_CAPACITY];
    }
    
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        }
    }
}

2.2 ArrayList的核心操作源码分析

java 复制代码
import java.util.ArrayList;

public class ArrayListDeepDive {
    public static void main(String[] args) {
        // 创建ArrayList的三种方式
        ArrayList<String> list1 = new ArrayList<>();          // 默认容量10
        ArrayList<String> list2 = new ArrayList<>(20);       // 指定初始容量
        ArrayList<String> list3 = new ArrayList<>(Arrays.asList("A", "B", "C"));
        
        // 容量动态增长机制
        System.out.println("=== ArrayList扩容机制 ===");
        ArrayList<Integer> numbers = new ArrayList<>(3);
        
        for (int i = 1; i <= 10; i++) {
            numbers.add(i);
            System.out.printf("添加第%d个元素,当前容量估算: %d%n", 
                i, getCapacity(numbers));
        }
        
        // 性能测试:随机访问 vs 插入删除
        performanceTest();
    }
    
    // 反射获取ArrayList容量(仅供演示)
    private static int getCapacity(ArrayList<?> list) {
        try {
            java.lang.reflect.Field field = ArrayList.class.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] elementData = (Object[]) field.get(list);
            return elementData.length;
        } catch (Exception e) {
            return -1;
        }
    }
    
    private static void performanceTest() {
        System.out.println("\n=== 性能测试 ===");
        int size = 100000;
        
        // 1. 随机访问测试
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            arrayList.add(i);
        }
        
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            arrayList.get((int) (Math.random() * size));
        }
        long end = System.nanoTime();
        System.out.printf("ArrayList随机访问1000次耗时: %d ns%n", end - start);
        
        // 2. 头部插入测试
        start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            arrayList.add(0, i);  // 在头部插入
        }
        end = System.nanoTime();
        System.out.printf("ArrayList头部插入1000次耗时: %d ns%n", end - start);
    }
}

2.3 ArrayList的迭代与快速失败机制

java 复制代码
import java.util.*;

public class ArrayListIteration {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(
            Arrays.asList("Java", "Python", "C++", "JavaScript", "Go")
        );
        
        // 1. 普通for循环(适合随机访问)
        System.out.println("=== 普通for循环 ===");
        for (int i = 0; i < list.size(); i++) {
            System.out.println("索引" + i + ": " + list.get(i));
        }
        
        // 2. 增强for循环(语法糖,底层使用迭代器)
        System.out.println("\n=== 增强for循环 ===");
        for (String lang : list) {
            System.out.println(lang);
        }
        
        // 3. 迭代器遍历
        System.out.println("\n=== 迭代器遍历 ===");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String lang = iterator.next();
            System.out.println(lang);
        }
        
        // 4. 列表迭代器(功能更强大)
        System.out.println("\n=== 列表迭代器 ===");
        ListIterator<String> listIterator = list.listIterator(2); // 从索引2开始
        while (listIterator.hasNext()) {
            int index = listIterator.nextIndex();
            String lang = listIterator.next();
            System.out.println("索引" + index + ": " + lang);
        }
        
        // 5. forEach方法(Java 8+)
        System.out.println("\n=== forEach方法 ===");
        list.forEach(System.out::println);
        
        // 6. 并发修改异常演示
        System.out.println("\n=== 快速失败机制演示 ===");
        try {
            for (String lang : list) {
                if ("C++".equals(lang)) {
                    list.remove(lang);  // 抛出ConcurrentModificationException
                }
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("捕获到并发修改异常: " + e.getMessage());
        }
        
        // 正确的方式:使用迭代器的remove方法
        System.out.println("\n=== 安全删除 ===");
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String lang = it.next();
            if ("C++".equals(lang)) {
                it.remove();  // 安全删除
            }
        }
        System.out.println("删除后: " + list);
    }
}

三、LinkedList:基于双向链表的实现

3.1 LinkedList的内部结构

java 复制代码
// LinkedList节点结构的简化示意
private static class Node<E> {
    E item;           // 存储的元素
    Node<E> next;     // 指向下一个节点
    Node<E> prev;     // 指向前一个节点
    
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

3.2 LinkedList的核心操作

java 复制代码
import java.util.*;

public class LinkedListDemo {
    public static void main(String[] args) {
        // LinkedList实现了List和Deque接口
        LinkedList<String> linkedList = new LinkedList<>();
        
        // 1. 作为List的基本操作
        linkedList.add("Java");
        linkedList.add("Python");
        linkedList.addFirst("C");      // 添加到头部
        linkedList.addLast("Go");      // 添加到尾部
        
        System.out.println("初始链表: " + linkedList);
        System.out.println("第一个元素: " + linkedList.getFirst());
        System.out.println("最后一个元素: " + linkedList.getLast());
        
        // 2. 作为队列的操作
        System.out.println("\n=== 队列操作 ===");
        Queue<String> queue = linkedList;
        queue.offer("新任务");      // 入队(添加到尾部)
        System.out.println("出队: " + queue.poll());  // 出队(从头部移除)
        
        // 3. 作为栈的操作
        System.out.println("\n=== 栈操作 ===");
        Deque<String> stack = linkedList;
        stack.push("栈顶元素");      // 压栈
        System.out.println("栈顶: " + stack.peek());
        System.out.println("弹栈: " + stack.pop());
        
        // 4. 性能测试:插入删除
        performanceTest();
    }
    
    private static void performanceTest() {
        System.out.println("\n=== LinkedList性能测试 ===");
        int size = 100000;
        LinkedList<Integer> linkedList = new LinkedList<>();
        
        // 头部插入性能
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            linkedList.addFirst(i);  // 头部插入
        }
        long end = System.nanoTime();
        System.out.printf("LinkedList头部插入1000次耗时: %d ns%n", end - start);
        
        // 随机访问性能
        start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            linkedList.get((int) (Math.random() * linkedList.size()));
        }
        end = System.nanoTime();
        System.out.printf("LinkedList随机访问1000次耗时: %d ns%n", end - start);
    }
}

四、ArrayList vs LinkedList:全方位对比

数据结构对比

特性 ArrayList LinkedList
底层结构 动态数组 双向链表
内存占用 连续内存,预留空间 分散内存,每个元素额外存储指针
默认容量 10 无(动态增长)
扩容机制 1.5倍增长 无需扩容,动态创建节点

五、选择指南:何时使用ArrayList,何时使用LinkedList

5.1 使用ArrayList的场景

场景1:需要频繁随机访问

场景2:存储大量数据,内存敏感

场景3:尾部操作为主

5.2 使用LinkedList的场景

场景1:需要频繁在头部/中部插入删除

场景2:需要实现栈、队列、双端队列

场景3:中间位置操作频繁

六、总结与建议

6.1 核心要点回顾

  1. ArrayList优势

    • 随机访问速度快(O(1))

    • 内存占用少(连续存储)

    • 尾部操作效率高

  2. LinkedList优势

    • 头部/中间插入删除快(O(1)找到位置后)

    • 可以实现多种数据结构(栈、队列、双端队列)

    • 无扩容开销

  3. 通用原则

    • 大多数情况下,ArrayList是默认选择

    • 只有在特定场景下才选择LinkedList

    • 根据具体操作类型选择合适的数据结构

6.2 决策流程图

text

复制代码
开始选择List实现
    │
    ├─ 是否需要频繁随机访问?
    │   ├─ 是 → 选择ArrayList
    │   └─ 否 → 
    │        ├─ 是否需要频繁在头部/中间插入删除?
    │        │   ├─ 是 → 选择LinkedList
    │        │   └─ 否 →
    │        │        ├─ 是否需要实现栈/队列/双端队列?
    │        │        │   ├─ 是 → 选择LinkedList
    │        │        │   └─ 否 → 选择ArrayList(默认)
    │        │        └─ 内存是否非常紧张?
    │        │            ├─ 是 → 测试两种实现的实际内存占用
    │        │            └─ 否 → 选择ArrayList
    │        └─ 数据量是否特别大?
    │            ├─ 是 → 考虑分页或数据库
    │            └─ 否 → 选择ArrayList
    └─ 是否需要线程安全?
        ├─ 是 → 考虑CopyOnWriteArrayList或Collections.synchronizedList
        └─ 否 → 继续上述判断

下篇预告:《Java集合(三):Set接口详解与HashSet、TreeSet、LinkedHashSet比较》

通过本篇学习,你应该已经掌握了List接口的核心知识以及ArrayList和LinkedList的详细对比。在实际开发中,合理选择数据结构是写出高效代码的关键。记住:没有最好的数据结构,只有最适合的场景!

相关推荐
daidaidaiyu2 小时前
一文学习和实践 当下互联网安全的基石 - TLS 和 SSL
java·netty
hssfscv3 小时前
Javaweb学习笔记——后端实战2_部门管理
java·笔记·学习
NE_STOP3 小时前
认识shiro
java
kong79069283 小时前
Java基础-Lambda表达式、Java链式编程
java·开发语言·lambda表达式
liangsheng_g3 小时前
泛型新认知
java·序列化·泛型
液态不合群3 小时前
【面试题】MySQL 三层 B+ 树能存多少数据?
java·数据库·mysql
李坤林4 小时前
Android Binder 详解(4) Binder 线程池
android·java·binder
代码方舟4 小时前
Java后端实战:构建基于天远手机号码归属地核验的金融级风控模块
java·大数据·开发语言·金融
困知勉行19854 小时前
springboot整合redis
java·spring boot·redis
颜淡慕潇4 小时前
深度解析官方 Spring Boot 稳定版本及 JDK 配套策略
java·后端·架构