上篇文章 东小西:源码学习「集合之ArrayList」已经了解的ArrayList的源码,下面了解一下List中常见的另一种数据结构:LinkedList。
LinkedList 和 ArrayList 的数据结构是不一样,ArrayList 底层是数组的结构,而 LinkedList 的底层是双向链表的结构。
从 LinkedList 继承关系看出,它不仅实现了 List 接口,还实现了 Queue 和 Deque 接口,所以它可以作为List使用、作为双向队列使用,也可以当做栈使用。
源码分析
本文基于JDK1.8的LinkedList。
主要属性
// 元素个数
transient int size = 0;
// 头节点
transient Node<E> first;
// 尾节点
transient Node<E> last;
// 内部类,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;
}
}
可以看出,属性都被 transient 关键字修饰,说明 LinkedList 内部实现了自己的一套序列化逻辑,提高内存利用率,减少 null 值得空间占用。
构造方法
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
// 将集合c数据插入到链表中
addAll(c);
}
LinkedList 是链表,不用设置初始容量,所以构造方法看起来比ArrayList简单。
下面主要看一下节点插入和删除操作。
插入操作
插入节点的方式有,头插方式、尾插方式和中间插入的方式。
public boolean add(E e) {
// 默认在列表尾部插入元素
linkLast(e);
return true;
}
// 在索引index位置插入元素
public void add(int index, E element) {
// 检查index合法性
checkPositionIndex(index);
if (index == size)
// 插到尾部
linkLast(element);
else
// 检索到index个节点后,插入元素
linkBefore(element, node(index));
}
头插方式
// 头部插入元素
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
// 原首节点 f
final Node<E> f = first;
// 创建新节点,新节点的后置节点是原首节点 f
final Node<E> newNode = new Node<>(null, e, f);
// 将新节点设置为头节点
first = newNode;
// 判断是不是插入的第一个元素
if (f == null)
// 尾节点也是新节点
last = newNode;
else
// 原首节点的前置节点设置为新节点
f.prev = newNode;
// 元素个数+1
size++;
// 操作次数+1,支持fail-fast
modCount++;
}
尾插方式
// 尾部插入元素
public void addLast(E e) {
linkLast(e);
}
void linkLast(E e) {
// 原尾节点 l
final Node<E> l = last;
// 创建新节点, 新节点的前置节点是原尾节点 l
final Node<E> newNode = new Node<>(l, e, null);
// 将新节点设置为尾节点
last = newNode;
// 判断是不是插入的第一个元素
if (l == null)
// 头节点也是新节点
first = newNode;
else
// 原尾节点 l 的后置节点设置为新节点
l.next = newNode;
size++;
modCount++;
}
任意位置插入方式
//查找到待插入元素的位置
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;
}
}
// 链表中间插入节点
void linkBefore(E e, Node<E> succ) {
// 从node()方法可以看出succ是待插入节点的后置节点
// 待插入位置的前置节点
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++;
}
删除操作
头删方式
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(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
// 把新的头节点的前置节点设置为null
next.prev = null;
size--;
modCount++;
return element;
}
尾删方式
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
private E unlinkLast(Node<E> l) {
final E element = l.item;
// l 的前置节点
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
// 将l 的前置节点设置为尾节点
last = prev;
if (prev == null)
first = null;
else
// 将l 的前置节点的next置空
prev.next = null;
size--;
modCount++;
return element;
}
任意位置删除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
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;
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
/**
* prev:x的前置节点
* next:x的后置节点
*/
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//说明 x是头节点
if (prev == null) {
// 新首节点设置为 x后置节点
first = next;
} else {
// x前置节点的后置节点为x的后置节点
prev.next = next;
x.prev = null;
}
//说明 x是尾节点
if (next == null) {
// 新尾节点设置为 x的前置节点
last = prev;
} else {
// x的后置节点的前置节点设置为x的前置节点
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
从上面了解了 LinkedList 的 add 和 remove 的原理,其它方法,如get、队列相关的 poll、pop、push等操作都很简单。
手撕代码实现LinkedList
下面仿照源码,自己实现了 LinkedList 的 add、remove、get方法。
import java.util.NoSuchElementException;
/**
* 简单实现add/remove/get方法
*/
public class MyLinkedList<E> {
public int size;
// 首节点
private Node<E> first;
// 尾节点
private Node<E> last;
// 构造方法
public MyLinkedList(){
}
/**
* add 操作
*/
public void add(E e){
addLast(e);
}
public void add(int index,E e){
checkElementIndex(index);
addBefore(e,node(index));
}
/**
* 尾插方式
* @param e
*/
public void addLast(E e){
Node<E> l = last;
Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if(null == l){
first = newNode;
} else {
l.next = newNode;
}
size++;
}
/**
* 头插方式
*/
public void addFirst(E e){
Node<E> f = first;
Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if(null == f){
last = newNode;
} else {
f.prev = newNode;
}
size++;
}
/**
* 任何位置插入
*/
public void addBefore(E e, Node<E> indexNode){
Node<E> pred = indexNode.prev;
Node<E> newNode = new Node<E>(pred,e,indexNode);
indexNode.prev = newNode;
if(pred == null){
first = newNode;
} else {
pred.next = newNode;
}
size++;
}
public Node<E> node(int index){
checkElementIndex(index);
// 对半折查找
if( index <= (size/2) ){
Node<E> node = first;
for(int i=0; i<index; i++){
node = node.next;
}
return node;
} else {
Node<E> node = last;
for(int i = size-1; i>index; i--){
node = node.prev;
}
return node;
}
}
public E remove(int index){
checkElementIndex(index);
Node<E> node = node(index);
E e = node.value;
Node<E> next = node.next;
Node<E> prev = node.prev;
if(prev == null){
removeFirst();
} else if(next == null){
removeLast();
} else {
prev.next = next;
next.prev = prev;
}
size--;
return e;
}
public E removeFirst(){
Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
E e = f.value;
Node<E> next = f.next;
first = next;
if(next == null)
last = null;
else
next.prev = null;
size--;
return e;
}
public E removeLast(){
Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
E e = l.value;
Node<E> prev = l.prev;
last = prev;
if(prev == null)
first = null;
else
prev.next = null;
size--;
return e;
}
public E get(int index){
checkElementIndex(index);
Node<E> node = node(index);
return node.value;
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private static class Node<E>{
E value;
Node<E> prev;
Node<E> next;
public Node(Node prev, E value, Node next){
this.value = value;
this.prev = prev;
this.next = next;
}
}
}
class MyLinkedListTest{
public static void main(String[] args) {
MyLinkedList<String> myLinkedList = new MyLinkedList<>();
myLinkedList.add("1");
myLinkedList.add("2");
myLinkedList.add("3");
print(myLinkedList);
myLinkedList.add(0,"0");
print(myLinkedList);
myLinkedList.addFirst("-1");
print(myLinkedList);
myLinkedList.addLast("5");
print(myLinkedList);
myLinkedList.add(5,"4");
print(myLinkedList);
System.out.println("remove: " + myLinkedList.removeFirst());
print(myLinkedList);
System.out.println("remove: " + myLinkedList.removeLast());
print(myLinkedList);
System.out.println("remove: " + myLinkedList.remove(3));
print(myLinkedList);
myLinkedList.get(10);
}
private static void print(MyLinkedList<String> myLinkedList){
for(int i=0 ; i<myLinkedList.size; i++){
System.out.print(myLinkedList.get(i)+" ");
}
System.out.println();
}
}
日志输出
1 2 3
0 1 2 3
-1 0 1 2 3
-1 0 1 2 3 5
-1 0 1 2 3 4 5
remove: -1
0 1 2 3 4 5
remove: 5
0 1 2 3 4
remove: 3
0 1 2 4
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 10, Size: 4
at com.yx.linkedlist.MyLinkedList.checkElementIndex(MyLinkedList.java:159)
at com.yx.linkedlist.MyLinkedList.get(MyLinkedList.java:148)
at com.yx.linkedlist.MyLinkedListTest.main(MyLinkedList.java:202)