区别于 ArrayList 和 Vecter ,LinkedList 的数据结构是 双向链表,而不是数组。
链表也有单链表双链表和循环链表之分,它与 数组 的区别就是数据存储地址不是连续,需要通过存储下一个数据的引用而关联起来。我们来看看下面这张图:
单链表:
因为元素存储的地址不是连续的,一个 node 为一个元素。单链表里元素(node) 分为两部分,数据域(存储value部分) 和 指针域(存储引用对象部分) ,所以每一个 node 都会把下一个 元素(node) 的引用存放在 指针域 里,这样就可以把元素有序的串联起来。而当 指针域 里是 null 时,说明这个元素(node)是最后一个。
双向链表:
上面我们了解了单链表后,那双链表又是如何的呢?我们看下面这个图:
相比单链表,双链表多了一个指针域。多出来的这个指针域是用来存放上一个元素(node)的引用。这样我们看到不单可以找下一个元素(node)也可以找上一个元素(node),是双向的。如果元素(node)的 perv 是 null ,当前元素(node)就是第一个(first),next 是 null 说明就是最后一个(last)元素(node)。
LinkedList 采用链表的方式实现,每个元素(node) 存储地址都不是连续性,所以也不需要考虑空间开辟(初始化容量)问题。有空间就存入,只需要记录前后元素(node)的地址就能串联起来。也应为是链表,元素(node)存储地址不是连续的,不可以像数组那样通过下标随机去访问元素。只能依次遍历的去寻找元素(node)。
因为 JAVA 没有指针的概念,所以指针域里存入的是 元素(node)的引用对象。
接下来我们看看 LinkedList 是如何实现的。
/*** LinkedList 类部分代码 ***/
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// 元素(node)的数量
transient int size = 0;
/**
* 指向第一个节点指针
*/
transient Node<E> first;
/**
* 指向最后节点指针
*/
transient Node<E> last;
/**
* 参数为空的构造函数
*/
public LinkedList() {
}
/***
参数为集合类型的构造函数
***/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
/***
内部的 Node 类
***/
private static class Node<E> {
// 元素的值(数据域)
E item;
// 下一个元素(node):指针域
Node<E> next;
// 上一个元素(node):指针域
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}
我们看到上面又两个构造函数提供我们创建 LinkedList 集合,一个有参的一个无参的。first 变量的类型是一个 Node 这个类是 LinkedList 的一个内部类,我们看这个类也有个构造函数会初始化一个节点。
添加元素(node):
/*** 例子代码 1 ***/
/ 使用无参的构造函数创建 LinkedList 集合
LinkedList<String> objects = new LinkedList<>();
// 使用add 方法添加一个元素(node)
objects.add("1");
那 add 方法如何在链表添加元素的(node)?
/*** LinkedList 类部分代码 ***/
/*** 在末端添加一个元素(node) ***/
public boolean add(E e) {
// 调用 linkLast 方法在链接最后添加元素
linkLast(e);
return true;
}
/*** 链接最后添加元素 ***/
void linkLast(E e) {
// 把最后一个元素(last) 赋值给 l 变量
final Node<E> l = last;
// 新创建一个元素(node),l 赋值给 prev ,e 赋值给 item,null 赋值next。
final Node<E> newNode = new Node<>(l, e, null);
// newNode 赋值给 last 记录为最后一个元素(node)
last = newNode;
// l 等于 null,说明链表里没有元素(node),把 newNode 设置为第一个节点
if (l == null)
first = newNode;
else
// l 不等于 null 说明集合不是空的,则把 newNode 赋值给 l 的 next,能让上一个元素(node)指向下一个元素(node)
l.next = newNode;
// 集合元素 + 1
size++;
// 修改次数 + 1
modCount++;
}
/*** 指定位置添加一个元素(node); index:值得的位置; element: 添加的内容(值) ***/
public void add(int index, E element) {
// 检查 index 是否在集合范围
checkPositionIndex(index);
// index 等于 size 说明是在链接最后添加元素(node),所以调用 linkLast 方法
if (index == size)
linkLast(element);
else
// index 不等于 size 说明是插入一个元素(node),所以调用 linkBefore 方法
// node 方法传入 index 查找插入元素的位置,这里调用了下面的这个 node 方法
linkBefore(element, node(index));
}
/*** 获取 index 相应位置的元素(node),这里用于查找插入元素的位置 ***/
Node<E> node(int index) {
// 这里使用二分法的方式快速查找元素插入的位置的元素(node)
// index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
if (index < (size >> 1)) {
// 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
/*** 链接之前添加元素;e:添加的内容(值),succ:添加位置的元素(node) ***/
void linkBefore(E e, Node<E> succ) {
// 获取添加位置的元素(node)的前一个元素(node)
final Node<E> pred = succ.prev;
// 将原本添加位置元素(node)的前一个元素(node)和添加的内容(值)及添加位置的元素(node),生成一个新的元素(node)
final Node<E> newNode = new Node<>(pred, e, succ);
// 把新的元素(node),赋值给添加位置元素的前一个元素(node),完成添加位置元素(node)的后移操作
succ.prev = newNode;
// 判断添加元素(node)的前一个元素(node)是空的,就把新添加的元素(node)记录为第一个元素
if (pred == null)
first = newNode;
else
// 不是空的说明集合不是空的,就把添加位置的前一个元素(node),的下一个元素(node)设置为新的元素(node),完成前一个元素(node)对后一个元素的链接(node)
pred.next = newNode;
// 集合的元素总数 + 1
size++;
// 修改次数 + 1
modCount++;
}
概览图:
add(E e):
add(int index, E element) :
获取元素(node):
了解了元素(node)的添加后,我们再看看获取元素(node)内容。
/*** 例子代码2 ***/
LinkedList<String> objects = new LinkedList<>();
objects.add("1");
objects.add("2");
objects.add("3");
objects.get(1);
/*** LinkedList 类部分代码 ***/
/*** 指定获取元素(node); index:获取第几个元素(node) ***/;
public E get(int index) {
// 检查 index 是否在集合的范围内
checkElementIndex(index);
// 调用 node 方法获取相应元素(node),并.item 返回内容(值)
return node(index).item;
}
/*** 获取 index 相应位置的元素(node),和上面的 node 方法是同一个 ***/
Node<E> node(int index) {
// 这里使用二分法的方式快速查找元素插入的位置的元素(node)
// index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
if (index < (size >> 1)) {
// 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
因为链表存储元素(node)存储地址不是连续的,不可以像数组那样通过下标随机去访问元素。只能依次遍历的去寻找元素。
删除元素(node):
那删除元素(node)呢?删除元素有以下几个方法:
clear():
/*** 例子代码3 ***/
LinkedList<String> objects = new LinkedList<>();
objects.add("1");
objects.add("2");
objects.add("3");
objects.clear();
/*** LinkedList 类部分代码 ***/
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
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++;
}
通过遍历集合,把每个元素(node)之间的连接(引用),清除。只是清除了集合里的元素(node),集合对象再未被GC回收之前都可以重新给该集合添加新的元素(node):
- 把第一个元素(node) 赋值给 x 变量,通过给 x 里的数据域和指针域赋值为空,直到判断 x 为 null 说明已经删除了最后一个元素(node)
- 把记录第一个和最后一个节点的 first 和 last 变量也赋值为 null
- 集合元素总数赋值为 0 个
- 修改次数 + 1
这里为什么要把元素(node)之间的连接(引用)清除,而不是直接把集合赋值为 null?
我们看上面的注释就大概能知道,这个和GC回收算法有些关系,在一些旧版本,GC判断当前对象是否为垃圾对象并可以被回收,是看这个对象是否有被引用,被引用了说明这个对象还是在使用着不能被回收。所以即使直接把集合赋值为 null,但是元素(node)之间还是相互连接(引用)着,不利于GC的回收。
这里也是应该为了兼容旧版本而做的一些工作,方便GC进行更好的回收不用的对象提高性能。
remove():
/*** 例子代码4 ***/
LinkedList<String> objects = new LinkedList<>();
objects.add("1");
objects.add("2");
objects.add("3");
objects.remove();
/*** LinkedList 类部分代码 ***/
/*** 删除元素(只删除第一个元素(node)),并返回删除的内容(值) ***/
public E remove() {
// 删除第一个元素(node)
return removeFirst();
}
/*** 删除第一个元素,并返回删除的内容(值) ***/
public E removeFirst() {
// 获取当前第一个元素(node)赋值给 f
final Node<E> f = first;
// f 等于空,说明集合为空,则不能删除,并抛出异常
if (f == null)
throw new NoSuchElementException();
// 删除集合的首个元素(node);参数 f:删除的元素(node)
return unlinkFirst(f);
}
/*** 删除集合的首个元素(node);参数 f:删除的元素(node)***/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
// 获取删除元素(node)的内容(值),赋值给 element 变量
final E element = f.item;
// 获取删除元素(node)的下一个元素(node),赋值给 next 变量
final Node<E> next = f.next;
// 把删除元素(node)的数据域清空,赋值为 null
f.item = null;
// 把删除元素(node)的指向下一个元素(node)的指针域清空,赋值为 null
f.next = null; // help GC
// 把删除元素(node)的下一个元素(node)标记为第一个元素(node)
first = next;
// next 等于 null,说明删除的元素是集合里的最后一个元素,所以也把 last 赋值为 null
if (next == null)
last = null;
else
// 把删除的元素(node)与下一个元素(node)的之前引用清除
next.prev = null;
// 集合元素总数 - 1
size--;
// 修改次数 + 1
modCount++;
// 返回删除元素(node)的内容(值)
return element;
}
当我们直接调用 remove 方法的时候并不会像 clear 方法那样删除所有的元素(node),而是删除集合的首个元素,并返回删除元素(node)的内容(值)。
概览图:
remove(int index):
/*** 例子代码5 ***/
LinkedList<String> objects = new LinkedList<>();
objects.add("1");
objects.add("2");
objects.add("3");
objects.remove(1);
/*** LinkedList 类部分代码 ***/
/*** 指定删除第几个元素,并返回删除元素(node)的内容(值);index:集合的第几个元素 ***/
public E remove(int index) {
// 检查index 是否在集合范围内
checkElementIndex(index);
// 这里调用了两个方法,node方法通过 index 找到删除的元素(node),并传入 unlink 方法做删除操作
return unlink(node(index));
}
/*** 获取 index 相应位置的元素(node),和上面的 node 方法是同一个 ***/
Node<E> node(int index) {
// 这里使用二分法的方式快速查找元素插入的位置的元素(node)
// index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
if (index < (size >> 1)) {
// 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
/*** 删除指定的元素(node),返回删除元素(node)的内容(值);x:删除的元素(node)***/
E unlink(Node<E> x) {
// assert x != null;
// 获取删除元素(node)的内容(值),赋值给 element
final E element = x.item;
// 获取删除元素(node)的后一个元素(node),赋值给 next
final Node<E> next = x.next;
// 获取删除元素(node)的前一个元素(node),赋值给 prev
final Node<E> prev = x.prev;
// perv 等于 null 说明是集合的首个元素(node)
if (prev == null) {
// 就把要删除元素(node)的下一个元素(next)标记为首个元素(first)
first = next;
} else {
// prev 不等于 null,说明删除的不是集合的首个一个元素(node)
// 把删除元素(node)的上一个元素(node)与删除元素(node)的下一个元素做连接,将 next 赋值给 prev 的 next
prev.next = next;
// 把删除元素的指向前一个元素(node)的指针域清空,解除删除元素(node)与上一个元素(node)的连接
x.prev = null;
}
// next 等于 null ,说明删除的元素(node)是集合的最后一个
if (next == null) {
// 把删除元素(node)的上一个元素(node)标记为最后一个元素(node)
last = prev;
} else {
// next 不等于 null ,说明删除的不是集合的最后一个元素(node)
// 把删除元素(node)的下一个元素(node)与 删除元素(node)的上一个元素连接,将 prev 赋值给 next 的 prev
next.prev = prev;
// 把删除元素的指向后一个元素(node)的指针域清空,解除删除元素(node)与下一个元素(node)的连接
x.next = null;
}
// 清空删除元素(node)的数据域
x.item = null;
// 集合元素总数 - 1
size--;
// 修改次数 + 1
modCount++;
// 返回删除元素(node)的内容(值)
return element;
}
概览图:
remove(Object o):
/*** 例子代码6 ***/
LinkedList<Integer> objects = new LinkedList<>();
objects.add(1);
objects.add(2);
objects.add(3);
Integer removeItem = 3;
objects.remove(removeItem);
/*** LinkedList 类部分代码 ***/
/*** 根据内容(值)删除元素(node),成功返回 true,失败返回 false; o:删除元素(node)的内容(值)***/
public boolean remove(Object o) {
// 因为 LinkedList 的元素内容是可以为 null,所以这里判断要删除的内容是否是 null
if (o == null) {
// 通过遍历寻找内容(值)为 null 的元素(node)
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
// 将找到的元素(node),作为参数传入 unlink 方法进行删除
unlink(x);
// 完成删除,返回true
return true;
}
}
} else {
// 删除元素(node)的内容(值)不是 null
// 也是通过遍历去对比集合里元素(node)的内容(值),来找到相应的元素(node)
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
// 将找到的元素(node),作为参数传入 unlink 方法进行删除
unlink(x);
// 完成删除,返回true
return true;
}
}
}
// 如果没有找到相应的内容的元素(node),删除失败返回 false
return false;
}
/*** 删除指定的元素(node),返回删除元素(node)的内容(值);x:删除的元素(node);和上面的 unlink 方法是同一个 ***/
E unlink(Node<E> x) {
// assert x != null;
// 获取删除元素(node)的内容(值),赋值给 element
final E element = x.item;
// 获取删除元素(node)的后一个元素(node),赋值给 next
final Node<E> next = x.next;
// 获取删除元素(node)的前一个元素(node),赋值给 prev
final Node<E> prev = x.prev;
// perv 等于 null 说明是集合的首个元素(node)
if (prev == null) {
// 就把要删除元素(node)的下一个元素(next)标记为首个元素(first)
first = next;
} else {
// prev 不等于 null,说明删除的不是集合的首个一个元素(node)
// 把删除元素(node)的上一个元素(node)与删除元素(node)的下一个元素做连接,将 next 赋值给 prev 的 next
prev.next = next;
// 把删除元素的指向前一个元素(node)的指针域清空,解除删除元素(node)与上一个元素(node)的连接
x.prev = null;
}
// next 等于 null ,说明删除的元素(node)是集合的最后一个
if (next == null) {
// 把删除元素(node)的上一个元素(node)标记为最后一个元素(node)
last = prev;
} else {
// next 不等于 null ,说明删除的不是集合的最后一个元素(node)
// 把删除元素(node)的下一个元素(node)与 删除元素(node)的上一个元素连接,将 prev 赋值给 next 的 prev
next.prev = prev;
// 把删除元素的指向后一个元素(node)的指针域清空,解除删除元素(node)与下一个元素(node)的连接
x.next = null;
}
// 清空删除元素(node)的数据域
x.item = null;
// 集合元素总数 - 1
size--;
// 修改次数 + 1
modCount++;
// 返回删除元素(node)的内容(值)
return element;
}
我们可以看到不管传入的是什么类型的参数,都会调用它们重写的 equal 方法与元素(node)的内容(值)比较来找到对应的元素(node)。
修改元素(node):
/*** 例子代码7 ***/
LinkedList<String> objects = new LinkedList<>();
objects.add("1");
objects.add("2");
objects.add("3");
objects.set(1,"6");
/*** LinkedList 类部分代码 ***/
/*** 修改指定位置元素(node)的内容(值),并返回旧元素(node)的内容;index:指定元素(node)的位置,element:修改的内容 ***/
public E set(int index, E element) {
// 检查 index 是否在集合范围内
checkElementIndex(index);
// node方法通过 index 找到修改的元素(node)
Node<E> x = node(index);
// 修改前把旧元素(node)内容(值),赋值给 oldVal 变量存储,以便后续返回
E oldVal = x.item;
// 将新的内容(值),赋值给修改的元素(node)
x.item = element;
// 返回旧的内容
return oldVal;
}
/*** 获取 index 相应位置的元素(node),和上面的 node 方法是同一个 ***/
Node<E> node(int index) {
// 这里使用二分法的方式快速查找元素插入的位置的元素(node)
// index 小于 size 的一半就从集合的头开始找插入的位置; >>:相当于 size 除于 2
if (index < (size >> 1)) {
// 那第一个元素(node),这里因为不能像数组那样可以通过下标随机获取元素(node),所以要遍历去拿到插入位置的元素(node),并返回插入位置的元素(node)
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// index 大于 size 就从集合的尾开始找插入的位置,并返回插入位置的元素(node)
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
如有错误还请大家多多指出,让我们共同进步,谢谢。能帮助到你的话也请动动发财的小手帮忙点个赞,感谢。