LinkedList底层用双向链表实现的存储。
特点:查询效率低,增删效率高,线程不安全。
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。
所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
LinkedList的存储结构图
每个节点都应该有3部分内容:
class Node {
Node prev; // 前一个节点
Object element; // 本节点保存的数据
Node next; // 后一个节点
}
我们查看LinkedList的源码,可以看到里面包含了双向链表的相关代码:
对LinkedList的操作,其实就是对双链表的操作,下面我们来分析LinkedList的底层实现:
- 节点类:
节点类很简单,element存放业务数据,previous与next分别存放前后节点的信息(在数据结构中我们通常称之为前后节点的指针)。
public class LinkedList {
// 链表节点
private static class Node {
// 前一个节点
Node prev;
// 本节点保存的数据
Object element;
// 后一个节点
Node next;
// 构造方法
Node(Node prev, Object element, Node next) {
this.element = element;
this.next = next;
this.prev = prev;
}
}
}
- 私有属性:
LinkedList中之定义了三个属性:
public class LinkedList {
// 链表首节点
private Node firstNode;
// 链表尾节点
private Node lastNode;
// 节点个数(元素个数)
private int size;
// ...省略链表节点类...
}
- 构造方法:
LinkedList提供了两个构造方法。
第一个构造方法不接受参数,第二个构造方法接收一个Collection参数c,然后通过addAll方法将c中的元素全部添加到链表中。
public class LinkedList {
// ...省略私有属性...
// 无参构造方法
public LinkedList() {}
// 有参构造方法
public LinkedList(Collection c) {
addAll(c); // 此方法实现省略
}
// ...省略链表节点类...
}
- 添加元素方法:
在链表的尾部追加一个节点,但是要注意判断当前链表是否为空链表。
public class LinkedList {
// ...省略私有属性和构造方法...
// 追加元素方法
public void add(Object element) {
// 1.把元素内容包装为一个节点对象
Node newNode = new Node(null, element, null);
// 2.判断链表中尾节点是否存在,如果不存在则证明链表中还没有节点
if (lastNode == null) {
// 2.1设置首尾节点都为newNode
firstNode = newNode;
lastNode = newNode;
}
// 3.链表中存在节点,那么把尾节点和newNode链接起来
else {
// 3.1把尾节点和newNode链接起来
lastNode.next = newNode;
newNode.prev = lastNode;
// 3.2更新尾节点
lastNode = newNode;
}
// 4.链表中节点递增
size++;
}
// ...省略链表节点类...
}
- 获取元素方法:
获取链表中指定位置的元素,首先判断索引是否合法,然后通过循环来找到对应索引位置的节点,从而拿到节点中存放的内容。
public class LinkedList {
// ...省略私有属性和构造方法...
// 根据索引获取元素
public Object get(int index) {
// 1.检查索引是否合法
checkElementIndex(index);
// 2.获取元素节点的内容
return node(index).element;
}
// 检查索引是否合法
private void checkElementIndex(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("索引越界异常");
}
// 根据索引获取节点
private Node node(int index) {
// 1.如果索引在前半区,则从前往后开始找
if (index < (size >> 1)) {
// 1.1准备开始从首节点开始查找
Node currentNode = firstNode;
// 1.2从前往后遍历节点,一直到index所在位置
for (int i = 0; i < index; i++)
// 1.3找到index索引对应的节点
currentNode = currentNode.next;
// 1.4返回找到的节点
return currentNode;
}
// 2.如果索引在后半区,从后往前开始查找
else {
// 2.1准备开始从尾节点开始查找
Node currentNode = lastNode;
// 2.2从后往前遍历节点,一直到index所在位置
for (int i = size - 1; i > index; i--)
// 2.3找到index索引对应的节点
currentNode = currentNode.prev;
// 2.4返回找到的节点
return currentNode;
}
}
// ...省略链表节点类...
}
- 修改元素方法:
修改链表中指定位置的元素,首先判断索引是否合法,然后通过循环来找到对应索引位置的节点,最后再修改节点中存放的内容。
public class LinkedList {
// ...省略私有属性和构造方法...
// 根据索引修改元素
public Object set(int index, Object element) {
// 1.检查索引是否合法
checkElementIndex(index);
// 2.根据索引获取元素节点
Node node = node(index);
// 3.获取节点以前存放的内容
Object oldVal = node.element;
// 4.修改节点中存放的内容
node.element = element;
// 5.返回节点修改之前的内容
return oldVal;
}
// ...省略链表节点类...
}
- 插入元素方法:
在链表中指定位置插入元素,首先判断索引是否合法,然后通过循环来找到对应索引位置的节点,最后执行插入操作。
public class LinkedList {
// ...省略私有属性和构造方法...
// 插入元素方法
public void add(int index, Object element) {
// 1.检查索引是否合法(index和size可以相同)
isPositionIndex(index); // 此处判断索引是否合法与checkElementIndex不同
// 2.节点插入操作
// 2.1如果index和size相等,那么直接就是节点追加
if (index == size) { // 此操作还包含空链表的情况
add(element);
}
// 2.2如果index和size不相等,则进行插入操作
else {
// 2.3根据索引获取链表中的节点
Node node = node(index);
// 2.4进行插入操作
linkBefore(element, node);
// 2.5链表中节点递增
size++;
}
}
// 插入节点操作
public void linkBefore(Object element, Node targetNode) {
// 1.获取目标节点的上一个节点
Node preNode = targetNode.prev;
// 2.把元素内容包装为一个节点对象
Node newNode = new Node(preNode, element, targetNode);
// 3.把targetNode节点的prev指向newNode
targetNode.prev = newNode;
// 4.把preNode的next指向newNode
// 4.1如果preNode存在,正常处理
if (preNode != null)
preNode.next = newNode;
// 4.2如果preNode不存在,则证明newNode为首节点
else
firstNode = newNode;
}
// 检查索引是否合法
private void isPositionIndex(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("索引越界异常");
}
// ...省略链表节点类...
}
- 移除元素方法:
根据索引来移除元素,首先先判断索引是否合法,然后通过循环来找到对应索引位置的节点,最后删除该节点。
根据元素来移除元素,首先找到该元素在数组中所在的索引,如果没有找到则证明移除失败,如果找到则删除该节点。
public class LinkedList {
// ...省略私有属性和构造方法...
// 移除元素方法
public Object remove(int index) {
// 1.检查索引是否合法
checkElementIndex(index);
// 2.找到需要移除的节点对象
Node node = node(index);
// 3.获取被删除节点中存放的内容
Object value = node.element;
// 4.执行移除节点操作
unlink(node);
// 5.返回被删除节点的内容
return value;
}
// 根据内容移除元素方法
public boolean remove(Object o) {
// 因为集合中可以存放null,所以判断之前需判断元素是否为null
if(o == null) {
for(Node node = firstNode; node != null; node = node.next) {
// 判断元素是否为null
if(node.element == null) {
// 执行移除节点操作
unlink(node);
return true;
}
}
}
else {
for(Node node = firstNode; node != null; node = node.next) {
// 判断元素是否和传入的元素相同
if(node.element.equals(o)) {
// 执行移除节点操作
unlink(node);
return true;
}
}
}
return false;
}
// 移除节点操作
public void unlink(Node node) {
// 1.获取被移除节点的上一个节点和下一个节点
Node prev = node.prev;
Node next = node.next;
// 2.判断prev是否为空
if(prev == null)
// 2.1prev为null,则证明删除node后,next就为链表的首节点
firstNode = next;
else
// 2.2prev不为null,则把prev.next设置为next
prev.next = next;
// 3.判断next是否为空
if(next == null)
// 3.1next为null,则证明删除node后,prev就为链表的尾节点
lastNode = prev;
else
// 3.2next不为null,则把next.prev设置为prev
next.prev = prev;
// 4.释放node对象的引用关系
node.next = null;
node.prev = null;
node.element = null;
// 5.链表中节点递减
size--;
}
// ...省略链表节点类...
}
ps:如需最新的免费文档资料和教学视频,请添加QQ群(627407545)领取。