extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList是集合中除ArrayList之外的另一个List实现,其本质是双向链表,同时实现Deque、Cloneable、Serializable接口。
LinkedList的结构较ArrayList相比更为复杂,可看做是由包含前后两个指针的结点(Node)链接而成。
成员
int size 链表元素个数,默认为0,即空链表
Node<E> first 指向头结点的指针
Node<E> last 指向尾结点的指针
结点类型
private static class Node<E> 结点类,定义为LinkedList的内部类
E item 存放结点中的数据
Node<E> next 指向当前结点的下一结点的指针
Node<E> prev 指向当前结点的上一结点的指针
tips: 头尾结点指针的类型也是Node,当链表为空时,链表内不含任何结点,此时 first == null && last == null;
当链表非空时,链表内至少有一个结点,此时 first.prev == null && first.item != null && last.next == null && last.item != null
构造函数
LinkedList() 构造一个空链表(first == last == null,size == 0)
LinkedList(Collection<? extends E> c) 构造一个链表,把集合c中的元素一次添加到这个链表中
常用方法
·查询
E getFirst()、E getLast() 返回链表的头、尾结点元素。若链表为空,则抛出异常throw new NoSuchElementException()
boolean contains(Object o) 如果链表中包含至少一个元素o,那么返回true
E get(int index) 返回指定位置的元素。如果给定的index小于0或大于链表size,则抛出IndexOutOfBoundsException
int indexOf(Object o) 、int lastIndexOf(Object o) 返回给定元素o在链表中第一次、最后一次出现的位置。若其不存在,则返回-1
·插入
void addFirst(E e)、void addLast(E e) 在链表的头部或尾部加入新元素,使其成为新的头结点或尾结点。后者的效果和 boolean add(E e) 相同。
boolean addAll(Collection<? extends E> c)、boolean addAll(int index, Collection<? extends E> c) 把集合c中的元素添加到链表中,参数index指示在原链表中,开始添加的位置而前者表示直接添加在链表末尾
void add(int index, E element) 在指定位置插入新元素element。首先进行参数检验,如果给定的index小于0或大于链表size,则抛出IndexOutOfBoundsException
·修改
E set(int index, E element) 修改index位置的元素为element,返回修改前的元素
·删除
E removeFirst()、E removeLast() 删除并返回头/尾元素
boolean remove(Object o) 删除链表中第一次出现的元素o,即下标最小的一个。如果链表中不包含o,则无任何变化。
遍历
List<String> l = new LinkedList<>();
for(int i = 0; i < 10000; i++){
l.add("第" + i + "个");
}
//for+get遍历
long startTime = System.currentTimeMillis();
for(int j = 0; j < 10000; j++){
l.get(j);
}
System.out.println("get遍历:" + (System.currentTimeMillis() - startTime));
//iterator遍历
startTime = System.currentTimeMillis();
Iterator<String> i = l.iterator();
while(i.hasNext()){
String s = i.next();
}
System.out.println("iterator遍历:" + (System.currentTimeMillis() - startTime));
//foreach遍历
startTime = System.currentTimeMillis();
for(String s : l){
;
}
System.out.println("foreach遍历:" + (System.currentTimeMillis() - startTime));
输出:
get遍历:104
iterator遍历:1
foreach遍历:1
显然,迭代器和foreach循环的性能比get方式好很多。
LinkedList与ArrayList的比较
List<String> myLinkedList = new LinkedList<>();
List<String> myArrayList = new ArrayList<>();
//依次插入50000条
long startTime = System.currentTimeMillis();
for(int i = 0; i < 50000; i++){
myLinkedList.add("第" + i + "项");
}
System.out.println("LinkedList依次插入:" + (System.currentTimeMillis() - startTime));
startTime = System.currentTimeMillis();
for(int i = 0; i < 50000; i++){
myArrayList.add("第" + i + "项");
}
System.out.println("ArrayList依次插入:" + (System.currentTimeMillis() - startTime));
//在头部插入50000条
myLinkedList.clear();
myArrayList.clear();
startTime = System.currentTimeMillis();
for(int i = 0; i < 50000; i++){
myLinkedList.add(0, "第" + i + "项");
}
System.out.println("LinkedList头部插入:" + (System.currentTimeMillis() - startTime));
startTime = System.currentTimeMillis();
for(int i = 0; i < 50000; i++){
myArrayList.add(0, "第" + i + "项");
}
System.out.println("ArrayList头部插入:" + (System.currentTimeMillis() - startTime));
输出:
LinkedList依次插入:15
ArrayList依次插入:10
LinkedList头部插入:7
ArrayList头部插入:252
·依次插入时,每次都在list的末尾添加元素,ArrayList的性能要优于LinkedList。ArrayList只需将数据添加到list的末尾,而LinkedList还需进行指针操作。二者的时间复杂度均为o(1)。
·“每次都在头部插入”是“在list中间任意位置插入”的一种极端情况。此时LinkedList的性能要远远优于ArrayList。LinkedList只需简单操作几个指针,时间复杂度为o(1),而ArrayList需要把插入位置之后的所有元素都向后移动一个位置,时间复杂度为o(n)。
以上这两个例子和结论同样适用于删除操作。
不难想象,对于随机访问,ArrayList可以通过下标直接访问指定位置的元素,而LinkedList则需要通过遍历计数的方式来获取第n个元素,时间复杂度分别为o(1)和o(n)。
总结一下,二者在末尾添加元素的开销是固定的,ArrayList稍稍优于LinkedList。但是对于ArrayList,其底层是数组,在末尾添加元素有可能会导致数组扩容、重新分配,这个操作的代价比较大。ArrayList支持高效快速的随机访问,LinkedList做中间元素的增删操作性能不错。此外,在空间方面,ArrayList会在list末尾预留一些空间,LinkedList没有容量的概念,但它的每个元素都需要较大的存储空间。