一 什么是双链表
双链表是一种基本数据结构。每个数据结构都包含三部分:前指针,当前数据值,后指针。前指针用来指向前一个元素即前驱,后指针用来后一个元素即后继。这样就形成了链条。
其节点结构图如下:
其数据结构图如下:
如果first的前指针指向last的元素且last的后指针指向first元素则形成双向循环链表。在JDK7以前LinkedList是双向循环链表,JDK7则改成了非循环双链表。所以在浏览些老的文章说其为循环双链表也不为过。
本文的主角LinkedList就是双链表结构
内部声明了Node类型的first和last属性,默认值为null
first 节点也是头节点, last节点也是尾节点
transient int size = 0; // 链表的容量
transient Node<E> first; // 指向第一个节点
transient Node<E> last; // 指向最后一个节点
每一个链表都是一个Node节点,由三个元素组成:
private static class Node<E> {
// Node节点的元素值
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;
}
}
二、LinkedList的特点
- 查到列表中的元素,需要从头至尾的遍历列表
- 没有实现同步
- 每个元素都是一个节点,它保留对下一个和前一个节点的引用
- 插入和删除效率较高,查询效率较差
- 维护了插入的顺序,可以按插入顺序遍历
- 虽然没有实现同步但可以调用Collections.synchronizedList实现同步
List list = Collections.synchronizedList(LinkedList(...));
面试题:ArrayList LinkedList Vector 的异同?
相同:都实现了List的接口,存储数据特点相同–存储有序的可重复的数据.
不同:ArrayList:作为List接口的主要实现类,线程不安全,执行效率较高;LinkedList:底层使用双向链表存储;对于频繁的插入、删除操作使用频率较高Vector作为List的接口的古老实现类,线程安全,效率较低.
三、重要方法解析
1 add(E e)
默认尾部添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
// 真正添加节点的操作
void linkLast(E e) {
final Node<E> l = last;//获取尾部元素
// 生成一个Node节点,并将此节点的前指针指向last元素
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
// 如果l = null,代表的是第一个节点,所以这个节点即是头节点
// 又是尾节点
if (l == null)
first = newNode;//设置新的首元素为新节点
else
// 如果不是的话,那么就让该节点的next 指向新的节点
l.next = newNode;
size++;
modCount++;
}
2 add(int index,E e) :
指定index位置插入元素
public void add(int index, E element) {
// 检查index合法性
checkPositionIndex(index);
if (index == size)
// 如果需要插入的位置和链表的长度相同,就在链表的最后添加
linkLast(element);
else
// 否则就链接在此位置的前面
linkBefore(element, node(index));
}
// 越界检查
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 判断参数是否是有效位置(对于迭代或者添加操作来说)
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
// 查找索引所在的节点
Node<E> node(int index) {
//将链表长度折成两半,比index小的从左边查找,比index大的从右边,循环直到找到为止
if (index < (size >> 1)) {//比index小的从左边查找
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//比index大的从右边
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
// 在非空节点插入元素
void linkBefore(E e, Node<E> succ) {
// succ 即是插入位置的节点
// 查找该位置处的前面一个节点
final Node<E> pred = succ.prev;//获取当前succ的前一个节点
final Node<E> newNode = new Node<>(pred, e, succ);//构建新的节点并将前指针指向pred
succ.prev = newNode;//succ前指针指向新的节点
if (pred == null)//在链表头部添加
first = newNode;
else//在链表中间添加
pred.next = newNode;
size++;
modCount++;
}
3 addAll(int index, Collection<? extends E> c)
指定位置添加集合
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
//为什么这么做?--1传入的集合可能是ArrayList,linkedList,Vector,栈等等,每个数据类型
//不一样,转换成数组,便于将其转成双链表结构方便插入;2 数组查询遍历快提高性能
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
if (index == size) {//就是尾部插入
succ = null;
pred = last;//获取last元素①
} else {//头部或者中间插入
succ = node(index);//当前插入节点
pred = succ.prev;
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//只能确定insertNode的前指针是pred,后指针暂时无法确定
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)//加入了插入位置是头部的情况
first = newNode;
else//将①位置的后指针指向当前创建的元素newNode;
//下一次循环会将②位置的前一次循环的newNode的后指针指向本次循环创建的newNode
pred.next = newNode;
//pre赋予当前newNode;②--将元素串成链表的关键;循环结束pred变成此链表的最后一位③
pred = newNode;
}
if (succ == null) {//说明index位置是原链表的最后一位元素
last = pred;//将③位置的元素作为last元素
} else {//将链表最后元素与index位置的元素建立联系
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
4 E remove(int index)
按索引进行删除
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//删除核心
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;//当前删除元素的值
final Node<E> next = x.next;//前一个节点
final Node<E> prev = x.prev;//后一个节点
if (prev == null) {//代表删除节点是头结点
first = next;//first指向下一个节点
} else {//从中间删除
prev.next = next;//上一个节点的后指针指向下一个节点
x.prev = null;//置空当前删除节点的前指针
}
if (next == null) {//代表删除节点是尾结点
last = prev;//last指向前一个节点
} else {//从中间删除
next.prev = prev;//下一个节点的前指针指向前一个节点
x.next = null;//置空当前删除节点的后指针
}
x.item = null;//置空元素的节点的值
size--;
modCount++;
return element;
}
四、模拟LinkedList的完整代码实现
以下代码实现了linkedList的绝大部分功能,并拓展了其部分功能。完整代码如下:
package aiguigu.collectionExer;
import java.util.*;
import java.util.function.Predicate;
/**
* @author Jerssy
* @version V1.0
* @Description:双链表代码实现
* @create 2020-11-25 16:49
*/
public class MyLinkedList<E> {
private Node<E> first;//链表头部节点
private Node<E> last;//链表尾部节点
private int count;//记录元素个数
public MyLinkedList() {
}
//内部节点类用来构建数据
private static class Node<E>{
Node<E> prev;//前指针保存当前链表节点前一个节点数据
Node<E> next;//后指针保存当前链表节点后一个节点数据
E item;//当前节点数据
Node(Node<E> prev, E item,Node<E> next) {
this.item = item;
this.next = next;
this.prev = prev;
}
}
@Override
public String toString() {
Node<E> pre = first;
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < count; i++) {
sb.append(pre.item);
pre = pre.next;
if (i == count - 1) {
break;
}
sb.append(',');
}
sb.append(']');
return sb.toString();
}
//添加元素,双链表默认从尾部添加
public void addNode(E e){
addLast(e);
}
//头部添加与尾部添加类似
public void addFirst(E e){
addOfFirst(e);
}
//尾部添加
private void addLast(E e){
//这里可以插入null元素,双链表支持null元素的插入
//第一步:获取尾部节点last
Node<E> lastNode= last;
//第二步 addData前指针指向lastNode,addData后指针置空,构建Node节点类
Node<E> addData=new Node<>(lastNode,e,null);
//第三步:当前addData变成last节点
last=addData;
if(null==lastNode){//说明链表是空的,将first元素赋予addData;
first=addData;
}
else{
lastNode.next=addData;//将lastNode的尾部指针指向addData
}
count++;
}
//头部添加
private void addOfFirst(E e){
//尾部一样--找头元素,构建节点Node,将插入元素变成first节点,最后将插入元素尾部指针指向原来的first节点
Node<E> firstNode=first;
Node<E> addData=new Node<>(null,e,firstNode);
first=addData;
if(null==firstNode){//说明链表是空的,将last元素赋予addData;
last=addData;
}
else{
firstNode.prev=addData;//将firstNode的头部指针指向addData
}
count++;
}
//从指定位置添加元素
public void addInsert(E e, int index){
//看看插入index是否合法,然后找插入的位置,断开此位置的前与后连接
if(index<0||index>count){//不合法抛个异常
throw new IndexOutOfBoundsException("参数不合法,请检查参数");
}
else if(index==0){//不就是头部插入嘛
addFirst(e);
}
else if(index==count){//不就是尾部插入嘛
addLast(e);
}
else{//中间插入
//找插入位置index的元素
Node<E> indexNode=searchNodeOfIndex(index);
//将待插入节点的前指针指向indexNode的前一个元素,后指针指向indexNode
Node<E> insertNode=new Node<>(indexNode.prev,e,indexNode);
indexNode.prev.next=insertNode;//indexNode的前一个元素的后指针指向插入节点insertNode
indexNode.prev=insertNode;//indexNode的前指针指向插入节点insertNode
}
count++;
}
//俩表尾部插入集合
public void addAllNode(Collection<? extends E> e){
addAll(e, count);
}
//指定位置插入集合
public void addAllNode(Collection<? extends E> e, int index){
addAll(e, index);
}
private void addAll(Collection<? extends E> e, int index){
//不合法抛异常
if(index<0||index>count){//不合法抛个异常
throw new IndexOutOfBoundsException("参数不合法,请检查参数");
}
if(null==e){
throw new NullPointerException("集合为空");
}
//1 先将插入集合转成数组--为什么这么做?--1传入的集合可能是ArrayList,linkedList,Vector,栈等等,每个数据类型
//不一样,转换成数组,便于将其转成双链表结构方便插入;2 数组查询遍历快提高性能
Object[] objArray =e.toArray();
if(objArray.length==0){
return;
}
//方法一:先数组转成双链表
//2先将arrays数据转成双链表,拿到此链表的first和last元素进行操作
@SuppressWarnings("unchecked") E s = (E) objArray[0];
Node<E> predNode=new Node<>(null,s,null);
Node<E> firstNode=predNode;//保存第一个元素后续操作
for (int i = 1; i < objArray.length; i++) {
@SuppressWarnings("unchecked") E a = (E) objArray[i];
Node<E> insertNode = new Node<>(predNode, a, null);
predNode.next=insertNode;
predNode=insertNode;//循环结束predNode变成此链表的最后一位
}
Node<E> insertFirst=firstNode.next.prev;//获取新链表的first元素
//3 判断插入位置
if(index==count){//就是尾部插入嘛
last.next=insertFirst;
insertFirst.prev=last;
last=predNode;//更新last元素
}
else if(index==0){//就是头部插入嘛
predNode.next=first;
first.prev=predNode;
first=firstNode;//更新first元素
}
else{//中间插入
Node<E> indexNode =searchNodeOfIndex(index);
indexNode.prev.next=insertFirst;//indexNode的前一元素后指针指向insertFirst
insertFirst.prev=indexNode;//insertFirst的前指针指向indexNode
predNode.next=indexNode;//新链表的后元素的后指针指向indexNode
indexNode.prev=predNode;//indexNode的前指针指向新链表的后元素。
}
//方法二:JDK实现
/* Node<E> sucNode=null;
Node<E> predNode;
if(index==count){//就是尾部插入嘛
predNode=last;//获取last元素①
}
else{//头部或者中间插入
sucNode =searchNodeOfIndex(index);//找index位置的元素
predNode=sucNode.prev;//此位置的前一个元素①
}
for (Object o : objArray) {
@SuppressWarnings("unchecked") E s = (E) o;
Node<E> insertNode=new Node<>(predNode,s,null);//只能确定insertNode的前指针是predNode,后指针暂时无法确定
if(null==predNode){//加入了插入位置是头部的情况
first=insertNode;
}
else{
predNode.next=insertNode;//将①位置的后指针指向当前创建的元素insertNode;
//下一次循环会将②位置的前一次循环的insertNode的后指针指向本次循环创建的insertNode
}
predNode=insertNode;//predNode赋予当前insertNode;②--将元素串成链表的关键;循环结束predNode变成此链表的最后一位③
}
if(null==sucNode){//说明index位置是原链表的最后一位元素
last=predNode;//将③位置的元素作为last元素
}
else{//将链表最后元素与index位置的元素建立联系
predNode.next=sucNode;
sucNode.prev=predNode;
}*/
count+=objArray.length;
}
//根据元素值获取索引--可以有重复数据这里用数组存储索引
public List<Integer> getNodeOfIndex(E e){
ArrayList<Integer> result=new ArrayList<>();
int i = 0;
Node<E> node=first;
while (i<count){
if(e==null&&node.item==null||e!=null&&e.equals(node.item)){//考虑下元素可能为null的情况
result.add(i);
}
node=node.next;
i++;
}
return result;
}
//找指定索引index位置的元素
public Node<E> searchNodeOfIndex(int index){
Node<E> result;
//将数组或者链表长度折成两半,比index小的从左边查找,比index大的从右边,循环直到找到为止
int leftIndex=count>>1;//位运算,相当于除以2;
Node<E> node;
int i;
if(index<leftIndex){//比index小的从左边查找
node = first;
i = 0;
while (i<index){
node=node.next;
i++;
}
}
else{//比index大的从右边开始查找
node = last;
i = count - 1;
while (i>index){
node=node.prev;
i--;
}
}
result=node;
return result;
}
//删除前元素
public E removeFirst(){
if(first==null){
return null;
}
E oldValue=first.item;
if(first.next==null){//表里就一个元素
first=last=null;
}
else{
Node<E> firstNode;
firstNode=first.next;//新的first元素
first=null;
first=firstNode;//设置新的first元素
first.prev=null;//将新顶上来的元素前指针置空
}
count--;
return oldValue;
}
//删除后元素
public E removeLast(){
if(last==null){//表可能是空的
return null;
}
E oldValue=last.item;
if(last.prev==null){//表里就一个元素
first=last=null;
}
else {
Node<E> lastNode;
lastNode=last.prev;
last=null;
last=lastNode;
last.next=null;
}
count--;
return oldValue;
}
// 从指定位置删除指定个数的元素
public List<E> removeRange( int index,int limit){//从指定位置(包含指定位置元素·)开始删除limit个元素
ArrayList<E> result=new ArrayList<>();
//不合法抛异常
if(index<0||index>=count||limit<0||(index+limit)>count){//不合法抛个异常
throw new IndexOutOfBoundsException("参数不合法,请检查参数");
}
if(limit>0){//确保要删除数据且limit代表删除几个
if(first.next==null){//只有1个元素
result.add(first.item);
first=last=null;
}
else{
//找limit位置的下一个元素
int limitIndex;
if(index==0){
limitIndex = limit;
}
else{
limitIndex=(limit+index==count)?count-1:limit+index;//limit位置不能超过last位置
}
Node<E> indexNode=searchNodeOfIndex(index);//删除开始位置元素
Node<E> limitNode=searchNodeOfIndex(limitIndex);//删除结束位置下一个元素
Node<E> indexPrev=indexNode.prev;//记录删除前一位元素
int i = 0;
while (i<limit){//删除index到limit位置所有元素
Node<E> newNode=indexNode;
result.add(newNode.item);
indexNode.prev=null;
newNode=newNode.next;
indexNode.next=null;
indexNode=newNode;
i++;
}
if(indexPrev==null){//删除了first元素,新的first是删除结束位置的下一位置元素
first=limitNode;
}
else{//从中间删除元素
indexPrev.next=limitNode;
limitNode.prev=indexPrev;
}
if(limitNode.next==null){//删除元素包含last元素,新的last是删除开始位置的前一位置元素
last=indexPrev;
}
}
count-=limit;
}
return result;
}
//删除指定位置元素
public List<E> remove(int index){
return removeRange(index,1);
}
//删除某个元素值
public boolean remove(Object obj){
Node<E> removeNode=first;
while (removeNode!=null){
if(obj==null){//元素值可能为空的情况
if(removeNode.item==null){
return remove(removeNode);
}
}
else{
if(obj.equals(removeNode.item)){
return remove(removeNode);
}
}
removeNode=removeNode.next;
}
return false;
}
private boolean remove(Node<E> node){
//获取此位置的前一个元素和后一个元素
Node<E> nodePrev=node.prev;
Node<E> nodeSuc=node.next;
node.item=null;
node.prev=null;
node.next=null;
if(null==nodePrev){//删除first元素
first=nodeSuc;
first.prev=null;//将新顶上来的元素前指针置空
}
else{
nodePrev.next=nodeSuc;
}
if(null==nodeSuc){//删除了last元素
last=nodePrev;
last.next=null;//将新顶上来的元素后指针置空
}
else{
nodeSuc.prev=nodePrev;
}
count--;
return true;
}
//按过滤条件删除
public void removeIf(Predicate<? super E> filter){
Node<E> flagNode=first;Node<E> removeNode;
while (flagNode!=null) {
if(filter.test((removeNode=flagNode).item)){//记录删除位置的元素
remove(removeNode.item);
}
flagNode=flagNode.next;
}
}
//判断某元素从fromIndex开始首次出现的位置
public int indexOf(E e, int fromIndex){
if(fromIndex<0){
fromIndex=0;
}
if(fromIndex<count){
Node<E> indexNode=searchNodeOfIndex(fromIndex);
for (int i = fromIndex; i < count; i++) {
if(e==null&&indexNode.item==null||e!=null&&e.equals(indexNode.item)){
return i;
}
indexNode=indexNode.next;
}
}
return -1;
}
//判断某元素首次出现的位置
public int indexOf(E e){
return indexOf(e,0);
}
//判断某元素从fromIndex开始反向搜索最后次出现的位置
public int lastIndexOf(E e, int fromIndex){
if(fromIndex<0){
fromIndex=0;
}
if(fromIndex>=count){
fromIndex=count-1;
}
Node<E> indexNode=searchNodeOfIndex(fromIndex);
for (int i = fromIndex; i>0; i--) {
if(e==null&&indexNode.item==null||e!=null&&e.equals(indexNode.item)){
return i;
}
indexNode=indexNode.prev;
}
return -1;
}
//判断某元素最后此出现的位置
public int lastIndexOf(E e){
return lastIndexOf(e,count-1);
}
//修改index位置元素的值
public E setValue(int index,E element){
if(index<0||index>count-1){
throw new IndexOutOfBoundsException("参数不合法");
}
Node<E> indexNode=searchNodeOfIndex(index);
E oldValue=indexNode.item;
indexNode.item=element;
return oldValue;
}
}
package aiguigu.collectionExer;
import java.util.ArrayList;
import java.util.Date;
/**
* @author Jerssy
* @version V1.0
* @Description
* @create 2020-11-26 21:31
*/
public class myLinkedTest {
public static void main(String[] args) {
MyLinkedList<Object> list = new MyLinkedList<>();
ArrayList<Object> arrayList=new ArrayList<>();
arrayList.add("tom");
arrayList.add("tom");
arrayList.add("tom");
arrayList.add("leo");
arrayList.add("jack");
arrayList.add(null);
arrayList.add(new Date());
System.out.println("**********添加元素********");
list.addNode(2);
list.addNode(3);
System.out.println(list);
System.out.println("**********头部添加元素********");
list.addFirst("你好");
System.out.println(list);
System.out.println("**********指定1位置添加collection集合********");
list.addAllNode(arrayList,1);
System.out.println(list);
System.out.println("**********遍历集合********");
System.out.println(list);
System.out.println("**********3位置插入kobe元素********");
list.addInsert("kobe",3);
System.out.println(list);
System.out.println("**********修改2位置元素的值为jrSmith,并返回原来的值********");
System.out.println(list.setValue(2,"jrSmith"));
System.out.println(list);
System.out.println("**********删除第一个,并返回原来的值********");
System.out.println(list.removeFirst());
System.out.println(list);
System.out.println("**********删除最后一个,并返回原来的值********");
System.out.println(list.removeLast());
System.out.println(list);
System.out.println("**********判断tom元素从2开始在链表首次出现的位置********");
System.out.println(list.indexOf("tom",2));
System.out.println("**********判断tom元素在链表中首次出现的位置********");
System.out.println(list.indexOf("tom"));
System.out.println("**********反向搜索tom元素从7开始在链表中最后次出现的位置********");
System.out.println(list.lastIndexOf("tom",7));
System.out.println("**********判断tom元素在链表最后出现的位置********");
System.out.println(list.lastIndexOf("tom"));
System.out.println("**********根据元素tom值获取索引********");
System.out.println(list.getNodeOfIndex("tom"));
System.out.println("**********从2的位置删除3个元素********");
System.out.println(list.removeRange(2 ,3));
System.out.println(list);
System.out.println("**********删除3位置的元素********");
System.out.println(list.remove(3));
System.out.println(list);
System.out.println("**********删除2元素,要用包装类修饰否则会按索引删除********");
System.out.println(list.remove(Integer.valueOf(2)));
System.out.println(list);
System.out.println("**********删除满足条件的所有元素,需要指定泛型********");
MyLinkedList<Integer> list2 = new MyLinkedList<>();
list2.addNode(12);
list2.addNode(3);
list2.addNode(14);
list2.addNode(17);
list2.removeIf(e->e>12);//删除满足条件的所有元素
System.out.println(list2);
}
}