Java学习-详谈List集合(ArrayList、LinkedList、Vector和Stack)

tech2022-07-15  160

1.List接口介绍

java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了 List 接口的对象称为List集合。 List集合有以下特点:

在List集合中允许出现重复的元素(通过元素的equals方法来比较是否为重复的元素)所有的元素是以一种线性方式进行存储的它是一个带有索引的集合,在程序中可以通过索引来访问集合中的指定元素List集合的元素有序,即元素的存入顺序和取出顺序一致

2.List接口中常用的方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操 作集合的特有方法,如下:

public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。public E get(int index) :返回集合中指定位置的元素。public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。public boolean addAll(int index,Collection c):将集合c所包含的所有元素都插入在List集合的Index处。public int indexOf(Object o):返回对象o在List集合中出现的位置索引。public int lastIndexOf(Object o):返回对象o在List集合中最后一次出现的位置索引。public List subList(int fromIndex,int toIndex):返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合。

3.ArrayList集合

3.1引入——对象数组

下列代码,使用学生数组,存储三个学生对象

class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class test { public static void main(String[] args) { //创建学生数组 Student[] students = new Student[3]; //创建学生对象 Student s1 = new Student("曹操",40); Student s2 = new Student("刘备",35); Student s3 = new Student("孙权",30); //把学生对象作为元素赋值给学生数组 students[0] = s1; students[1] = s2; students[2] = s3; //遍历学生数组 for(int x=0; x<students.length; x++) { Student s = students[x]; System.out.println(s.getName()+"‐‐‐"+s.getAge()); } } }

我们发现上述代码中存储对象数据,选择的容器是对象数组。而数组的长度是固定的,无法适应数据变化的需求。为了解决这个问题,Java提供了另一个容器 java.util.ArrayList 集合类,让我们可以更便捷的存储和操作对 象数据。

3.2ArrayList的简介

ArrayList基于数组实现,是一个动态的数组队列。但是它和Java中的数组又不一样,它的容量可以自动增长,类似于C语言中动态申请内存,动态增长内存!ArrayList继承了AbstractList,实现了RandomAccess、Cloneable和Serializable接口! public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 继承了AbstractList,AbstractList又继承了AbstractCollection实现了List接口,它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能! public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { /** * Sole constructor. (For invocation by subclass constructors, typically * implicit.) */ protected AbstractList() { } 实现了RandomAccess接口,提供了随机访问功能,实际上就是通过下标序号进行快速访问。实现了Cloneable接口,即覆盖了函数clone(),能被克隆。实现了Serializable接口,支持序列化,也就意味了ArrayList能够通过序列化传输。

3.3ArrayList的使用

查看类:java.util.ArrayList <E> :该类需要 import导入使后使用。

<E> ,表示一种指定的数据类型,叫做泛型。 E ,取自Element(元素)的首字母。在出现 E 的地方,我们使用一种引用数据类型将其替换即可,表示我们将存储哪种引用类型的元素。比如:

ArrayList<String>,ArrayList<Student> 查看构造方法:public ArrayList() 构造一个内容为空的集合。

基本格式:

ArrayList<String> list = new ArrayList<String>();

在JDK 7后,右侧泛型的尖括号之内可以留空,但是<>仍然要写。简化格式:

ArrayList<String> list = new ArrayList<>();

3.4ArrayList的方法(API)

// Collection中定义的API boolean add(E object) boolean addAll(Collection<? extends E> collection) void clear() boolean contains(Object object) boolean containsAll(Collection<?> collection) boolean equals(Object object) int hashCode() boolean isEmpty() Iterator<E> iterator() boolean remove(Object object) boolean removeAll(Collection<?> collection) boolean retainAll(Collection<?> collection) int size() <T> T[] toArray(T[] array) Object[] toArray() // AbstractCollection中定义的API void add(int location, E object) boolean addAll(int location, Collection<? extends E> collection) E get(int location) int indexOf(Object object) int lastIndexOf(Object object) ListIterator<E> listIterator(int location) ListIterator<E> listIterator() E remove(int location) E set(int location, E object) List<E> subList(int start, int end) // ArrayList新增的API Object clone() void ensureCapacity(int minimumCapacity) void trimToSize() void removeRange(int fromIndex, int toIndex)

对于元素的操作,基本体现在——增、删、查。常用的方法有:

public boolean add(E e) :将指定的元素添加到此集合的尾部。public E remove(int index) :移除此集合中指定位置上的元素。返回被删除的元素。public E get(int index) :返回此集合中指定位置上的元素。返回获取的元素。public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。

这些都是最基本的方法,操作非常简单,代码如下:

import java.util.ArrayList; public class test { public static void main(String[] args) { //创建集合对象 ArrayList<String> list = new ArrayList<String>(); //添加元素 list.add("hello"); list.add("world"); list.add("java"); //public E get(int index):返回指定索引处的元素 System.out.println("get:"+list.get(0)); System.out.println("get:"+list.get(1)); System.out.println("get:"+list.get(2)); //public int size():返回集合中的元素的个数 System.out.println("size:"+list.size()); //public E remove(int index):删除指定索引处的元素,返回被删除的元素 System.out.println("remove:"+list.remove(0)); //遍历输出 for(int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } }

运行:

get:hello get:world get:java size:3 remove:hello world java

3.5 ArrayList如何存储基本数据类型

ArrayList对象不能存储基本类型,只能存储引用类型的数据。类似 不能写,但是存储基本数据类型对应的包装类型是可以的。所以,想要存储基本类型数据, <> 中的数据类型,必须转换后才能编写,转换写法如下: 例如:

import java.util.ArrayList; public class test { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); list.add(4); System.out.println(list); } } [1, 2, 3, 4]

3.6 ArrayList练习

数值添加到集合 :生成6个1~33之间的随机整数,添加到集合,并遍历

import java.util.ArrayList; import java.util.Random; public class test { public static void main(String[] args) { // 创建Random 对象 Random random = new Random(); // 创建ArrayList 对象 ArrayList<Integer> list = new ArrayList<>(); // 添加随机数到集合 for (int i = 0; i < 6; i++) { int r = random.nextInt(33) + 1; list.add(r); } // 遍历集合输出 for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } } 28 23 15 24 2 16

对象添加到集合: 自定义4个学生对象,添加到集合,并遍历

import java.util.ArrayList; class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class test { public static void main(String[] args) { //创建集合对象 ArrayList<Student> list = new ArrayList<Student>(); //创建学生对象 Student s1 = new Student("赵丽颖",18); Student s2 = new Student("唐嫣",20); Student s3 = new Student("景甜",25); Student s4 = new Student("柳岩",19); //把学生对象作为元素添加到集合中 list.add(s1); list.add(s2); list.add(s3); list.add(s4); //遍历集合 for(int x = 0; x < list.size(); x++) { Student s = list.get(x); System.out.println(s.getName()+"‐‐‐"+s.getAge()); } } } 赵丽颖‐‐‐18 唐嫣‐‐‐20 景甜‐‐‐25 柳岩‐‐‐19

打印集合方法: 定义以指定格式打印集合的方法(ArrayList类型作为参数),使用{}扩起集合,使用@分隔每个元素。格式参照 {元素 @元素@元素}。

import java.util.ArrayList; public class test { public static void main(String[] args) { // 创建集合对象 ArrayList<String> list = new ArrayList<String>(); // 添加字符串到集合中 list.add("张三丰"); list.add("宋远桥"); list.add("张无忌"); list.add("殷梨亭"); // 调用方法 printArrayList(list); } public static void printArrayList(ArrayList<String> list) { // 拼接左括号 System.out.print("{"); // 遍历集合 for (int i = 0; i < list.size(); i++) { // 获取元素 String s = list.get(i); // 拼接@符号 if (i != list.size() - 1) { System.out.print(s + "@"); } else { // 拼接右括号 System.out.print(s + "}"); } } } } {张三丰@宋远桥@张无忌@殷梨亭}

获取集合方法: 定义获取所有偶数元素集合的方法(ArrayList类型作为返回值)

import java.util.ArrayList; import java.util.Random; public class test { public static void main(String[] args) { // 创建Random 对象 Random random = new Random(); // 创建ArrayList 对象 ArrayList<Integer> list = new ArrayList<>(); // 添加随机数到集合 for (int i = 0; i < 20; i++) { int r = random.nextInt(1000) + 1; list.add(r); } // 调用偶数集合的方法 ArrayList<Integer> arrayList = getArrayList(list); System.out.println(arrayList); } public static ArrayList<Integer> getArrayList(ArrayList<Integer> list) { // 创建小集合,来保存偶数 ArrayList<Integer> smallList = new ArrayList<>(); // 遍历list for (int i = 0; i < list.size(); i++) { // 获取元素 Integer num = list.get(i); // 判断为偶数,添加到小集合中 if (num % 2 == 0) { smallList.add(num); } } // 返回小集合 return smallList; } } [794, 762, 328, 12, 978, 974, 734, 964, 686]

4.LinkedList集合

4.1LinkedList简介

LinkedList是基于链表实现的,从源码可以看出是一个双向链表。除了当做链表使用外,它也可以被当作堆栈、队列或双端队列进行操作。不是线程安全的,继承 AbstractSequentialList实现 List、Deque、Cloneable、Serializable接口。

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable LinkedList继承AbstractSequentialList,AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数,这些接口都是随机访问List的。LinkedList 实现 List 接口,能对它进行队列操作。LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

4.2LinkedList的常用方法

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法:

public void addLast(E e) :将指定元素添加到此列表的结尾。public E getFirst() :返回此列表的第一个元素。public E getLast() :返回此列表的最后一个元素。public E removeFirst() :移除并返回此列表的第一个元素。public E removeLast() :移除并返回此列表的最后一个元素。public E pop() :从此列表所表示的堆栈处弹出一个元素。public void push(E e) :将元素推入此列表所表示的堆栈。public boolean isEmpty() :如果列表不包含元素,则返回true。 import java.util.LinkedList; public class test { public static void main(String[] args) { LinkedList<String> link = new LinkedList<String>(); //添加元素 link.addFirst("abc1"); link.addFirst("abc2"); link.addFirst("abc3"); System.out.println(link); // 获取元素 System.out.println(link.getFirst()); System.out.println(link.getLast()); // 删除元素 System.out.println(link.removeFirst()); System.out.println(link.removeLast()); while (!link.isEmpty()) { //判断集合是否为空 System.out.println(link.pop()); //弹出集合中的栈顶元素 } System.out.println(link); } } } [abc3, abc2, abc1] abc3 abc1 abc3 abc1 abc2 []

5.Vector集合

5.1Vector(向量类)的介绍

Vector 类可以实现可增长的对象数组。 java.util.vector提供了向量类(Vector) 以实现类似动态数组的功能。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。Vector 是同步的,可用于多线程。 对于预先不知或者不愿预先定义数组大小,并且需要频繁地进行查找,插入,删除工作的情况,可以考虑使用向量类

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable Vector 继承了AbstractList,实现了List接口;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。Vector实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。Vector 实现Serializable接口,支持序列化。

5.2Vector的使用

向量类提供了三种构造方法:

public vector() public vector(int initialcapacity,int capacityIncrement) public vector(int initialcapacity)

使用第一种方法系统会自动对向量进行管理,若使用后两种方法,则系统将根据参数,initialcapacity设定向量对象的容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时。系统会扩充向量对象存储容量。 参数capacityincrement给定了每次扩充的扩充值。当capacityincrement为0的时候,则每次扩充一倍,利用这个功能可以优化存储。

Vector类中提供了各种方法: 插入功能:

public final synchronized void addElement(Object obj) :将obj插入向量的尾部。obj可以是任何类型的对象。对同一个向量对象,亦可以在其中插入不同类的对象。但插入的应是对象而不是数值,所以插入数值时要注意将数组转换成相应的对象。

例如:要插入整数1时,不要直接调用v1.addElement(1),正确的方法为:

Vector v1 = new Vector(); Integer integer1 = new Integer(1); v1.addElement(integer1); public final synchronized void setElementAt(Object obj,int index) :将index处的对象设置成obj,原来的对象将被覆盖。public final synchronized void insertElementAt(Object obj,int index) :在index指定的位置插入obj,原来对象以及此后的对象依次往后顺延。

查询搜索功能:

public final int indexOf(Object obj) :从向量头开始搜索obj,返回所遇到的第一个obj对应的下标,若不存在此obj,返回-1.public final synchronized int indexOf(Object obj,int index) :从index所表示的下标处开始搜索obj.public final int lastindexOf(Object obj) :从向量尾部开始逆向搜索obj.public final synchornized int lastIndex(Object obj,int index) :从index所表示的下标处由尾至头逆向搜索obj.public final synchornized firstElement() :获取向量对象中的首个objpublic final synchornized Object lastElement() :获取向量对象的最后一个obj

其他功能:

public final int size(): 此方法用于获取向量元素的个数。它们返回值是向量中实际存在的元素个数,而非向量容量。可以调用方法capacity()来获取容量值。public final synchronized void setSize(int newsize): 此方法用来定义向量的大小,若向量对象现有成员个数已经超过了newsize的值,则超过部分的多余元素会丢失。

程序中定义Enumeration类的一个对象Enumeration是java.util中的一个接口类

public final synchronized Enumeration elements(): 此方法将向量对象对应到一个枚举类型。java.util包中的其他类中也都有这类方法,以便于用户获取对应的枚举类型。

在Enumeration中封装了有关枚举数据集合的方法。

方法 hasMoreElement() 来判断集合中是否还有其他元素。方法 nextElement() 来获取下一个元素

以下代码就是用hasMoreElement()和 nextElement()遍历Vector查询搜索功能:

import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Vector; public class TestVector { public void test01() { Vector<String> hs = new Vector<String>(); hs.add("aa"); hs.add("bb"); hs.add("aa"); hs.add("cc"); hs.add("aa"); hs.add("dd"); printSet2(hs); } public void printSet(List hs) { Iterator iterator = hs.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } public void printSet2(Vector<String> hs) { Enumeration<String> elements = hs.elements(); while (elements.hasMoreElements()) { System.out.println(elements.nextElement()); } } public static void main(String[] args) { new TestVector().test01(); } } aa bb aa cc aa dd

6.Stack

6.1Stack介绍

Stack 类表示后进先出(LIFO)的对象堆栈。继承自Vector,它通过五个操作对类 Vector 进行了扩展 ,允许将向量视为堆栈。它提供了通常的 push 和 pop 操作,以及取堆栈顶点的 peek 方法、测试堆栈是否为空的 empty 方法、在堆栈中查找项并确定到堆栈顶距离的 search 方法。 因为它继承自Vector,那么它的实现原理是以数组实现堆栈的。 如果要以链表方式实现堆栈可以使用LinkedList!

java.lang.Object 继承者 java.util.AbstractCollection<E> 继承者 java.util.AbstractList<E> 继承者 java.util.Vector<E> 继承者 java.util.Stack<E> 所有已实现的接口: Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess

并且从类 java.util.Vector 继承的字段

capacityIncrement, elementCount, elementData

从类 java.util.AbstractList 继承的字段

modCount

6.2Stack的方法和使用

Stack只有下面四个方法!由于Stack继承了Vector ,它也有Vector的API方法

push( num) :入栈pop() :栈顶元素出栈empty() :判定栈是否为空peek() :获取栈顶元素search(num) :判端元素num是否在栈中,如果在返回1,不在返回-1

(注意pop()和peek()的区别。pop()会弹出栈顶元素并返回栈顶的值,peek()只是获取栈顶的值,但是并不会把元素从栈顶弹出来)

import java.util.Stack; public class test { public static void main(String[] args) { Stack<Integer> stack = new Stack<>(); stack.push(2); System.out.println(stack.search(2)); //2在栈中,返回1 System.out.println(stack.search(0)); //0不在栈中,返回-1 System.out.println(stack.empty()); //栈不为空,返回false System.out.println(stack.peek()); //读取栈顶元素,返回2 System.out.println(stack.empty()); //栈不为空,返回false System.out.println(stack.pop()); //弹出栈顶元素,并返回栈顶元素2 System.out.println(stack.empty()); //栈空,返回true } } 1 -1 false 2 false 2 true

7.List集合总结

7.1总结概述

1、List继承了Collection,是有序的列表。 2、实现类有ArrayList、LinkedList、Vector、Stack等

ArrayList是基于数组实现的,是一个数组队列。可以动态的增加容量!LinkedList是基于链表实现的,是一个双向循环列表。可以被当做堆栈使用!Vector是基于数组实现的,是一个矢量队列,是线程安全的!Stack是基于数组实现的,是栈,它继承与Vector,特性是FILO(先进后出)!

7.2使用场景

在实际的应用中如果使用到队列,栈,链表,首先可以想到使用List。不同的场景下面使用不同的工具,效率才能更高!

当集合中对插入元素数据的速度要求不高,但是要求快速访问元素数据,则使用ArrayList!当集合中对访问元素数据速度不做要求不高,但是对插入和删除元素数据速度要求高的情况,则使用LinkedList!当集合中有多线程对集合元素进行操作时候,则使用Vector!但是现在BVector现在一般不再使用,如需在多线程下使用,可以用CopyOnWriteArrayList,在java.util.concurrent包下。当集合中有需求是希望后保存的数据先读取出来,则使用Stack!

7.3性能测试

/* * 性能测试,通过插入、随机读取和删除对ArrayList、LinkedList、Vector和Stack进行测试! * 结论:看LinkedList * 插入10万个元素,LinkedList所花时间最短:17 ms。 * 删除10万个元素,LinkedList所花时间最短: 9 ms。 * 遍历10万个元素,LinkedList所花时间最长:10255 ms;而ArrayList、Stack和Vector则相差不多,都只用了几秒。 * (1) 对于需要快速插入,删除元素,应该使用LinkedList。 * (2) 对于需要快速随机访问元素,应该使用ArrayList。 * */ public class ListTest { private static final int COUNT = 100000; //十万 private static ArrayList<Object> arrayList = new ArrayList<Object>(); private static LinkedList<Object> linkedList = new LinkedList<Object>(); private static Vector<Object> vector = new Vector<Object>(); private static Stack<Object> stack = new Stack<Object>(); public static void main(String[] args) { System.out.println("....开始测试插入元素.........."); // 插入元素测试 insertData(arrayList,"ArrayList") ; insertData(linkedList,"LinkedList") ; insertData(vector,"Vector") ; insertData(stack,"Stack") ; System.out.println("....开始测试读取元素.........."); // 随机读取元素测试 readAccessData(arrayList,"ArrayList") ; readAccessData(linkedList,"LinkedList") ; readAccessData(vector,"Vector") ; readAccessData(stack,"Stack") ; System.out.println("....开始测试删除元素.........."); // 随机读取元素测试 deleteData(arrayList,"ArrayList") ; deleteData(linkedList,"LinkedList") ; deleteData(vector,"Vector") ; deleteData(stack,"Stack") ; } /** * 指定的List 的子类中插入元素,并统计插入的时间 * @param list List 的子类 * @param name 子类的名称 */ private static void insertData(List<Object> list,String name) { long startTime = System.currentTimeMillis(); // 向list的位置0插入COUNT个数 for (int i=0; i<COUNT; i++){ list.add(0, i); } long endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println(name + " : 插入 "+COUNT+"元素, 使用的时间是 " + interval+" ms"); } /** * 指定的List 的子类中删除元素,并统计删除的时间 * @param list List 的子类 * @param name 子类的名称 */ private static void deleteData(List<Object> list,String name) { long startTime = System.currentTimeMillis(); // 删除list第一个位置元素 for (int i=0; i<COUNT; i++) list.remove(0); long endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println(name + " : 删除 "+COUNT+"元素, 使用的时间是 " + interval+" ms"); } /** * 指定的List 的子类中读取元素,并统计读取的时间 * @param list List 的子类 * @param name 子类的名称 */ private static void readAccessData(List<Object> list,String name) { long startTime = System.currentTimeMillis(); // 读取list元素 for (int i = 0; i < COUNT; i++) list.get(i); long endTime = System.currentTimeMillis(); long interval = endTime - startTime; System.out.println(name + " : 随机读取 "+COUNT+"元素, 使用的时间是 " + interval+" ms"); } } ....开始测试插入元素.......... ArrayList : 插入 100000元素, 使用的时间是 970 ms LinkedList : 插入 100000元素, 使用的时间是 17 ms Vector : 插入 100000元素, 使用的时间是 968 ms Stack : 插入 100000元素, 使用的时间是 888 ms ....开始测试读取元素.......... ArrayList : 随机读取 100000元素, 使用的时间是 6 ms LinkedList : 随机读取 100000元素, 使用的时间是 10255 ms Vector : 随机读取 100000元素, 使用的时间是 8 ms Stack : 随机读取 100000元素, 使用的时间是 4 ms ....开始测试删除元素.......... ArrayList : 删除 100000元素, 使用的时间是 1460 ms LinkedList : 删除 100000元素, 使用的时间是 9 ms Vector : 删除 100000元素, 使用的时间是 1472 ms Stack : 删除 100000元素, 使用的时间是 894 ms

欢迎阅读Java集合的相关博客: Java学习-Collection集合和Iterator迭代器 Java学习-详谈泛型 Java学习-详谈Map集合(HashMap、LinkedHashMap、TreeMap) Java学习-Collections工具类和Java常见的几种比较器 Java学习-详谈Set集合(HashSet、TreeSet、LinkedHashSet)

欢迎持续关注! 我的主页 个人博客站:jQueryZK Blog

最新回复(0)