Java数据结构篇六:List集合详解

前言

Java集合就像一个容器,可以存储任何类型的数据,也可以结合泛型来存储具体的类型对象。在程序运行时,Java集合可以动态的进行扩展,随着元素的增加而扩大。在Java中,集合类通常存在于java.util包中。

Java集合主要由2大体系构成,分别是Collection体系和Map体系,其中Collection和Map分别是2大体系中的顶层接口。

Collection主要有三个子接口,分别为List(列表)、Set(集)、Queue(队列)。其中,List、Queue中的元素有序可重复,而Set中的元素无序不可重复;在这里插入图片描述

在List集合中,我们常用到ArrayList和LinkedList这两个类。下面做学习记录。

一、ArrayList的定义:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

ArrayList继承了 抽象类AbstractList,实现了 List接口,并且实现了RandomAccess, Cloneable,
java.io.Serializable这三个标记接口,标记接口中是没有声明任何方法的,没有方法并不代表没有用,他们分别表示ArrayList具有可随机访问,可克隆及可序列化能力。

ArrayList的类的结构如下图所示
在这里插入图片描述

ArrayList常用方法

// ArrayList类对于元素的操作,基本体现在——增、删、查。常用的方法有:
A:添加功能
boolean add(E e):向集合中添加一个元素
void add(int index, E element):在指定位置添加元素
boolean addAll(Collection<? extends E> c):向集合中添加一个集合的元素。

B:删除功能
void clear():删除集合中的所有元素
E remove(int index):根据指定索引删除元素,并把删除的元素返回
boolean remove(Object o):从集合中删除指定的元素
boolean removeAll(Collection<?> c):从集合中删除一个指定的集合元素。

C:修改功能
E set(int index, E element):把指定索引位置的元素修改为指定的值,返回修改前的值。

D:获取功能
E get(int index):获取指定位置的元素
Iterator iterator():就是用来获取集合中每一个元素。

E:判断功能
boolean isEmpty():判断集合是否为空。
boolean contains(Object o):判断集合中是否存在指定的元素。
boolean containsAll(Collection<?> c):判断集合中是否存在指定的一个集合中的元素。

F:长度功能
int size():获取集合中的元素个数

G:把集合转换成数组
Object[] toArray():把集合变成数组。

ArrayList扩容机制

1.判断是不是第一次添加元素,若为第一次,则设置初始化大小为默认的值10,否则使用传入的参数(ArrayList 构造函数中如果没有设置初始化的容量大小,在没有添加有元素的时候,其初始化容量是0,只有当添加第一个元素的时候,才会初始化容量才会设置成10.)
2.如果元素的个数,大于其容量,则把其容量扩展为原来容量的1.5倍
3.实际扩容,使用了Arrays.copyof(elementData, newCapacity)
在这里插入图片描述
特性

  • ArrayList底层通过数组实现,读取速度非常快,随着元素的增加而动态扩容
  • ArrayList 频繁扩容将会导致性能下降,因为每次扩容都会复制原来的数组到创建的新数组。
  • ArrayList 插入时会移动数组元素,插入越靠前,移动的元素就越多,效率越差。
  • 有序集合,插入的元素可以为null
  • 线程不安全
  • ArrayList底层通过数组实现,随着元素的增加而动态扩容。而LinkedList底层通过链表来实现,随着元素的增加不断向链表的后端增加节点。

源码参考:https://mp.weixin.qq.com/s/er7Dqzu6o5l3RpEsml3DVQ
https://www.jianshu.com/p/63b01b6379fb

源码需注意一个点:transient修饰符是什么含义?
当我们序列化对象时,如果对象中某个属性不进行序列化操作,那么在该属性前添加transient修饰符即可实现;例如:

private transient Object[] elementData;

那么,为什么ArrayList不想对elementData属性进行序列化呢? elementData可是集合中保存元素的数组啊,如果不序列化elementData属性,那么在反序列化时候,岂不是丢失了原先的元素?ArrayList在添加元素时,可能会对elementData数组进行扩容操作,而扩容后的数组可能并没有全部保存元素。

例如:我们创建了new Object[10]数组对象,但是我们只向其中添加了1个元素,而剩余的9个位置并没有添加元素。当我们进行序列化时,并不会只序列化其中一个元素,而是将整个数组进行序列化操作,那些没有被元素填充的位置也进行了序列化操作,间接的浪费了磁盘的空间,以及程序的性能。所以,ArrayList才会在elementData属性前加上transient修饰符。

二、LinkedList定义

LinkedList是一个双向链表,每一个节点都拥有指向前后节点的引用。相比于ArrayList来说,LinkedList的随机访问效率更低。LinkedList的类的结构如下图所示:在这里插入图片描述
它继承AbstractSequentialList,实现了List, Deque, Cloneable, Serializable接口。

(1) LinkedList实现List,得到了List集合框架基础功能;
(2) LinkedList实现Deque,Deque是一个双向队列,也就是既可以先入先出,又可以先入后出,说简单些就是既可以在头部添加元素,也可以在尾部添加元素;
(3) LinkedList实现Cloneable,得到了clone()方法,可以实现克隆功能;
(4) LinkedList实现Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。

假如LinkedList中的元素为[“A”,“B”,“C”],其内部的结构如下图所示
在这里插入图片描述

List实现类的使用场景

  • ArrayList,底层采用数组实现,如果需要遍历集合元素,应该使用随机访问的方式,对于LinkedList集合应该采用迭代器的方式
  • 如果需要经常的插入。删除操作可以考虑使用LinkedList集合
  • 如果有多个线程需要同时访问List集合中的元素,开发者可以考虑使用Collections将集合包装成线程安全的集合(Collections.synchronizedList(new
    LinkedList<>()))。

总结:

1)LinkedList是一个功能很强大的类,可以被当作List集合,双端队列和栈来使用。
2)LinkedList使用链表保存集合中的元素,因此随机访问的性能较差,但是插入删除时性能高
3)LinkedList基于链表实现的,因此不存在容量不足的问题,所以没有扩容的方法
4)注意源码中的Node node(int index)方法。该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。

源码解析:https://www.jianshu.com/p/732b5294a985

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章