与ArrayList同为List,LinkedList却展现出不同的特性。作为java.util下的另一重要容器,我们下面来探究一下LinkedList的源码实现及特性分析。
上篇文章讲述到,ArrayList用数组来存储数据,伴随数据量的变大,ArrayList动态扩充数组容量。
与之不同,LinkedList使用链表来存储数据,因此它在插入/删除数据方面有着天然的优势,而在读取指定位置的元素时,性能却不及ArrayList速度快。
LinkedList存储元素的变量如下:
transient Node<E> first;
存储数据的节点:Node类如下:
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; //后节点 } }
可以看出来,所有元素节点连在一起,构成了双向链表(prev指向前一个元素节点,next指向后一个元素节点)。所有数据,保存在Node的item中。相比于单向链表,双向链表提供了两种操作的方向,因此可以提供更快的遍历查找速度,更快的插入、删除元素速度(代价是提升了链表维护的难度,以及一点点存储引用的空间)。
下面看一下LinkedList内部对于双向链表的维护:
在此之前,需要知道的一点:LinkedList额外保存了first和last两个节点,分别指向首位和末尾,作为双向链表的两端入口。
内部维护链表的一系列函数如下(private、protected):
//用e构造Node,将数据插到首位 private void linkFirst(E e) { final Node<E> f = first; //取得首位节点node final Node<E> newNode = new Node<>(null, e, f); //构造节点,item为e,prev为null,next指向当前的首位f first = newNode; //将first指向newNode,以它作为新的首位 if (f == null) last = newNode; //若之前的首位f不存在,则新的首位节点newNode在作为first的同时,也是last else f.prev = newNode; //若之前的首位f存在,则将f的prev指向新的首位节点newNode size++; modCount++; } //用e构造Node,将数据插到末尾,原理类似 void linkLast(E e); //将节点插入到指定节点前 void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; //取得原先succ之前的节点pred,现在需要将newNode插在succ之前,pred之后 final Node<E> newNode = new Node<>(pred, e, succ); //构造内容为e的newNode,prev指向pred,next指向succ succ.prev = newNode; //将succ的prev指向newNode if (pred == null) first = newNode; //若pred为空,则将newNode置为first else pred.next = newNode; //将pred的next指向newNode size++; modCount++; } //将首位first节点从链表去掉 private E unlinkFirst(Node<E> f); //将末位last节点从链表去掉 private E unlinkLast(Node<E> l); //将任意位置的节点x从链表去掉 E unlink(Node<E> x) { //分别考虑prev与next为null和非null的情况,修复prev与next的指向 }
基于上述链表操作函数,LinkedList开放了如下接口(public)
public E getFirst() { //取得first内部的item,返回 } public E getLast() { //取得last内部的item返回 } public E removeFirst() { //调用unlinkFirst(Node<E> f), 将first从链表移除 } public E removeLast() { //调用unlinkLast(Node<E> l), 将last从链表移除 } public void addFirst(E e) { //将e插入到first之前 } public void addLast(E e) { //将e插入到last之后 } public boolean add(E e) { //调用linkLast(e),将e插入到last之后 } public boolean remove(Object o) { //从first开始遍历链表,找到o,移除节点 } public int indexOf(Object o) { //从first开始遍历节点,找到o,返回index } public int lastIndexOf(Object o) { //从last开始反向遍历链表,找到o,移除链表 } public boolean contains(Object o) { //从first开始遍历链表,找到o,返回true;或者找不到o,返回false } public boolean addAll(Collection<? extends E> c) { //1. 将集合c转成数组 //2. 遍历数组,对于每个元素,构造node,链在last后面 }
总结:LinkedList用双向链表维护元素,相比于ArrayList提供了快速的插入、移除数据操作的同时,也比单向链表的遍历查找速度更快。但是,相比于ArrayList,LinkedList的查找指定index元素效率低(ArrayList使用数组存储数据,可以直接依据索引读取)。
原文地址:https://www.cnblogs.com/xinxinBlog/p/9937651.html