详解Java List中五种常见实现类的使用

 更新时间:2023年10月18日 08:07:12   作者:独爱竹子的功夫熊猫  
Java中提供了非常多的使用的List实现类,本文将重点介绍一下常见的五种实现类以及他们的应用场景,感兴趣的小伙伴可以跟随小编一起学习一下

一、 List概述

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

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

1.1 List 接口的常见实现类

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

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

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

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:

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);
    }

}

输出结果:

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:

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);
    }

}

输出结果:

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:

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);
    }

}

输出结果:

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:

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);
    }

}

输出结果:

栈顶元素:99
弹出的元素:99
栈的大小:2
栈是否为空:false

六、CopyOnWriteArrayList

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

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

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

6.1 CopyOnWriteArrayList 的特点

  • 线程安全:CopyOnWriteArrayList通过在修改操作时创建一个新的副本来实现线程安全性。这意味着多个线程可以同时进行读取操作,而不会阻塞彼此,且读取操作不会受到修改操作的影响。
  • 写时复制:在修改操作(如添加、修改、删除元素)时,CopyOnWriteArrayList会创建一个数组的新副本,以保持原有数组的不可变性。这意味着修改操作不会直接修改原始数组,而是在新副本上进行操作,从而保证了读取操作的线程安全性。
  • 高效的读取操作:由于读取操作不需要进行同步或加锁,所以读取操作的性能很高。适用于读多写少的场景。
  • 适用于静态数据集:CopyOnWriteArrayList适用于静态数据集,即在创建后很少有修改操作。如果需要频繁进行修改操作,可能会产生较高的内存开销,因为每次修改都会创建新的副本。

6.2 CopyOnWriteArrayList 使用案例

demo:

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);
    }

}

输出结果:

线程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的实现类,用于存储有序的元素集合,但它们在底层数据结构、线程安全性以及性能特点上存在一些差异。

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

以上就是详解Java List中五种常见实现类的使用的详细内容,更多关于Java List的资料请关注脚本之家其它相关文章!

相关文章

  • SpringCloud中的分布式锁用法示例详解(Java+Redis SETNX命令)

    SpringCloud中的分布式锁用法示例详解(Java+Redis SETNX命令)

    在Spring Cloud项目中,使用Java和Redis结合实现的分布式锁可以确保订单的一致性和并发控制,分布式锁的使用能够在多个实例同时提交订单时,仅有一个实例可以成功进行操作,本文给大家介绍Spring,Cloud中的分布式锁用法详解(Java+Redis SETNX命令),感兴趣的朋友一起看看吧
    2023-10-10
  • jdk动态代理源码分析过程

    jdk动态代理源码分析过程

    这篇文章主要介绍了jkd动态代理源码分析过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • java后台接受到图片后保存方法

    java后台接受到图片后保存方法

    在本篇文章里小编给大家整理了关于java后台接受到图片后怎么保存的相关知识点,需要的朋友们参考学习下。
    2019-06-06
  • Maven高级的聚合和继承的实现

    Maven高级的聚合和继承的实现

    在软件开发中,随着项目规模的扩大,单个模块的开发方式逐渐转变为多模块开发,这种方式带来了项目管理上的挑战,其中最常见的问题是模块间的依赖管理和版本控制问题,本文就来介绍一下
    2024-10-10
  • Java class文件格式之访问标志信息_动力节点Java学院整理

    Java class文件格式之访问标志信息_动力节点Java学院整理

    access_flags 描述的是当前类(或者接口)的访问修饰符, 如public, private等, 此外, 这里面还存在一个标志位, 标志当前的额这个class描述的是类, 还是接口
    2017-06-06
  • Spring的IOC解决程序耦合的实现

    Spring的IOC解决程序耦合的实现

    本文主要介绍了Spring的IOC解决程序耦合的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • 史上最全Java8日期时间工具类(分享)

    史上最全Java8日期时间工具类(分享)

    这篇文章主要介绍了史上最全Java8日期时间工具类(分享),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Spring Boot中WebMvcConfig配置详解及示例代码

    Spring Boot中WebMvcConfig配置详解及示例代码

    WebMvcConfig是一个配置类,它继承了WebMvcConfigurationSupport,允许我们对SpringMVC进行更细粒度的控制,这篇文章主要给大家介绍了关于Spring Boot中WebMvcConfig配置详解及示例的相关资料,需要的朋友可以参考下
    2024-03-03
  • 最新springboot解决跨域的几种方式小结

    最新springboot解决跨域的几种方式小结

    跨域指的是浏览器不能执⾏其他⽹站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制,这篇文章主要介绍了springboot解决跨域的几种方式,需要的朋友可以参考下
    2022-05-05
  • maven关于pom文件中的relativePath标签使用

    maven关于pom文件中的relativePath标签使用

    在Maven项目中,子工程通过<relativePath>标签指定父工程的pom.xml位置,以确保正确继承父工程的配置,这个标签可以配置为默认值、空值或自定义值,默认情况下,Maven会向上一级目录寻找父pom;若配置为空值
    2024-09-09

最新评论