除了数组外,链表也是非常常用的数据结构,java里面的LinkedList就是实现了链表这一数据结构,而且它的底层是一个双向链表,下面是LinkedList的继承和实现结构
Queue
LinkedList实现了List和Deque这两个接口,List这个接口在上一篇Array中解释过了,Queue队列除了Collection的基础操作之外,还额外提供了插入、提取和检查操作,这些操作每一个都以两种方式存在,一种是失败时抛出异常,另一种是失败时返回一个值(null或者false)。
Queue的全部方法如下
其实Queue的方法都是成双成对的,虽然功能一样,但是出现异常时的处理不一样
功能 | 失败时抛出异常 | 失败时返回特殊值 |
---|---|---|
添加元素 | add | offer(失败时返回false) |
删除元素 | remove | poll(失败时返回null) |
获取队列头部元素 | element | peek(失败时返回null) |
Deque
Deque是用于构造支持两端元素插入和删除的线性集,这个接口定义了访问操作双端队列两端的方法,每一种方法也是以两种方式存在,同样是一种是错误时抛出异常,另一种是错误时返回特殊值
操作队列头部的函数是xxxFirst(),操作队列尾部的函数是xxxLast(),具体的功能看方法名就可以知道了
除此之外,Deque还提供了push和pop方法
LinkedList
链表中的基本元素由一个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;
}
}
看一下LinkedList的属性,既然是双端队列,那肯定是要存储第一个元素节点以及最后一个元素系欸D节点
transient Node<E> first; // 指向第一个元素
transient Node<E> last; // 指向最后一个元素
添加元素
向头部添加一个节点
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first; // 另f为第一个节点
final Node<E> newNode = new Node<>(null, e, f); // 新建一个节点,next指向第一个节点
first = newNode; // first重置为新建的那个节点
if (f == null) // 原本的链表有可能是空的,这时候新建的节点既是第一个节点也是最后一个节点
last = newNode;
else
f.prev = newNode; // 修改原来的第一个节点的前置节点为新建的节点
size++; // size变大
modCount++;
}
这个就是实现了一个链表的基本添加操作了,向最后一个节点添加元素也是同理
删除元素
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
// 主体删除逻辑在私有方法unlinkLast中,传入最后一个节点就是删除最后一个元素
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
// 获取待删除元素的前一个节点
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev; // 此时链表中最后一个元素变为prev
// 可能链表在删除这个元素后就没有元素了,这时first和last都要置为null
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
这里的删除操作仍然是基本的链表删除操作,删除第一个节点也是同理
检索
java的LinkedList实现于普通的链表有所不同,因为实现了List接口,所以它也是支持检索的,可以通过索引来获取节点
public E get(int index) {
// 检查索引是否合法
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// 判断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;
}
}