LinkedList是线性存储结构中的链式存储,今天我们来简单的看一下他的add()方法的源码。
同时简单介绍一下链式数据结构。
什么是数据结构
简单地说,我们一般将数据存储到容器中,数据结构就是我们在容器中存储数据的方式。不同的存储方式会有不同的性能。比如在线性存储中,要实现快速的查询,顺序存储性能比较好;而实现快速的增加删除,链式存储的性能好。
常见的数据结构
- 数组
- 链表
- 栈
- 队列
- 树
- 图
- 字典树(这是一种高效的树形结构,但值得单独说明)
- 散列表(哈希表)
今天我们主要介绍的是链表存储的数据结构,我们先来图示说明。
线性链表
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。
优缺点
优点:删除还插入效率高
缺点:查询效率低
图片展示的是单向的链表结构,而linkedList是双向链表结构 ,下面我们看代码,然后下面的文字和图片是对代码位置的解释
package com.mainshi.mylist;
import java.util.*;
public class MyLinkedList<E> extends AbstractSequentialList<E> implements List<E> {
int size = 0;
//第一个节点
Node<E> first;
//最后一个节点
Node<E> last;
//空参构造方法
public MyLinkedList() { }
//
//阉割版静态内部类,执行每个节点存储的内容,见图1
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;
}
}
/**
* 这是一个查找对应位置元素的方法,因为是链式存储,
* 为了提高效率,这里会做一个判断,如果输入元素的位置为
*图4说明
* @param index
* @return
*/
Node<E> node(int index){
// >>右移运算符,相当于当前数字处以2
//1.判断当前输入的位置是否属于前半部分
if (index<size >>1){
// 1.1 如果是我们将头节点赋值给X
Node<E> x=first;
//1.2 在循环中进行到index位置的遍历
for (int i = 0; i <index ; i++)
//最后一次,将index的下一个节点只向自己
x=x.next;
return x;
}else{ //这里大于,则在后面往前找
Node<E> x=last;
for(int i=size-1;i>index;i--)
x=x.prev;
return x;
}
}
//这里我们用图3说明,在某个节点插入元素
private void linkBefore(E e, Node<E> 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++;
}
//这里我们用图2说明,在尾部插入元素
private void linkLast(E e) {
//最后一个节点赋值给l
final Node<E> l = last;
//将e封装到一个新的节点中,prev是last,next是null
final Node<E> newNode = new Node<>(l,e,null);
//将新的节点赋值给last;作为最后一个节点
last = newNode;
//如果插入前的最后节点是null,说明现在的集合为null
if (l == null)
//将新节点作为第一个节点
first = newNode;
else
//如果不是空,最后节点的next执行新加入的节点
l.next = newNode;
}
//这里是一个迭代器的类
private class ListItr implements ListIterator<E>{
//最后一次输入迭代出的节点
private Node<E> lastReturned;
//指针指向的下一个节点
private Node<E> next;
//下一个节点的index
private int nextIndex;
//操作计数器
private int expectedModCount = modCount;
//构造方法
ListItr(int index){
next=(index==size)?null:node(index);
nextIndex=index;
}
@Override
public void remove() {
//校验
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
}
@Override
public void set(E e) {
//校验信息,迭代器最后返回的对象
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
//将元素的信息装载到对应的位置,前后的指针都不变
lastReturned.item = e;
}
@Override
public void add(E e) {
checkForComodification();
lastReturned = null;
//如果添加的尾部没有元素了
if (next == null)
linkLast(e);
//如果添加的尾部还有元素
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
//校验参数的方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
类的简单介绍
在LinkedList类中,有两个内部类,他们分别是
- 私有静态内部类Node,这个类是一个节点,对应的就是链表结构中的每一个存储单元,具体描述见下图。
这里的一个节点就是一个Node类,在类中封装了两个私有的Node类,分别是前一个Node和后一个Node.这就是双向链表封装的类。
//阉割版静态内部类,执行每个节点存储的内容,见图1
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;
}
}
- 私有迭代器类ListItr执行的插入和增加的方法都是在这里进行。在这个类中有一个构造方法,传入参数的时候,会寻找到对应的位置。
//构造方法
ListItr(int index){
next=(index==size)?null:node(index);
nextIndex=index;
}
/**
* 这是一个查找对应位置元素的方法,因为是链式存储,
* 为了提高效率,这里会做一个判断,图1说明
* @param index
* @return
*/
Node<E> node(int index){
// >>右移运算符,相当于当前数字处以2
//1.判断当前输入的位置是否属于前半部分
if (index<size >>1){
// 1.1 如果是我们将头节点赋值给X
Node<E> x=first;
//1.2 在循环中进行到index位置的遍历
for (int i = 0; i <index ; i++)
//最后一次,将index的下一个节点只向自己
x=x.next;
return x;
}else{ //这里大于,则在后面往前找
Node<E> x=last;
for(int i=size-1;i>index;i--)
x=x.prev;
return x;
}
}
直接我们用图片展示
- 最后是我们本次展示的add()方法。
先看add方法,我们有代码注释说明,在add()方法中,我们调用了 linkLast(e)方法将元素插入末尾,或者 linkBefore(e, next);将元素插入指定位置位置。
@Override
public void add(E e) {
checkForComodification();
lastReturned = null;
//如果添加的尾部没有元素了
if (next == null)
linkLast(e);
//如果添加的尾部还有元素
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
//在尾部插入元素
private void linkLast(E e) {
//最后一个节点赋值给l
final Node<E> l = last;
//将e封装到一个新的节点中,prev是last,next是null
final Node<E> newNode = new Node<>(l,e,null);
//将新的节点赋值给last;作为最后一个节点
last = newNode;
//如果插入前的最后节点是null,说明现在的集合为null
if (l == null)
//将新节点作为第一个节点
first = newNode;
else
//如果不是空,最后节点的next执行新加入的节点
l.next = newNode;
}
//在某个节点插入元素
private void linkBefore(E e, Node<E> 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++;
}
- linkLast(E e)方法图示说明,请结合代码
- linkBefore(E e, Node succ)方法图示说明,请结合代码
这里我们只介绍了add()方法,其他方法没有演示,修改方法都是指向的变化,而查找方法实际是需要通过判断长度,可以稍微提高效率,找到对应的位置。