Java8的集合:LinkedList的实现原理

一、概述

LinkedList 是通过一个双向链表来实现的,它允许插入所有元素,包括 null,同时,它是线程不同步的。下面是双向链表的结构:

在这里插入图片描述

双向链表每个结点除了数据域之外,还有一个前指针和后指针,分别指向前驱结点和后继结点(如果有前驱/后继的话)。另外,双向链表还有一个 first 指针,指向头节点和last指针,指向尾节点。

二、属性

LinkedList中的属性如下:


	/**
	*链表的节点个数
	*/
	transient int size = 0;

    /**
     *指向头节点的指针
     */
    transient Node<E> first;

    /**
     * 指向尾节点的指针
     */
    transient Node<E> last;

三、方法

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;
        }
    }
2、添加元素

对于链表这种数据结构来说,添加元素的操作无非就是在表头/表尾插入元素,又或者在指定位置插入元素。因为 LinkedList 有头指针和尾指针,所以在表头或表尾进行插入元素只需要 O(1) 的时间,而在指定位置插入元素则需要先遍历一下链表,所以复杂度为 O(n)。

在表头添加元素的过程如下:

在这里插入图片描述

当向表头插入一个节点时,很显然当前节点的前驱一定为 null,而后继结点是 first指针指向的节点,当然还要修改first指针指向新的头节点。除此之外,原来的头节点变成了第二个节点,所以还要修改原来头节点的前驱指针,使它指向表头节点,源码的实现如下:

private void linkFirst(E e) {
        final Node<E> f = first;
    	// 当前结点的前驱指向null,后继指针原来的头指针
        final Node<E> newNode = new Node<>(null, e, f);
    	//头指针指向新的头节点
        first = newNode;
    	//如果原来有头节点,则更新原来节点的前驱指针,否则更新尾指针
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

在表尾添加元素跟在表头添加元素类似,如下图:

在这里插入图片描述
当向表尾插入一个节点时,很显然当前节点的后继一定为 null,而前驱结点是last指针指向的节点,然后还要修改 last 指针指向新的尾节点。此外,还要修改原来尾节点的后继指针,使它指向新的尾节点,源码的实现如下:

void linkLast(E e) {
        final Node<E> l = last;
    	//当前节点的前驱指向尾节点,后继指向 null
        final Node<E> newNode = new Node<>(l, e, null);
    	//尾指针指向新的尾节点
        last = newNode;
    	// 如果原来有尾节点,则更新原来节点的后继指针,否则更新头指针
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

在指定节点之前插入,如下图

在这里插入图片描述

当向指定节点之前插入一个节点时,当前节点的后继为指定节点,而前驱结点为指定节点的前驱节点。此外,还要修改前驱节点的后继为当前节点,以及后继节点的前驱为当前节点,源码的实现如下:

void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
    	//指定节点的前驱
        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++;
    }
3、删除元素

删除操作与添加操作大同小异,例如删除指定节点的过程如下图所示,需要把当前节点的前驱节点的后继修改为当前节点的后继,以及当前节点的后继结点的前驱修改为当前节点的前驱

在这里插入图片描述

删除头节点和尾节点跟删除指定节点非常类似:

// 删除表头节点,返回表头元素的值
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;//头指针指向后一个节点
        if (next == null)
            last = null;
        else
            next.prev = null;//新头节点的前驱为null
        size--;
        modCount++;
        return element;
    }

// 删除表尾节点,返回表尾元素的值
private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;// 尾指针指向前一个节点
        if (prev == null)
            first = null;
        else
            prev.next = null;//新尾结点的后继为null
        size--;
        modCount++;
        return element;
    }

//删除指定节点,返回指定元素的值
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;
        } else {
            prev.next = next;//更新前驱节点的后继为当前节点的后继
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;//更新后继节点的前驱为当前节点的前驱
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
4、获取元素

获取元素的方法,源码如下:

//获取表头元素
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

    /**
     * 获取表尾元素
     */
    public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
/**
*获取指定下标的元素
*/
Node<E> node(int index) {
        // assert isElementIndex(index);
		//根据下标是否超过链表长度的一半,来选择从头部开始遍历还是从尾部开始遍历
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
5、常用方法

前面介绍了链表的添加和删除操作,发现那些方法都不是 public 的,LinkedList 是在这些基础的方法进行操作的,下面就来看看可以调用的方法有哪些

//删除表头元素
public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
//删除表尾元素
public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
//插入新的表头节点
public void addFirst(E e) {
        linkFirst(e);
    }

    //插入新的表尾节点
    public void addLast(E e) {
        linkLast(e);
    }
//链表的大小
public int size() {
 return size;
}
//添加元素到表尾
public boolean add(E e) {
 linkLast(e);
 return true; 
}
//删除指定元素
public boolean remove(Object o) {
    if (o == null) {
 for (Node<E> x = first; x != null; x = x.next) {
 if (x.item == null) {
 unlink(x);
 return true;
 }
 }
 } else {
 for (Node<E> x = first; x != null; x = x.next) {
 if (o.equals(x.item)) {
 unlink(x);
 return true;
 }
 }
 }
 return false; 
}
//获取指定下标的元素
public E get(int index) {
 checkElementIndex(index); //先检查是否越界
 return node(index).item;
}
//替换指定下标的值
public E set(int index, E element) {
 checkElementIndex(index);
 Node<E> x = node(index);
 E oldVal = x.item;
 x.item = element;
 return oldVal;
}
//在指定位置插入节点
public void add(int index, E element) {
 checkPositionIndex(index);
 if (index == size)
 linkLast(element);
 else
 linkBefore(element, node(index));
}
//删除指定下标的节点
public E remove(int index) {
 checkElementIndex(index);
 return unlink(node(index));
}
//获取表头节点的值,表头为空返回 null
public E peek() {
    final Node<E> f = first;
 return (f == null) ? null : f.item;
}
//获取表头节点的值,表头为空抛出异常
public E element() {
 return getFirst();
}
//获取表头节点的值,并删除表头节点,表头为空返回 null
public E poll() {
 final Node<E> f = first;
 return (f == null) ? null : unlinkFirst(f);
}
//添加元素到表头
public void push(E e) {
    addFirst(e);
}
//删除表头元素
public E pop() {
 return removeFirst();
}

四、总结

  1. LinkedList 的底层结构是一个带头/尾指针的双向链表,可以快速的对头/尾节点进行操作
  2. 相比数组,链表的特点就是在指定位置插入和删除元素的效率较高,但是查找的效率就不如数组
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值