详解Java List中五种常见实现类的使用
作者:独爱竹子的功夫熊猫
一、 List概述
Java中的List
是一个接口,它继承自Collection
接口,代表了一个有序的集合,其中的元素可以重复。List提供了一系列方法用于对集合中的元素进行操作,例如添加、删除、获取元素等。Java中常见的List实现类有ArrayList
、LinkedList
、Vector
、Stack
和CopyOnWriteArrayList
。
在实际开发中,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
接口,基于双向链表的数据结构。相比于ArrayList
,LinkedList
在某些场景下具有一些特殊的优势和适用性。
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及以后的版本中,推荐使用更加高效的并发集合类,如CopyOnWriteArrayList
或ConcurrentLinkedDeque
,来替代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的资料请关注脚本之家其它相关文章!