一、什么是链表
定义:链式存储结构的特点是用一组任意的存储单元存储线性表的 数据元素,这组存储单元可以是连续的,也可以是不连续的。
物理存储结构如下:图中每一个节点均代表存储的数据,并且都包含该节点的下一个节点的位置信息。

二、链表的分类
我们可以把链表分为3类:
1)单链表 : 链表中的元素节点只能指向下一个节点或者空节点,节点直接不能相互指向。
2)双向链表 : 链表中的每个元素节点都有两个指向,一个指向上一个节点,另一个指向下一个节点。
3)循环链表 : 在单链表和双向链表的基础上,将两种链表的最后一个节点指向第一个节点的头部,从而形成循环。
我们本节只用java实现双向链表。
三、双向链表的实现
我们通过双向链表来手动实现LinkedList,具体看代码实现。
1 class LinkedList<E>{
2
3 private transient int size; //维护大小 transient序列化时该值不被序列化
4
5 private transient Node<E> first; //第一个节点
6
7 private transient Node<E> last; //最后一个节点
8
9 public LinkedList(){
10 this.size = 0; //初始化节点大小为0
11 }
12 //增
13 public boolean add(E element){ //在末尾添加节点
14 final Node<E> l = last; //最后一个节点
15 final Node<E> newNode = new Node(l, element, null); //构建需要添加的新节点
16 last = newNode; //因为是往末尾添加元素,不管任何情况,该节点必定为最后一个节点,所以last标记为最后一个节点
17 if(l == null) //如果最后一个节点为空,则说明该链表还没有节点,则创建的节点即为头节点也为尾节点
18 first = newNode;
19 else //不为空,则说明尾节点需要将下一个节点的位置指向刚新建的节点
20 l.next = newNode;
21 size ++;
22 return true;
23 }
24 public boolean add(int index, E element){ //在index位置添加element,将原来index位置的节点挂在新添加的节点上
25 checkIndex(index);
26 if(index == size) //如果添加位置和size一样大,说明是在末尾添加
27 add(element);
28 else //非末尾添加
29 linkNode(element, node(index));
30 return true;
31 }
32 //删
33 public boolean remove(E element){ //删除元素为element的节点
34 for(Node<E> x = first; x != null; x = x.next){ //依次循环,将链表中所有满足的element全部删除,这里有判空操作是因为传进来的elemet值不确定,为了避免发生空指针异常,需要做判空处理
35 if((element != null && element.equals(x.item))
36 || (element == null && x.item == element)){
37 unlink(x);
38 }
39 }
40 return true;
41 }
42 public E remove(int index){ //删除下标index位置的元素
43 checkIndex(index);
44 return unlink(node(index));
45 }
46 //改
47 public E set(int index, E element){ //将下标index位置的节点元素设置为element
48 checkIndex(index);
49 Node<E> node = node(index);
50 E oldValue = node.item;
51 node.item = element; //将原来的元素更改为新的元素,其它连接关系不变
52 return oldValue;
53 }
54 //查
55 public E get(int index){ //根据下标位置去获取节点元素
56 return node(index).item;
57 }
58 public int size(){ //获取链表长度大小
59 return this.size;
60 }
61
62 /**
63 * 断开链接,建立新的链接
64 * @param node 被删除的节点信息
65 * @return 返回被删除的节点元素
66 */
67 private E unlink(Node<E> node){
68 final Node<E> prev = node.prev; //保存node节点的上一个节点信息
69 final E item = node.item; //保存node节点的元素信息
70 final Node<E> next = node.next; //保存node节点的下一个节点信息
71 if(prev == null){ //如果node节点的上一个节点信息为空,则说明node节点为头节点,则需要将头节点指针first指向node节点的下一个节点
72 first = next;
73 } else { //node上一个节点存在
74 prev.next = next; //node的上一个节点的下一个节点指针指向node节点的下一个节点
75 node.prev = null; //断开node节点与上一个节点之间的连接关系
76 }
77 if(next == null) { //node的下一个节点为空,则说明node本身为尾节点,则需要更新last指向新的尾节点,即为node节点的上一个节点
78 last = prev;
79 } else { //node的下一个节点存在
80 next.prev = prev; //需要将node节点的下一个节点的上一个节点指向node节点的上一个节点
81 node.next = null; //断开node节点与下一个节点之间的连接关系
82 }
83 node.item = null; //经过上面两步操作之后,node节点的上下节点之间的关系已经断开,此时node变成了孤立的节点,再将该节点的元素置为null,方便gc进行回收
84 size --;
85 return item;
86 }
87
88 /**
89 * 在node节点前面插入新节点
90 * @param e 需要插入的元素
91 * @param node 被插入节点位置
92 */
93 private void linkNode(E e, Node<E> node){
94 final Node<E> pre = node.prev; //保存node节点的上一个节点信息
95 final Node<E> newNode = new Node(pre, e, node); //构建新的节点,同时该节点的上一个节点信息及下一个节点信息均已包含
96 node.prev = newNode; //将node节点上一个节点指向新建立的节点
97 if(pre == null) //node节点的上一个节点为空,则说明node原本为头节点,此时在node节点前插入一个新节点,则头指针需要变更
98 first = newNode;
99 else
100 pre.next = newNode; //将node上一个节点的下一个节点指向新建立的节点
101 size ++;
102 }
103
104 /**
105 * 找到index位置的节点,为了提升效率,这里先判断范围
106 * @param index 下标位置
107 * @return
108 */
109 private Node<E> node(int index){
110 //在前半段
111 if(index < (size >> 1)){
112 Node<E> node = first;
113 for(int i = 0; i < index; i++)
114 node = node.next;
115 return node;
116 }
117 //在后半段
118 Node<E> node = last;
119 for(int i = size - 1; i > index; i--)
120 node = node.prev;
121 return node;
122 }
123
124 //检查下标index是否越界
125 private void checkIndex(int index){
126 if(index < 0 || index > size)
127 throw new IndexOutOfBoundsException("index: " + index + ", size:" + size);
128 }
129
130 //节点定义
131 private static class Node<E>{
132 E item; //数据元素
133 Node<E> next; //下一个节点
134 Node<E> prev; //上一个节点
135
136 Node(Node<E> prev, E item, Node<E> next){
137 this.prev = prev;
138 this.item = item;
139 this.next = next;
140 }
141 }
142 }链表具备增删改查功能,通过上面代码,我们对双向链表也有了一个清楚的了解。具体也不多说,每一行都有注释。
















