之前介绍了集合Set,在这章中我们主要介绍Collection的其中一种实现方式,List。
List主要分为3类,ArrayList, LinkedList和Vector
List是一个有序的集合,和set不同的是,List允许存储项的值为空,也允许存储相等值的存储项,还举了e1.equal(e2)的例子。 List是继承于Collection接口,除了Collection通用的方法以外,扩展了部分只属于List的方法。
List比Collection主要多了几个add(…)方法和remove(…)方法的重载,还有几个index(…), get(…)方法。而AbstractList也只是实现了List接口部分的方法,和AbstractCollection是一个思路。
ArrayList是一个数组实现的列表,由于数据是存入数组中的,所以它的特点也和数组一样,查询很快,但是中间部分的插入和删除很慢。
Vector 可实现自动增长的对象数组。 java.util.vector提供了向量类(Vector)以实现类似动态数组的功能。 创建了一个向量类的对象后,可以往其中随意插入不同类的对象,即不需顾及类型也不需预先选定向量的容量,并可以方便地进行查找。
对于预先不知或者不愿预先定义数组大小,并且需要频繁地进行查找,插入,删除工作的情况,可以考虑使用向量类。
向量类提供了三种构造方法:
public vector() public vector(int initialcapacity,int capacityIncrement) public vector(int initialcapacity)
Vector大致与ArrayList一致,但是有以下几点区别
1.初始化 默认无参构造方法 Vector会初始化一个长度为10的数组,ArrayList在具体调用时再创建数组。 比较之下,ArrayList延迟化加载更节省空间 2.扩容 (grow()) Vector当增量为0时,扩充为原来大小的2倍,当增量大于0时,扩充为原来大小加增量 ArrayList扩充算法:原来数组大小加原来数组的一半 3.线程安全 Vector是线程安全的,ArrayList是非线程安全的 Vector的线程安全包括其内部类如迭代器实现类ListItr
其实最大的区别就是线程安全性,当然如果我们想创建一个线程安全的ArrayList,可以调用Collections.synchronizedList(),得到静态内部类SynchronizedList,利用同步代码块处理ArrayList。 这种方式得到的线程安全的ArrayList和Vector有什么区别?很简单,一是同步代码块和同步方法的区别,剩下的是ArrayList和Vector除了线程安全性的其他区别;还有一点不能忽略,前者的迭代器的同步实现需要使用者手动控制
LinkedList也是List接口的实现类,与ArrayList不同之处是采用的存储结构不同,ArrayList的数据结构为线性表,而LinkedList数据结构是链表。链表数据结构的特点是每个元素分配的空间不必连续、插入和删除元素时速度非常快、但访问元素的速度较慢。
LinkedList是一个双向链表, 当数据量很大或者操作很频繁的情况下,添加和删除元素时具有比ArrayList更好的性能。但在元素的查询和修改方面要弱于ArrayList。LinkedList类每个结点用内部类Node表示,LinkedList通过first和last引用分别指向链表的第一个和最后一个元素,当链表为空时,first和last都为NULL值。
LinkedList 是一个继承于AbstractSequentialList的双向链表。 LinkedList 可以被当作堆栈、队列或双端队列进行操作。 LinkedList 实现 List 接口,所以能对它进行队列操作。 LinkedList 实现 Deque 接口,能将LinkedList当作双端队列使用。 LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。 LinkedList 实现java.io.Serializable接口,所以LinkedList支持序列化,能通过序列化去传输。 LinkedList 是非同步的。
Node节点一共有三个属性:item代表节点值,prev代表节点的前一个节点,next代表节点的后一个节点。每个结点都有一个前驱和后继结点,并且在 LinkedList中也定义了两个变量分别指向链表中的第一个和最后一个结点。
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ transient int size = 0; /** * 指向链表中的第一个节点 * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; /** * 指向链表中的最后一个节点 * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last; ... private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }LinkedList提供了多个添加元素的方法: ● 在链表尾部添加一个元素,如果成功,返回true,否则返回false boolean add(E e) ● 在链表头部插入一个元素 void addFirst(E e) ● 在链表尾部添加一个元素 addLast(E e) ● 在指定位置插入一个元素 void add(int index, E element)
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } /** * Links e as last element. */ void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; } /** * Inserts element e before non-null Node succ. */ void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }LinkedList提供了多个删除元素的方法: ● 从当前链表中移除指定的元素 boolean remove(Object o) ● 从当前链表中移除指定位置的元素 E remove(int index) ● 从当前链表中移除第一个元素 E removeFirst() ● 从当前链表中移除最后一个元素 E removeLast() ● 从当前链表中移除第一个元素,同removeLast()相同 E remove()
通过上面对ArrayList和LinkedList的分析,可以理解List的3个特性 1.是按顺序查找 2.允许存储项为空 3.允许多个存储项的值相等 可以知其然知其所以然
然后对比LinkedList和ArrayList的实现方式不同,可以在不同的场景下使用不同的List ArrayList是由数组实现的,方便查找,返回数组下标对应的值即可,适用于多查找的场景 LinkedList由链表实现,插入和删除方便,适用于多次数据替换的场景