深入解析Java List接口及其实现类

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 深入解析Java List接口及其实现类

Java编程语言中,List接口是Java集合框架(Java Collections Framework)的一部分,它定义了有序集合的行为,允许我们对元素进行插入、删除和访问等操作。List中的每个元素都有一个精确的索引,从0开始,这使得我们可以准确地定位和操作集合中的任何一个元素。

List接口的实现类有很多,其中最常见的是ArrayListLinkedList。这两种实现类在内部结构和性能上有很大的不同,适用于不同的使用场景。

ArrayList

ArrayList是一种基于数组的实现,它提供了快速的随机访问能力。由于数组在内存中是连续存储的,因此可以直接通过索引计算元素的内存地址,从而实现O(1)的访问时间复杂度。但是,插入和删除操作可能需要移动数组中的元素,因此时间复杂度较高,为O(n)。

下面是一个使用ArrayList的示例代码:

import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
    public static void main(String[] args) {
        // 创建一个ArrayList并添加元素
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");
        
        // 访问ArrayList中的元素
        System.out.println(list.get(0)); // 输出: Apple
        
        // 修改ArrayList中的元素
        list.set(1, "Blueberry");
        System.out.println(list.get(1)); // 输出: Blueberry
        
        // 遍历ArrayList中的元素
        for (String fruit : list) {
            System.out.println(fruit);
        }
    }
}

在这个示例中,我们创建了一个ArrayList并向其中添加了几个水果名称。然后,我们通过索引访问和修改了列表中的元素,并使用增强的for循环遍历了列表中的所有元素。

LinkedList

ArrayList不同,LinkedList是一种基于双向链表的实现。链表中的每个元素都存储了前一个和后一个元素的引用,这使得插入和删除操作变得非常高效,时间复杂度为O(1)。但是,由于链表的元素在内存中不是连续存储的,因此无法直接通过索引计算元素的内存地址,导致随机访问的时间复杂度较高,为O(n)。

下面是一个使用LinkedList的示例代码:

import java.util.LinkedList;
import java.util.List;
public class LinkedListExample {
    public static void main(String[] args) {
        // 创建一个LinkedList并添加元素
        List<String> list = new LinkedList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");
        
        // 在LinkedList的开头插入元素
        list.add(0, "Avocado");
        System.out.println(list.get(0)); // 输出: Avocado
        
        // 从LinkedList中删除元素
        list.remove(1); // 删除索引为1的元素(Banana)
        System.out.println(list.get(1)); // 输出: Cherry
        
        // 遍历LinkedList中的元素
        for (String fruit : list) {
            System.out.println(fruit);
        }
    }
}

在这个示例中,我们创建了一个LinkedList并向其中添加了几个水果名称。然后,我们在列表的开头插入了一个新元素,并删除了索引为1的元素。最后,我们使用增强的for循环遍历了列表中的所有元素。需要注意的是,与ArrayList不同,LinkedList提供了在列表开头和结尾进行插入和删除操作的额外方法,如addFirstaddLastremoveFirstremoveLast等。这些方法使得在处理栈和队列等数据结构时更加方便。

Vector

除了ArrayListLinkedList之外,Vector也是List接口的一个古老实现。VectorArrayList非常相似,都是基于数组的实现,提供了快速的随机访问能力。然而,Vector是线程安全的,而ArrayList则不是。这意味着在多线程环境中,如果多个线程同时修改Vector,那么操作将会是安全的。但请注意,即使在多线程环境中,也不总是推荐使用Vector,因为同步操作会带来额外的性能开销。在现代Java应用中,更常见的做法是使用并发集合(如CopyOnWriteArrayList)或通过良好的并发控制来处理多线程访问。

下面是一个简单的Vector示例:

import java.util.Vector;
public class VectorExample {
    public static void main(String[] args) {
        // 创建一个Vector并添加元素
        Vector<String> vector = new Vector<>();
        vector.add("Apple");
        vector.add("Banana");
        vector.add("Cherry");
        
        // 访问Vector中的元素
        System.out.println(vector.get(0)); // 输出: Apple
        
        // 修改Vector中的元素
        vector.set(1, "Blueberry");
        System.out.println(vector.get(1)); // 输出: Blueberry
        
        // 遍历Vector中的元素
        for (String fruit : vector) {
            System.out.println(fruit);
        }
    }
}

CopyOnWriteArrayList

CopyOnWriteArrayList是另一个实现了List接口的类,它是为并发访问设计的。如其名所示,当修改操作(如add、set等)发生时,它会复制底层数组,而不是在原有数组上进行修改。这样,读取操作就可以继续在不受干扰的旧数组上进行,从而实现了线程安全。但是,由于每次修改都需要复制整个数组,因此这种实现不适合写操作非常频繁的场景。它更适合读多写少的场景。

下面是一个使用CopyOnWriteArrayList的示例:

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        // 创建一个CopyOnWriteArrayList并添加元素
        List<String> list = new CopyOnWriteArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");
        
        // 模拟并发访问:一个线程读取,另一个线程修改
        new Thread(() -> {
            for (String fruit : list) {
                System.out.println("Reading: " + fruit);
                try {
                    Thread.sleep(100); // 模拟耗时操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        new Thread(() -> {
            try {
                Thread.sleep(500); // 让读取线程先开始执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.set(1, "Blueberry"); // 修改元素,这会触发底层数组的复制
            System.out.println("Writing complete.");
        }).start();
    }
}

在这个示例中,我们创建了两个线程来模拟并发访问。一个线程遍历列表并打印每个元素,而另一个线程稍后修改列表中的一个元素。由于CopyOnWriteArrayList的特性,读取线程不会看到修改操作的中间状态,它要么看到修改前的状态,要么看到修改后的状态。这保证了读取操作的一致性和原子性。

性能考虑和选择建议

在选择合适的List实现时,性能是一个重要的考虑因素:

  • ArrayList:适用于随机访问频繁且插入/删除操作较少的场景。它提供了最佳的随机访问性能。
  • LinkedList:适用于在列表的开头或结尾进行大量插入/删除操作的场景。对于需要在列表中间进行插入/删除的情况,虽然它仍然比ArrayList快,但性能优势不如在两端操作时明显。此外,它还提供了用作栈、队列和双端队列的方法。
  • Vector:由于其同步开销,通常不推荐使用,除非需要完全的线程安全且不介意额外的性能开销。在现代应用中,更推荐使用其他并发集合或手动同步机制。
  • CopyOnWriteArrayList:适用于读多写少的高并发场景。它能够提供线程安全的迭代而不需要额外的同步措施。但是,写操作的开销较大,因为它需要复制整个底层数组。因此,在写操作非常频繁的场景下应避免使用。
相关文章
|
4天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
5天前
|
Java
java线程接口
Thread的构造方法创建对象的时候传入了Runnable接口的对象 ,Runnable接口对象重写run方法相当于指定线程任务,创建线程的时候绑定了该线程对象要干的任务。 Runnable的对象称之为:线程任务对象 不是线程对象 必须要交给Thread线程对象。 通过Thread的构造方法, 就可以把任务对象Runnable,绑定到Thread对象中, 将来执行start方法,就会自动执行Runable实现类对象中的run里面的内容。
16 1
|
10天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
38 4
|
17天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
16天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
15天前
|
Java
Java基础(13)抽象类、接口
本文介绍了Java面向对象编程中的抽象类和接口两个核心概念。抽象类不能被实例化,通常用于定义子类的通用方法和属性;接口则是完全抽象的类,允许声明一组方法但不实现它们。文章通过代码示例详细解析了抽象类和接口的定义及实现,并讨论了它们的区别和使用场景。
|
15天前
|
Java 测试技术 API
Java零基础-接口详解
【10月更文挑战第19天】Java零基础教学篇,手把手实践教学!
16 1
|
20天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
15 3
|
20天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
29 2
|
20天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2

推荐镜像

更多