聊聊集合那些事 之 List集合

tech2022-08-16  136

集合Collection

    我们看下边这张集合的关系图,橙色的是接口,蓝色的是实现类。下边我们将对这些接口和实现类进行一一介绍:

集合的概述

    集合和数组一样,都是java提供的一种容器。集合不能存储基本数据类型数据,只能用来存储对象,但是它可以用来存储多个不同类型的数据,下面我们看下集合和数组的区别。

集合和数组的区别:

数组:长度固定不可变,同一数组只能存储同一类型的元素。集合:长度可变,同一集合可以存储不同类型的元素。

    在实际开发中,数组一般存储基本数据类型,集合一般存储对象,因为集合不能存储基本数据类型的元素,集合存储基本数据类型的数据需要使用基本数据类型的包装类。

 

集合框架

    集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map,

    Collection 是单列集合的根接口,用于存储一系列符合某种规则的元素,他有三个重要的子接口,分别是 List接口、Set接口、Queue接口(不常见)。其中 List集合的特点是 元素有序、可重复,Set集合的特点是 元素无序、不可重复。

    Map 是双列集合的根接口,用于存储一系列的键值对(key-value),一组键值对是 Map集合的一个元素,通过键可以找到对应的值。其中值得注意的是:Map集合中的 键不可重复,值可重复,一个键只对应一个值。

 

Collection常用方法

    Collection是单列集合的根接口,所以它定义了一些Set接口和List接口的常用方法,Collection根接口的方法可以操作所有的单列集合。

public boolean add(E e): 将指定的对象添加到集合中。

public boolean remove(E e): 从集合中删除指定的元素。

public boolean contains(E e): 判断当前集合中是否包含指定的对象元素。

public boolean isEmpty(): 判断当前集合是否为空,为空返回true。

public int size(): 返回集合中元素的个数。

public Object[] toArray(): 把集合中的元素,存储到数组中。

public void clear() : 清空集合中所有的元素。

Iterator<E> iterator(): 返回 Collection集合的迭代器

 

Iterator迭代器

    Iterator接口被Collection接口所实现,迭代器的作用就是用来遍历集合中的元素的!

public E next():返回迭代的下一个元素。

public boolean hasNext():如果仍有元素可以迭代,则返回 true。

代码演示

    下面我们通过一段代码,来演示以下Collection集合的常用方法及Iterator迭代器的使用:

public static void main(String[] args) { // 创建一个ArrayList集合对象,这里使用了多态,父类的引用指向子类的实现。忘记的同学可以去复习下面向对象三大特性 Collection collection = new ArrayList(); ​ collection.add("小明"); // 添加 String类型元素 collection.add(11); // 添加 int类型元素,这里会被转换成Integer包装类型,因为集合中不能存储基本数据类型 collection.add(12.2); // 添加 Double包装类型元素 collection.add(true); // 添加 Boolean包装类型元素 ​ System.out.println("判断集合中元素是否为空,为空返回true:" + collection.isEmpty()); // false System.out.println("判断集合中是否包含指定元素:" + collection.contains(11)); // true System.out.println("返回集合中元素的个数:" + collection.size()); // 4 ​ collection.remove(11); // 删除集合中指定元素 ​ System.out.println("删除元素11后,集合中元素个数:" + collection.size()); // 3 ​ // 以下进行迭代器是使用 // 获取 collection集合对象的迭代器 Iterator iterator = collection.iterator(); // 使用while循环遍历集合中元素 while (iterator.hasNext()) { // hashNext() 判断是否存在下一个元素,存在返回true,不存在返回false // next() 获取下一个元素,并将指针指向下一个元素 System.out.println(iterator.next()); // 打印结果:小明 12.2 true } ​ // 将collection集合转换成数组,这里的toArray()方法,在内部使用类copy方法,所以它操作的是collection集合的副本 Object[] objects = collection.toArray(); // 遍历数组中元素 for (Object obj : objects) { System.out.println(obj); // 打印结果:小明 12.2 true } ​ // 清空集合中所有元素 collection.clear(); ​ System.out.println("清空集合中元素后,查看集合是否为空:" + collection.isEmpty()); // true }

 

 

List集合

List介绍

    List接口继承Collection接口,它是单列集合的一个重要分支,我们习惯性的将实现类List接口的对象叫做 List集合。

    List集合的特点是:元素可重复、元素有序;这和数组的特点是很像的,而它的实现类ArrayList,底层正是维护了一个数组。下面 ArrayList 源码解读会给大家详细分析。

    List有以上特点,是因为集合中的所有元素都是以一种线性方式存储的,在程序中可以通过索引来访问集合中的指定元素;并且集合中元素的存储顺序与取出顺序相同,这些都是数组的特点,再次强调,因为 ArrayList的底层正是维护了一个 final Object类型的数组。

List接口特点总结:

1、List集合中的元素时存取有序的。比如存入顺序是1、2、3、4,那么存储元素的顺序也是按照1、2、3、4来完成存储的。

2、List集合中的元素可重复。可以通过 equals方法来比较是否是重复元素。

3、List集合带索引,通过索引就可以精确的操作List集合中的元素了,它的索引与数组一个道理。

 

List接口的常用方法

    List接口作为 Collection接口的子接口,他不仅继承了Collection接口中的所有方法,还有自己的一套方法,主要是一些根据索引来操作集合中元素的方法:

public void add(int index, E element): (添加元素)将指定的元素,添加到该集合中的指定位置上。

public E remove(int index): (删除元素)移除列表中指定位置的元素, 返回的是被移除的元素。

public E set(int index, E element): (更新元素)用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

public E get(int index): (查找元素)返回集合中指定位置的元素。

 

ArrayList 集合

ArrayList介绍

 java.util.ArrayList集合的数据存储结构是数组结构。元素增删慢,查找快。由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

    许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

    java.util.ArrayList 是大小可变的数组的实现,它是List集合的一个实现类, ArrayList 中可以不断添加元素,其大小也自动增长。

 

ArrayList常用方法

public boolean add(E element) : 将元素添加到集合的尾部

public boolean add(int index,E element) : 将元素添加到集合的指定位置

public E remove(int index) : 删除集合中指定位置的元素

public boolean remove(Object o) : 删除集合中首次出现的指定的元素

public void clear() : 清空集合中的所有元素

public boolean contains(Object o) : 判断集合中是否包含指定的元素,存在返回true

public E get(int index) : 返回集合中指定位置的元素

public boolean isEmpty() : 判断集合中是否存在元素,如果没有元素返回true

public int indexOf(Object o) :返回集合中首次出现指定元素的索引位置,如果不存在返回-1

public int lastIndexOf(Object o) : 返回集合中最后一次出现指定元素的索引位置,不存在返回-1

public E set(int index,E element) : 用指定元素替换指定位置上的元素,返回被替换的元素值

public Object[] toArray() : 将集合转换成数组,操作的是集合的副本,不影响集合本身。

public void trimToSize() : 将此 ArrayList 实例的容量调整为列表的当前大小。应用程序可以使用此操作来最小化 ArrayList 实例的存储量。

public int size(): 返回集合中元素的数量

 

ArraList源码解读

下面我们来阅读一段 ArrayList的源码,来看看 ArrayList集合是怎么实现数组长度大小可变的。

// 默认初始容量为10 private static final int DEFAULT_CAPACITY = 10; ​ // ArrayList底层维护了一个 final Object类型的空数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 存储ArrayList元素的数组缓冲区,ArrayList的容量是这个数组缓冲区的长度。 // 任何空的ArrayList集合,也就是使用无参构造方法创建的ArrayList集合,它的初始容量都是 DEFAULT_CAPACITY,也就是10. transient Object[] elementData; // 通过构造方法,构建一个初始容量为10的ArrayList空集合。 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } ​ /** * 添加元素,我们可以通过源码来了解ArrayList是怎么实现数组长度大小可变的。 * ensureCapacityInternal:用来保证集合的容量,防止数组越界的发生 * elementData 数组缓冲区,它的长度是ArrayList集合中元素的个数 */ public boolean add(E e) { ensureCapacityInternal(size + 1); // size是当前ArrayList集合中元素个数,将元素个数+1,保证集合的容量 elementData[size++] = e; // 将元素插入到集合的尾部, return true; } // minCapacity是集合中存储元素的最小容量,所以每次像集合中插入元素时,都要+1 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 比较 初始容量10 与 集合中实际元素个数,返回他们之中的最大值。以保证集合可以成功扩容并添加元素 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } 大家要养成阅读源码的习惯,这在企业开发中是很重要的,可能开始阅读起来比较困难,只要坚持,你会发现源码其实也没那么神秘。

ArrayList 集合代码演示

public static void testMyArrayList() { // 构建一个初始值为10的空ArrayList集合(去看源码) List list = new ArrayList(); System.out.println("查看集合中是否存在元素:" + list.isEmpty()); System.out.println("查看集合中元素个数:" + list.size()); ​ // 向集合中添加元素 list.add("aa"); // 添加String类型的元素 list.add(22); // 添加Integer类型的元素 list.add('a'); // 添加Char类型的元素 list.add(12.22); // 添加Double类型的元素 ​ /* * 根据索引获取集合中的元素 * 集合中元素的存储顺序与添加顺序是相同的,可以通过索引访问指定位置的元素,一旦索引越界就 *会报异常。 */ System.out.println("索引值为0的元素" + list.get(0)); // aa System.out.println("索引值为1的元素" + list.get(1)); // 22 System.out.println("索引值为2的元素" + list.get(2)); // a System.out.println("索引值为3的元素" + list.get(3)); // 12.22 // java.lang.IndexOutOfBoundsException 索引越界异常 // System.out.println("索引值为4的元素" + list.get(4)); ​ // 向集合中指定位置添加元素 list.add(2, "aa"); list.add(0, 22); list.add(3, 2); /* * 向集合最后一个位置添加元素,之前最后一个位置的元素应该是12.22,但是我们向最后一个位置 *添加元素后,这个位置的值被'a'取代了,12.22会向后移动一位 * 所以集合中的元素排序应该是 最后一位是12.22,倒数第二位是‘a’,我们来查看一下对不对 */ list.add(list.size() - 1, 'a'); // 向当前集合最后一个位置添加元素a // 22,因为我们向索引0位置添加了元素22,所以返回值是22 System.out.println("索引值为0的元素" + list.get(0)); System.out.println("当前集合中元素个数:" + list.size()); // 8 System.out.println("索引值为6的元素(最后一位元素)" + list.get(7)); // 12.22 // 返回值是 a 结果证明我们的猜想是对的 System.out.println("索引值为5的元素(倒数第二位元素)" + list.get(6)); // a ​ // 遍历list集合中的元素 for (Object obj : list) { System.out.println(obj); // 当前集合中元素的顺序是 :22 aa 22 2 aa a a 12.22 } ​ ​ /* * remove(int index)、remove(Object o) * 当前集合中元素的顺序是 :22 aa 22 2 aa a a 12.22 * 值得注意的是,remove方法有两个重载方法,一个是移除指定索引位置的元素,一个是移除第一 *次出现在集合中的指定元素. * 当要被移除的元素是整数时,list集合会调用按索引删除的remove方法。 * 当我们要删除集合中的整数时,需要先使用找到要删除元素的索引,然后按索引删除可。 * 以上是包子自己发现的一个有趣的结论,可能不正确,知道内部原理的大佬欢迎留言指正,包子 *会及时进行修改 * * */ // 删除元素中第一个出现的元素22 int i = list.indexOf(22);// 元素22第一次出现的索引位置 // 返回值是被删除的元素值,22 System.out.println("根据索引删除第一个出现的元素2:" + list.remove(i)); // 删除元素中最后一个出现的元素22 int j = list.lastIndexOf(22);// 元素22最后一次出现的索引位置 // 返回值是被删除的元素值,22 System.out.println("根据索引删除第一个出现的元素2:" + list.remove(j)); ​ ​ // 替换指定位置的元素值 System.out.println("集合中索引为3的位置的元素是:" + list.get(3)); System.out.println("将集合中索引为3的位置的元素替换成‘哈哈’,返回值是被替换的元素值:" + list.set(3, "哈哈")); System.out.println("替换后集合中索引值为3的元素是:" + list.get(3)); ​ // 查看集合中是否包含元素 "哈哈",存在返回true,否则返回false System.out.println("集合中是否存在元素‘哈哈’?" + list.contains("哈哈")); //true ​ // 清空集合中所有元素 list.clear(); System.out.println("被清空后集合中元素数量:" + list.size()); // 0 }

 

LinkedList集合

java.util.LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。

在学习LinkedList集合之前,我们先看一下什么是双向链表?

链表结构是数据结构的一种,和数组一样,它是一种物理结构的数据结构,数据结构分为两大类:物理结构、逻辑结构。

物理结构:就是按照这种结构真实的在内存中存储数据的结构,物理结构的数据结构只有 链表和数组两种,这是最基本的数据结构。

逻辑结构: 是一种假想的不是真实存在的,但是能够更好的帮助我们完成对数据的操作的结构,就像我们的业务逻辑一样。

关于数据结构,包子会单独写一篇文章来说明,这里就简单一提,我们还是先来说说 LinkedList集合。

LinkedList介绍

上图中已经很详细的描述类双向链表的结构以及增删查操作,LinkedList集合就是使用双向链表实现的。

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,LinkedList集合除了继承了List集合的一些对元素的操作方法外,LinkedList提供了大量首尾操作的方法。还这些方法我们作为了解即可:

public boolean add(E e): 向集合的末尾添加元素

public boolean offer(E e): 将指定元素添加到集合的末尾

public boolean add(int index,E e):向集合中指定位置插入指定元素

public void addFirst(E e): 将指定元素插入集合的开头

public void addLast(E e):将指定元素添加到集合的末尾。

public void push(E e): 将元素压入栈中,也就是添加到集合的第一个位置

public E pop(): 将元素从栈中弹出,也就是移除并返回集合中第一个元素。

public boolean offerFirst(E e):向集合开头插入指定元素

public boolean offerLast(E e): 向集合末尾插入指定元素

public E poll(): 获取并移除集合中第一个元素

public E pollFirst(): 获取并移除集合中第一个元素,如果集合为空返回null

public E pollLast(): 获取并移除集合中最后一个元素,如果集合为空返回null

public E remove(): 移除并返回集合的第一个元素

public E remove(int index): 移除并返回列表中指定索引位置的元素

public boolean remove(Object o): 移除集合中首次出现的指定元素,如果存在返回true

public E removeFirst(): 移除并返回集合中第一个元素

public E removeLast(): 移除并返回集合中最后一个元素

public E set(int index,E element): 替换集合中指定位置的元素

public ListIterator<E> listIterator(int index): 返回集合的迭代器(顺序迭代)

public Iterator<E> descendingIterator(): 返回集合的逆序迭代器(逆序迭代)

public E element(): 获取但不移除集合的第一个元素,列表为空抛出异常

public E peek(): 获取但不移除集合的第一个元素,如果列表为空,则返回null

public E peekFirst(): 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。

public E peekLast(): 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null

public E get(int index): 返回集合中指定索引位置的元素,超出索引范围抛出异常

public E getFirst(): 返回集合中第一个元素,集合为空抛出异常

public E getLast(): 返回集合中最后一个元素,集合为空抛出异常

public int indexOf(Object o): 返回首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1

public int lastIndexOf(Object o): 返回最后一次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1

public boolean contains(Object o): 如果集合中存在指定元素,则返回true

public int size(): 返回集合中元素的总个数

public boolean isEmpty():如果列表不包含元素,则返回true。

    LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用(了解即可)。代码的实现同学们自己去实现吧,还是很简单的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

最新回复(0)