探索LinkedList底层实现
前言
老样子,还是先看注释!本来以为能收获点什么干货,结果注释与ArrayList的注释基本相同,有点尴尬...LinkedList的源码是基于JDK1.8。
开干
先上LinkedList类的注释,在深入到类中详细说明属性与方法。
阅读注释


它的意思是:通过索引来操作LinkedList的话,每次都需要从头或从尾开始一个一个遍历,直到遇到指定的索引。

也是非线程安全,所以在多线程环境下要在外部控制同步防止数据紊乱。

不多说了。

有一个点要注意下,获取迭代器后,不能在直接通过对象调用方法去修改结构,只能通过迭代器去调用,否则将会抛出异常。

数据结构
接下来将详细说明LinkedList类的属性与方法,在这之前,先通过一张图简单了解下它的数据结构。

很简单的一张图,每一个节点都保存了当前节点的信息,同时又关联了下一个节点与上一个节点。若要获取指定节点信息,那么只能通过这种关联关系来找到具体节点,这也奠定了它查找的速度相对于ArrayList来说要慢很多,但也是由于有这层关系,添加/删除的速度相对于ArrayList来说要更快,毕竟它只需要更改当前节点关联的上下节点关系即可,而ArrayList需要移动一波元素。
//支持克隆、序列化、双端队列
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
//链表的容量大小,意指包含多少个节点
transient int size = 0;
//链表的第一个节点,也就是首部节点
//链表中的每一个节点都保存着当前节点的信息、上一个节点的地址、下一个节点的地址
//首部节点的上一个节点指向null
transient Node<E> first;
//链表的最后一个节点,也就是尾部节点
//尾部节点的下一个节点指向null
//有了首部节点为什么还要有个尾部节点呢,个人理解是为了方便遍历计算
transient Node<E> last;
}
LinkedList的底层是通过链表实现的,并不需要提供成员属性来预先开辟内存空间以便后续的存储节点,所以它充分利用了内存空间,实现灵活的内存管理,不存在什么扩容机制。也正因为此,它并未提供有入参initCapacity的构造函数,在实现上看起来比ArrayList更简单。
构造函数
/**
* 空参数构造函数
*/
public LinkedList() {
}
/**
* 构造一个包含指定集合的链表
* 指定集合中的节点按照迭代器的顺序依次添加到链表的尾部
* @param c 指定集合
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
感觉上确实比ArrayList简单多了,不用考虑那么多,直接new一个对象即可。
方法说明
接下来按照类的声明顺序介绍方法,有必要的情况下结合例子进行说明。
简单方法
/**
* 添加指定节点到首部
* 指定节点作为首部节点,它的上一个节点指向null
* 若是空链表,当添加指定节点时首部节点与尾部节点指向同一个节点,即指定节点
* 若不是空链表,则将原先的首部节点的上一个节点指向指定节点
* @param e 指定节点
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
/**
* 添加指定节点到尾部
* 指定节点作为尾部节点,它的下一个节点指向null
* 若是空链表,当添加指定节点时首部节点与尾部节点指向同一个节点,即指定节点
* 若不是空链表,则将原先的尾部节点的下一个节点指向指定节点
* @param e 指定节点
*/
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++;
}
/**
* 添加指定节点到指定节点succ前
* 在添加节点时,更换对应上下节点的指向地址
* @param e 添加的指定节点
* @param 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++;
}
/**
* 删除首部节点
* @param f 首部节点
*/
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
/**
* 删除尾部节点
* @param l 尾部节点
*/
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
/**
* 删除指定节点
* @param x 指定节点
* @return 指定节点的信息
*/
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
/**
* 判断是否包含指定信息
* 一个节点一个节点的判断是否与指定信息相等
* @param o 指定信息
* @return 是否包含指定信息
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
}
/**
* 正向遍历的方式获取指定信息的位置
* 由于是链表的数据结构,每个节点只知道自己的信息与上下两个节点,所以每次查找时都只能从头/尾开始遍历
* 相当于是一个人一个人的问,直到问到自己想要的人
* 若未找到指定节点则返回-1
* @param 0 指定信息
* @return 指定信息的位置或-1
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
/**
* 获取节点个数
*/
public int size() {
return size;
}
/**
* 清空所有节点信息
*/
public void clear() {
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
/**
* 判断指定位置是否越界
* @param index 指定位置
* @return 是否越界
*/
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
/**
* 判断指定位置是否合法
* @param index 指定位置
* @return 是否合法
*/
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
/**
* 获取指定位置越界或不合法的信息
* @param index 指定位置
* @return 信息
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
/**
* 校验指定位置是否越界
* @param index 指定位置
*/
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 校验指定位置是否合法
* @param index 指定位置
*/
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 获取指定位置的节点
* 先判断指定位置处于0-mid-size的哪个区间,避免从头遍历消耗不必要的时间
*/
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
/**
* 反向遍历的方式获取指定信息的位置
* 由于是链表的数据结构,每个节点只知道自己的信息与上下两个节点,所以每次查找时都只能从头/尾开始遍历
* 相当于是一个人一个人的问,直到问到自己想要的人
* 若未找到指定节点则返回-1
* @param 0 指定信息
* @return 指定信息的位置或-1
*/
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
/**
* 存储每个节点的信息及指向上下节点的地址
*/
private static class Node<E> {
//当前节点的信息
E item;
//当前节点的下一个节点
Node<E> next;
//当前节点的上一个节点
Node<E> prev;
/**
* 初始化参数
* @param prev 当前节点的上一个节点
* @param element 当前节点的信息
* @param next 当前节点的下一个节点
*/
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
/**
* 调用该clone之前,该类要实现Cloneable,不然会抛出异常
* 浅拷贝与深拷贝,举个例子吧
* 比如A类中包含基本类型与B类,当调用A类clone方法后,两个A对象肯定是不一致,不然就不叫做拷贝了,不过这不是关键
* 若A1对象中的B对象与A2对象中的B对象指向同一个对象,则认为它是浅拷贝,认为B没有被拷贝新的一份
* 若两者指向不相等的话,则认为深拷贝,认为B重新拷贝了一份,不过这通常需要我们自定义代码,就像下面的方法一样
* @return 克隆后的新对象
*/
@SuppressWarnings("unchecked")
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
public Object clone() {
LinkedList<E> clone = superClone();
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
for (Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
/**
* 构造新数组,存放链表中的所有节点信息
* @return 包含所有节点信息的新数组
*/
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
/**
* 将链表中的所有节点信息放入到指定数组中并返回
*
* 一开始我也好奇为啥要在索引为size上设置个null呢?
* 看了注释加上自我的理解,若传入的新数组是个空数组的话,那么除了拷贝列表元素后剩余的所有空间的值都为null,此时在给索引为size的值设置成null似乎没有多大
* 意思;另外一种情况是若传入的新数组不是个空数组,那这个设置就有意义了,传入的新数组的某些元素会被列表元素覆盖,同时有个null,剩下的才是自己本身的数据,呈现这样子一种效果
*
* List<Integer> list = new ArrayList<>();
* list.add(11);
*
* Integer[] str = new Integer[]{1,2,3,4,5,6,7,8,9,10};
* Integer[] s1 = list.toArray(str);
*
* for (Integer s : s1) {
* System.out.println(s + ",");
* }
*
* 输出结果:11,null,3,4,5,6,7,8,9,10,
* 那么设置这个null的意义就在于能够确定列表中元素个数(长度),但有个前提就是调用者知道链表中的所有节点信息不存在null才有意义,目前我只有想到这一种情况下有用!
*
* @param a 指定数组
* @return 填充完链表所有节点信息的指定数组
*/
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
/**
* 自定义序列化
* 每个节点当中保存的上下节点的关联关系对于序列化来说可有可无,只要你按次序保存好每个节点的信息,反序列化后依然可以构造出这些关联关系
* 默认的序列化机制会将非静态与非瞬时(非transient修饰)写入流中
* @param s 输出流
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeInt(size);
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
/**
* 自定义反序列化
* @param s 输入流
*/
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
int size = s.readInt();
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
/**
* 获取分割迭代器
* 分割迭代器的方法作用都是类似的,不同的地方在于算法的实现上,所以不在做重复的分析,可参考另外一篇文章的详细介绍
* 附上文章地址:http://zlia.tech/2019/08/28/explain-arraylist-spliterator-sourcecode
*/
public Spliterator<E> spliterator() {
return new LLSpliterator<E>(this, -1, 0);
}
很多方法都没有过多的说明,是因为可能涉及到算法或者只要简单画一画就能理解了!
获取节点
/**
* 获取首部节点信息
* 若是空链表,则会抛出异常
* @return 首部节点信息
*/
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
/**
* 获取尾部节点信息
* 若是空链表,则会抛出异常
* @return 尾部节点信息
*/
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
/**
* 获取指定位置的信息
* 校验位置是否越界
* @param index 指定位置
* @return 指定位置的信息
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* 获取首部节点信息
* 若是空链表,则会返回null
* 该方法比getFirst更友好
* @return 首部节点信息
*/
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* 获取首部节点信息
* 若是空链表,则会抛出异常
* @return 首部节点信息
*/
public E element() {
return getFirst();
}
/**
* 获取首部节点信息
* 若是空链表,则会返回null
* 该方法比getFirst更友好
* @return 首部节点信息
*/
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
/**
* 获取尾部节点信息
* 若是空链表,则会返回null
* 该方法比getLast更友好
* @return 首部节点信息
*/
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
移除节点
/**
* 移除首部节点
* 若是空链表,则会抛出异常
* @return 首部节点的信息
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
/**
* 移除尾部节点
* 若是空链表,则会抛出异常
* @return 尾部节点的信息
*/
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* 移除指定信息
* 由于是链表的数据结构,每个节点只知道自己的信息与上下两个节点,所以每次查找时都只能从头开始遍历
* 相当于是一个人一个人的问,直到问到自己想要的人
* 若未找到指定节点则返回false
* @param o 指定信息
* @return 是否移除成功
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* 移除指定位置的节点
* @param index 指定位置
* @return 旧节点信息
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
/**
* 移除首部节点,相当于队列取出元素
* 若是空链表,则会返回null
* @return 旧节点信息
*/
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
/**
* 移除首部节点
* 若是空链表,则会抛出异常
* @return 首部节点的信息
*/
public E remove() {
return removeFirst();
}
/**
* 移除首部节点,相当于队列取出元素
* 若是空链表,则会返回null
* @return 旧节点信息
*/
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
/**
* 移除尾部节点,相当于队列取出元素
* 若是空链表,则会返回null
* @return 旧节点信息
*/
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
/**
* 正向遍历,删除指定节点信息
* @param o 指定节点信息
* @return 是否删除指定节点信息成功
*/
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
/**
* 反向遍历,删除指定节点信息
* @param o 指定节点信息
* @return 是否删除指定节点信息成功
*/
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
添加节点
/**
* 添加指定信息到首部
* @param e 指定信息
*/
public void addFirst(E e) {
linkFirst(e);
}
/**
* 添加指定信息到尾部
* @param e 指定信息
*/
public void addLast(E e) {
linkLast(e);
}
/**
* 添加指定信息到尾部
* @param e 指定节点信息
* @return 是否添加成功
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 添加指定集合信息到尾部
* @param c 指定集合信息
* @return 是否添加成功
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* 在指定位置添加指定集合信息
* @param index 指定位置
* @param c 指定集合信息
* @return 是否添加成功
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
/**
* 添加指定节点信息到指定位置上
* 在添加过程中只需要修改旧节点的上下节点关联关系即可,可以说效率提升了很多
* @param index 指定位置
* @param element 指定节点信息
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* 添加指定节点信息到尾部
* @param e 指定节点信息
* @return 是否添加成功
*/
public boolean offer(E e) {
return add(e);
}
/**
* 添加指定节点信息到首部
* @param e 指定节点信息
* @return 是否添加成功
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
/**
* 添加指定节点信息到尾部
* @param e 指定节点信息
* @return 是否添加成功
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
修改节点
/**
* 修改指定位置的节点信息
* @param index 指定位置
* @param element 指定节点信息
* @return 旧节点信息
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
迭代器
/**
* 获取包含指定位置开始到结尾之间的节点的正向迭代器
* @param index 指定位置
* @return 包含指定位置到结尾之间的节点的迭代器
*/
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
/**
* 列表迭代器,正向迭代
* 可获取上一个元素、下一个元素及位置
*/
private class ListItr implements ListIterator<E> {
//指向当前节点
private Node<E> lastReturned;
//指向上/下一个节点,随着调用方法的不同指向便不同
private Node<E> next;
//上/下一个节点的位置
private int nextIndex;
//每次调用迭代器中的方法时,都会判断是否调用了LinkedList的外部方法去修改结构列表,如此的操作通常会造成快速失败
private int expectedModCount = modCount;
/**
* 初始化参数,设置下一个节点及下一个节点的位置
* @param index 当前位置,调用迭代器中的方法时该位置对应的节点信息是第一个获取到的节点
*/
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
/**
* 判断是否有下一个节点
* @return 是否有下一个节点
*/
public boolean hasNext() {
return nextIndex < size;
}
/**
* 获取下一个节点的信息
* 初始化时的入参index对应的节点作为开始
* 在获取下一个节点信息前,会判断是否造成了快速失败
* 在获取完下一个节点后,将其指向赋值給lastReturned,同时修改next指向下一个节点
* @return 下一个节点的信息
*/
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
/**
* 判断是否有上一个节点
* @return 是否有上一个节点
*/
public boolean hasPrevious() {
return nextIndex > 0;
}
/**
* 获取上一个节点的信息
* 初始化时的入参index对应的节点的上一个节点作为开始
* 在获取下一个节点信息前,会判断是否造成了快速失败
* @return 上一个节点的信息
*/
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
/**
* 获取下一个节点的位置
* @return 下一个节点的位置
*/
public int nextIndex() {
return nextIndex;
}
/**
* 获取上一个节点的位置
* @return 上一个节点的位置
*/
public int previousIndex() {
return nextIndex - 1;
}
/**
* 移除当前节点,也就是lastReturned指向的节点
*/
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
/**
* 将当前节点的信息修改成指定信息
* @param e 指定信息
*/
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
/**
* 在下一个节点前添加指定节点
* 若下一个节点指向null,也就是说当前节点已经是尾部节点了,在将指定节点添加到尾部
* 若下一个节点指向不为null,则在下一个节点前添加指定节点
* @param e 指定节点信息
*/
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
/**
* 正向遍历链表的剩余节点,对每一个节点执行指定的动作
* 对于每个迭代器,除非剩余节点未遍历完毕,否则该方法只能执行一次,因为随着遍历nextIndex的次数在增加
* @param action 对每一个节点执行指定的动作
*/
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
/**
* 初始化时 modCount 与 expectedModCount 是相等的
* 但如果在遍历的过程修改数组结构的话,此时 modCount 会有所变化,导致两者不相等,故而抛出异常,也就是我们上面提到的fast-failed异常
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
/**
* 获取反向迭代器,尾部节点作为开始
* @return 以尾部节点作为开始的反向迭代器
*/
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
/**
* 列表迭代器,反向迭代
* 实际上都是采用ListItr类中的方法,只不过包装了一层
*/
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
/**
* 从反向的角度来看,判断是否有下一个节点
* @return 是否有下一个节点
*/
public boolean hasNext() {
return itr.hasPrevious();
}
/**
* 从反向的角度来看,获取下一个节点信息
* @return 下一个节点信息
*/
public E next() {
return itr.previous();
}
/**
* 从反向的角度来看,移除当前节点
*/
public void remove() {
itr.remove();
}
}
- 队列中的方法简单说明下:
peek、peekFirst、elements方法都是获取首部节点,但若是空链表的话,前两者不会抛出异常(返回null),最后一个会。
peekLast方法是获取尾部节点。
poll、pollFirst、remove、pop方法都是删除并首部节点,但若是空链表的话,前两者不会抛出异常(返回null),后两者会。
pollLast方法是删除并获取尾部节点。
offer与offerLast方法都是添加指定节点到尾部,没啥区别。
offerFirst、push方法是添加指定节点到首部,两者的区别在于有无返回值。
总结
-
LinkedList允许存放Null。
-
LinkedList内部通过链表实现,属于非线程安全。
-
LinkedList充分利用了内存空间,不存在扩容机制。
-
ArrayList具有
iterator与listIterator,虽然LinkedList也有这两个方法,但实际上这两个方法的内部实现都是调用的listIterator。 -
在遍历过程中不允许修改结构,否则会抛出错误。
-
LinkedList实现了队列。
重点关注
底层是通过链表实现,有序可重复 充分利用内存空间
浙公网安备 33010602011771号