详解Java List,业务场景中如何选择

一、 List概述

Java中的List是一个接口,它继承自Collection接口,代表了一个有序的集合,其中的元素可以重复。List提供了一系列方法用于对集合中的元素进行操作,例如添加、删除、获取元素等。Java中常见的List实现类有ArrayListLinkedListVectorStackCopyOnWriteArrayList

在实际开发中,List接口是一种频繁使用的数据结构,它提供了丰富的方法来操作有序的元素集合。由于其灵活性和常用性,List在许多场景下被广泛应用,是开发人员经常选择的数据结构之一。

1.1 List 接口的常见实现类

Java中提供了非常多的使用的List实现类,本文将重点介绍一下这些类以及他们的应用场景。首先罗列一下本文要介绍的实现类都有哪些。

graph LR A(List实现类) B(ArrayList) C(LinkedList) C1(Queue) C2(Deque) D(Vector) E(Stack) F(CopyOnWriteArrayList) A ---> B A ---> C C --实现了--> C2 --继承--> C1 A ---> D A ---> E A ---> F style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#00FFFF,stroke:#00FFFF,stroke-width:2px style C1 fill:#98FB98,stroke:#98FB98,stroke-width:2px style C2 fill:#00FFFF,stroke:#00FFFF,stroke-width:2px

1.2 List接口都定义了那些方法

List接口里面定义的方法还是挺多的,大体可以分为六类,下面我将这些方法分类说明一下:

graph TD A(List方法分类) B(添加元素) C(获取元素) D(删除元素) E(修改元素) F(列表大小) G(遍历元素) A --> B A --> C A --> D A --> E A --> F A --> G style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px style G fill:#00FFFF,stroke:#00FFFF,stroke-width:2px
  1. 添加元素:

    • boolean add(E element):向列表的末尾添加一个元素。

    • void add(int index, E element):在指定的索引位置添加一个元素。

  2. 获取元素:

    • E get(int index):获取指定索引位置的元素。

    • int indexOf(Object obj):返回指定元素在列表中首次出现的索引。

    • int lastIndexOf(Object obj):返回指定元素在列表中最后出现的索引。

  3. 删除元素:

    • boolean remove(Object obj):从列表中删除指定元素的第一个匹配项。

    • E remove(int index):删除指定索引位置的元素。

  4. 修改元素:

    • E set(int index, E element):替换指定索引位置的元素。
  5. 列表大小:

    • int size():返回列表中的元素数量。

    • boolean isEmpty():检查列表是否为空。

  6. 遍历元素:

    • 使用迭代器(Iterator)遍历列表中的元素。

    • 使用增强的for循环(for-each)遍历列表中的元素。

二、ArrayList

ArrayList是一个动态数组实现的类,它是Java集合框架中List接口的一个常用实现类。与传统的数组相比,ArrayList具有更灵活的长度和操作方式。

通过使用ArrayList,可以方便地管理和操作元素集合,它是Java开发中常用的数据结构之一。

2.1 ArrayList的特点

  • 动态数组:ArrayList在内部使用数组来存储元素,并且具有动态扩容的能力。当元素数量超过当前数组容量时,ArrayList会自动增加其容量以容纳更多的元素。

  • 有序集合:ArrayList是一个有序集合,可以按照元素的插入顺序迭代访问元素。

  • 允许重复元素:ArrayList允许存储重复的元素,即可以在列表中存储相同的元素多次。

  • 随机访问:由于ArrayList使用基于索引的数组实现,因此可以通过索引进行快速的随机访问和修改元素。可以使用get(index)方法根据索引获取元素,使用set(index, element)方法根据索引修改元素。

  • 动态修改:ArrayList提供了一系列方法来动态修改列表,包括添加元素、删除元素、插入元素等。常用的方法包括add(element)用于在列表末尾添加元素,remove(element)用于删除指定元素,add(index, element)用于在指定位置插入元素等。

  • 支持迭代器:ArrayList实现了Iterable接口,因此可以使用迭代器来遍历列表中的元素。可以通过iterator()方法获取迭代器,并使用hasNext()和next()方法依次访问元素。

  • 非线程安全:ArrayList不是线程安全的,如果在多个线程同时修改ArrayList时,需要进行外部同步或使用线程安全的替代类,如CopyOnWriteArrayList。

2.2 ArrayList使用案例

demo:

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

public class ArrayListDemo {

    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("苹果");
        fruits.add("香蕉");
        fruits.add("榴莲");
        fruits.add("菠萝");

        // 获取ArrayList的大小
        int size = fruits.size();
        System.out.println("ArrayList的大小:" + size);

        // 访问指定位置的元素
        String element = fruits.get(2);
        System.out.println("索引2上的元素:" + element);

        // 修改指定位置的元素
        fruits.set(1, "菠萝蜜");
        System.out.println("修改后的ArrayList:" + fruits);

        // 删除指定位置的元素
        String removedElement = fruits.remove(3);
        System.out.println("被删除的元素:" + removedElement);
        System.out.println("删除后的ArrayList:" + fruits);

        // 检查ArrayList是否包含某个元素
        boolean contains = fruits.contains(30);
        System.out.println("ArrayList是否包含30:" + contains);

        // 清空ArrayList
        fruits.clear();
        System.out.println("清空后的ArrayList:" + fruits);
    }

}

输出结果:

java 复制代码
ArrayList的大小:4
索引2上的元素:榴莲
修改后的ArrayList:[苹果, 菠萝蜜, 榴莲, 菠萝]
被删除的元素:菠萝
删除后的ArrayList:[苹果, 菠萝蜜, 榴莲]
ArrayList是否包含30:false
清空后的ArrayList:[]

三、LinkedList

LinkedList是Java集合框架中的一个实现类,它实现了List接口和Deque接口,基于双向链表的数据结构。相比于ArrayListLinkedList在某些场景下具有一些特殊的优势和适用性。

LinkedList适用于需要频繁进行插入和删除操作的场景,特别是在实现队列和栈时。

3.1 LinkedList的特点

  • 双向链表:LinkedList内部使用双向链表来存储元素。每个节点都包含对前一个节点和后一个节点的引用,因此在插入和删除元素时,LinkedList比ArrayList更高效。由于不需要像ArrayList那样进行数组的扩容和元素的移动,LinkedList对于频繁的插入和删除操作更快。

  • 高效的插入和删除操作:由于LinkedList的双向链表结构,插入和删除元素的平均时间复杂度为O(1),而在ArrayList中,这些操作的时间复杂度为O(n),其中n是元素的数量。因此,在需要频繁进行插入和删除操作的场景下,LinkedList通常比ArrayList更适合。

  • 低效的随机访问:由于LinkedList是基于链表实现的,访问元素需要从头节点或尾节点开始遍历链表,因此随机访问元素的效率较低。在需要频繁进行随机访问的场景下,ArrayList通常更适合。

  • 适合实现队列和栈:LinkedList实现了Queue接口和Deque接口,因此可以用作队列(先进先出)和栈(后进先出)的数据结构。它提供了相关的方法,如add()和remove()用于队列操作,以及push()和pop()用于栈操作。

  • 内存消耗较大:相比于ArrayList,LinkedList在存储相同数量元素时需要更多的内存,因为每个节点都需要额外的引用来指向前一个节点和后一个节点。

3.2 LinkedList使用案例

demo:

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

public class LinkedListDemo {

    public static void main(String[] args) {
        // 创建一个LinkedList,用于存储字符串
        LinkedList<String> names = new LinkedList<>();

        // 添加元素到LinkedList
        names.add("张三");
        names.add("李四");
        names.add("王五");
        names.add("赵六");

        // 获取LinkedList的大小
        int size = names.size();
        System.out.println("LinkedList的大小:" + size);

        // 访问指定位置的元素
        String element = names.get(2);
        System.out.println("索引2上的元素:" + element);

        // 修改指定位置的元素
        names.set(1, "林七");
        System.out.println("修改后的LinkedList:" + names);

        // 删除指定位置的元素
        String removedElement = names.remove(3);
        System.out.println("被删除的元素:" + removedElement);
        System.out.println("删除后的LinkedList:" + names);

        // 在特定位置插入元素
        names.add(0, "马八");
        System.out.println("插入后的LinkedList:" + names);

        // 检查LinkedList是否包含某个元素
        boolean contains = names.contains("李四");
        System.out.println("LinkedList是否包含李四:" + contains);

        // 清空LinkedList
        names.clear();
        System.out.println("清空后的LinkedList:" + names);
    }

}

输出结果:

java 复制代码
LinkedList的大小:4
索引2上的元素:王五
修改后的LinkedList:[张三, 林七, 王五, 赵六]
被删除的元素:赵六
删除后的LinkedList:[张三, 林七, 王五]
插入后的LinkedList:[马八, 张三, 林七, 王五]
LinkedList是否包含李四:false
清空后的LinkedList:[]

四、Vector

Vector是Java集合框架中的一个类,它实现了List接口,是一个动态数组(类似于ArrayList)的线程安全版本。与ArrayList相比,Vector具有额外的同步机制,可以在多线程环境中安全地使用。

虽然Vector具有线程安全的特性,但由于同步机制的开销,它在性能上可能不如ArrayList。因此,如果在单线程环境下工作,建议使用ArrayList;仅在多线程环境下需要线程安全操作时,才考虑使用Vector

需要注意的是,在Java 5及以后的版本中,推荐使用更加高效的并发集合类,如CopyOnWriteArrayListConcurrentLinkedDeque,来替代Vector,因为它们提供更好的性能和扩展性。

4.1 Vector 的特点

  • 动态数组:Vector内部使用数组来存储元素,并且具有动态扩容的能力。当元素数量超过当前数组容量时,Vector会自动增加其容量以容纳更多的元素。

  • 线程安全:Vector的操作是线程安全的,即多个线程可以同时对Vector进行操作而不会导致数据不一致或其他线程安全问题。Vector通过使用同步机制来实现线程安全,确保在多线程环境中的并发访问操作的正确性。

  • 有序集合:Vector是一个有序集合,可以按照元素的插入顺序迭代访问元素。

  • 允许重复元素:Vector允许存储重复的元素,即可以在列表中存储相同的元素多次。

  • 随机访问:由于Vector使用基于索引的数组实现,因此可以通过索引进行快速的随机访问和修改元素。可以使用get(index)方法根据索引获取元素,使用set(index, element)方法根据索引修改元素。

  • 动态修改:Vector提供了一系列方法来动态修改列表,包括添加元素、删除元素、插入元素等。常用的方法包括add(element)用于在列表末尾添加元素,remove(element)用于删除指定元素,add(index, element)用于在指定位置插入元素等。

  • 迭代器支持:Vector实现了Iterable接口,因此可以使用迭代器来遍历列表中的元素。可以通过iterator()方法获取迭代器,并使用hasNext()和next()方法依次访问元素。

4.2 Vector 使用案例

demo:

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

public class VectorExample {

    public static void main(String[] args) {
        // 创建一个Vector,用于存储整数
        Vector<Integer> numbers = new Vector<>();

        // 添加元素到Vector
        numbers.add(11);
        numbers.add(22);
        numbers.add(33);
        numbers.add(44);

        // 获取Vector的大小
        int size = numbers.size();
        System.out.println("Vector的大小:" + size);

        // 访问指定位置的元素
        int element = numbers.get(2);
        System.out.println("索引2上的元素:" + element);

        // 修改指定位置的元素
        numbers.set(1, 25);
        System.out.println("修改后的Vector:" + numbers);

        // 删除指定位置的元素
        int removedElement = numbers.remove(3);
        System.out.println("被删除的元素:" + removedElement);
        System.out.println("删除后的Vector:" + numbers);

        // 在特定位置插入元素
        numbers.add(0, 5);
        System.out.println("插入后的Vector:" + numbers);

        // 检查Vector是否包含某个元素
        boolean contains = numbers.contains(30);
        System.out.println("Vector是否包含30:" + contains);

        // 清空Vector
        numbers.clear();
        System.out.println("清空后的Vector:" + numbers);
    }

}

输出结果:

java 复制代码
Vector的大小:4
索引2上的元素:33
修改后的Vector:[11, 25, 33, 44]
被删除的元素:44
删除后的Vector:[11, 25, 33]
插入后的Vector:[5, 11, 25, 33]
Vector是否包含30:false
清空后的Vector:[]

五、Stack

Stack(栈)是Java集合框架中的一个类,它实现了"后进先出"(Last-In-First-Out,LIFO)的数据结构。Stack继承自Vector类,因此具有Vector的所有特性,同时提供了一些额外的栈操作方法。

Stack的主要用途是在需要后进先出操作的场景中,例如在逆序输出、括号匹配、深度优先搜索等算法中常用到。需要注意的是,由于Stack继承自Vector,它具有线程安全的特性,但在性能上可能不如其他非同步的栈实现,如ArrayDeque。因此,在不需要线程安全操作的情况下,可以考虑使用ArrayDeque代替Stack

5.1 Stack 的特点

  • 后进先出(LIFO):Stack中的元素按照后进先出的顺序进行操作。最后添加的元素将首先被访问或删除,而最先添加的元素将最后被访问或删除。

  • 继承自Vector:Stack继承了Vector类的所有功能,包括动态数组实现、随机访问、动态修改等。由于Stack是Vector的子类,因此可以使用Vector的所有方法来操作栈。

  • 压栈和出栈:Stack提供了push(element)方法用于将元素压入栈顶,以及pop()方法用于从栈顶弹出并返回栈顶元素。通过这两个方法,可以实现栈的基本操作。

  • 查看栈顶元素:Stack提供了peek()方法,用于返回但不删除栈顶元素。这个方法可以用于查看栈顶元素而不改变栈的状态。

  • 判空和栈大小:Stack提供了isEmpty()方法来检查栈是否为空,以及size()方法来获取栈中元素的数量。

  • 搜索元素:Stack提供了search(element)方法,用于在栈中搜索指定元素,并返回相对于栈顶的距离(如果元素存在于栈中)。如果元素不存在于栈中,则返回-1。

5.2 Stack 使用案例

demo:

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

public class StackDemo {

    public static void main(String[] args) {
        // 创建一个Stack,用于存储整数
        Stack<Integer> stack = new Stack<>();

        // 压入元素到栈顶
        stack.push(66);
        stack.push(88);
        stack.push(99);

        // 查看栈顶元素
        int topElement = stack.peek();
        System.out.println("栈顶元素:" + topElement);

        // 弹出栈顶元素
        int poppedElement = stack.pop();
        System.out.println("弹出的元素:" + poppedElement);

        // 查看栈的大小
        int size = stack.size();
        System.out.println("栈的大小:" + size);

        // 判断栈是否为空
        boolean isEmpty = stack.isEmpty();
        System.out.println("栈是否为空:" + isEmpty);
    }

}

输出结果:

java 复制代码
栈顶元素:99
弹出的元素:99
栈的大小:2
栈是否为空:false

六、CopyOnWriteArrayList

CopyOnWriteArrayList是Java并发集合框架中的一种线程安全的列表实现。

由于CopyOnWriteArrayList的写操作会创建新的副本,因此在多个线程同时进行写操作时,不会发生数据不一致的情况。最终输出的列表中包含了所有写线程添加的元素。

注意,由于CopyOnWriteArrayList的特性,读取操作不会受到写操作的影响,因此可以安全地在写操作进行时进行读取操作。

6.1 CopyOnWriteArrayList 的特点

  • 线程安全:CopyOnWriteArrayList通过在修改操作时创建一个新的副本来实现线程安全性。这意味着多个线程可以同时进行读取操作,而不会阻塞彼此,且读取操作不会受到修改操作的影响。

  • 写时复制:在修改操作(如添加、修改、删除元素)时,CopyOnWriteArrayList会创建一个数组的新副本,以保持原有数组的不可变性。这意味着修改操作不会直接修改原始数组,而是在新副本上进行操作,从而保证了读取操作的线程安全性。

  • 高效的读取操作:由于读取操作不需要进行同步或加锁,所以读取操作的性能很高。适用于读多写少的场景。

  • 适用于静态数据集:CopyOnWriteArrayList适用于静态数据集,即在创建后很少有修改操作。如果需要频繁进行修改操作,可能会产生较高的内存开销,因为每次修改都会创建新的副本。

6.2 CopyOnWriteArrayList 使用案例

demo:

java 复制代码
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListDemo {

    public static void main(String[] args) {
        // 创建一个CopyOnWriteArrayList,用于存储整数
        CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>();

        // 创建并启动多个线程进行写操作
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                numbers.add(finalI);
                System.out.println("线程" + Thread.currentThread().getName() + ":添加元素 " + finalI);
            });
            thread.start();
        }

        // 等待所有写线程执行完毕
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出列表中的元素
        System.out.println("列表中的元素:" + numbers);
    }

}

输出结果:

java 复制代码
线程Thread-0:添加元素 0
线程Thread-4:添加元素 4
线程Thread-3:添加元素 3
线程Thread-1:添加元素 1
线程Thread-2:添加元素 2
列表中的元素:[0, 1, 3, 4, 2]

七、总结

ArrayList、LinkedList、Vector、Stack和CopyOnWriteArrayList都是Java集合框架中的List的实现类,用于存储有序的元素集合,但它们在底层数据结构、线程安全性以及性能特点上存在一些差异。

在实际开发中我们要根据业务的需求来合理的选择不同的数据结构。

希望本文能给你带来帮助,如有错误或建议,欢迎指正和提出。

相关推荐
凡人的AI工具箱4 分钟前
15分钟学 Python 第38天 :Python 爬虫入门(四)
开发语言·人工智能·后端·爬虫·python
loss_rose77713 分钟前
【场景题】秒杀系统设计以及解决方案
java
java_heartLake23 分钟前
设计模式之解释器模式
java·设计模式·解释器模式
丶213636 分钟前
【SQL】深入理解SQL:从基础概念到常用命令
数据库·后端·sql
风清扬_jd38 分钟前
Chromium 硬件加速开关c++
java·前端·c++
木子020439 分钟前
Nacos的应用
后端
哎呦没39 分钟前
Spring Boot框架在医院管理中的应用
java·spring boot·后端
苓诣39 分钟前
Java Vector类
java·数据结构
陈序缘1 小时前
Go语言实现长连接并发框架 - 消息
linux·服务器·开发语言·后端·golang
络71 小时前
Spring14——案例:利用AOP环绕通知计算业务层接口执行效率
java·后端·spring·mybatis·aop