Java 数据结构 -- 10.Java 8 数据结构 LinkedList

本文深入分析了 Java 8 中的 LinkedList 数据结构,探讨了其作为 AbstractSequentialList 和 Deque 实现的特性。LinkedList 仅维护首尾节点,支持双端操作,通过节点对象存储数据。虽然双端操作高效,但按位置操作效率较低,且在并发修改时需要注意迭代器的使用。最后,文章提及 LinkedList 的反向迭代器功能。
摘要由CSDN通过智能技术生成

书接上文,上一篇中对链表结构最终实现类 Vector 做了分析,本篇将继续对 LinkedList 做分析。

LinkedList 与 ArrayList/Vector 有一点不同,它的继承关系是 AbstractList -》 AbstractSequentialList -〉 LinkedList,并且实现了 Deque 接口,而 ArrayList/Vector 是直接继承自 AbstractList 的,首先来看下 LinkedList 的关系图

在这里插入图片描述

所以在查看 LinkedList 之前,还需要看一下 Deque(与 Queue) 的接口

Queue

/**
Qeueu 接口,一种被设计用于存放加工前数据的数据结构。除了基础的 Collection 方法外,队列提供了额外的添加,提取和检查操作。每个这样的操作都有两种形式:一种在操作失败时抛出一个异常,另一种返回一个特殊值(可能是 null 或者 false,具体看操作)。添加操作的后裔中方式被特别设计用来配合优先容量的 Queue 实现类实现,大部分的 Queue 的是下了,插入操作是不会失败的。
继承自 Collection 接口
**/
public interface Queue<E> extends Collection<E> {
   
    /**向队列中插入指定参数,如果它能不考虑容量限制,就能快速地避开违反容量限制(翻译的可能不太准确。。。),如果成功返回 true,如果当前没有剩余空间了则抛出 IllegalStateException**/
    boolean add(E e);

    /**向队列中插入指定参数,如果它能不考虑容量限制,就能快速地避开违反容量限制,当使用一个容量有限的队列时,建议使用 add 方法,它(add)是一个只能在抛出异常时插入失败的方法**/
    boolean offer(E e);

    /**获取并且移除队列的头部元素。这个方法与 poll 方法唯一的区别是它会在队列为空时抛出一个异常**/
    E remove();

    /**获取并且移除队列的头部元素。当队列为空时返回 null**/
    E poll();

    /**返回队列的头部元素,但不移除。这个方法和 peek 方法唯一的区别是它会在队列为空时抛出一个异常**/
    E element();

    /**返回队列的头部元素,但不移除。当队列为空时返回 null**/
    E peek();
}

可以看到 Queue 继承自 Collection 接口,并提供一些针对队列的操作方式。每一种方式都有成对的方法,一种返回异常,一种返回 false 或者 null。

Deque

/**
Deque 接口,一种支持在双端进行元素插入和移除的线性数据结构。	“deque” 是 “double ended queue”(双端队列)的缩写。大部分 Deque 的实现类没有固定的元素上限,不会这个接口同时支持有上限的 deque 与无上限的。
这个接口定义了从 deque 双端访问元素的方法。提供的方法有插入,移除和校验元素。每个方法都有两种形式:一种在操作失败时抛出异常,另一种返回一个特殊的值(可能是 null 或者 false,具体要看操作)。插入操作的后面一种形式被特别设计来配合有限容量 Deque 实现类使用,在大部分实现类中,插入操作不会失败。
继承自 Queue 接口
**/
public interface Deque<E> extends Queue<E> {
   
    /**向双端队列中的开头插入指定参数,如果它能不考虑容量限制,就能快速地避开违反容量限制,当没有剩余空间时抛出一个 IllegalStateException。当使用一个容量有限的双端队列时,建议使用 offerFirst 方法**/
    void addFirst(E e);

    /**向双端队列中的末尾插入指定参数,如果它能不考虑容量限制,就能快速地避开违反容量限制,当没有剩余空间时抛出一个 IllegalStateException。当使用一个容量有限的双端队列时,建议使用 offerLast 方法**/
    void addLast(E e);

    /**向双端队列中的开头插入指定参数,除非它会违反容量限制。当使用一个容量有限的双端队列时,建议使用 addFirst 方法,它(addFirst)只在能抛出一个异常时插入失败**/
    boolean offerFirst(E e);

    /**向双端队列中的末尾插入指定参数,除非它会违反容量限制,当使用一个容量有限的双端队列时,建议使用 addLast 方法,它(addlast)只能在抛出一个异常时插入失败**/
    boolean offerLast(E e);

    /**获取并移除双端队列的第一个元素,这个方法和 pollFirst 方法的唯一区别在于它会在双端队列中没有元素时抛出一个异常**/
    E removeFirst();

    /**获取并移除双端队列最后一个元素,这个方法和 pollLast 方法的唯一区别在于它会在双端队列中没有元素时抛出一个异常**/
    E removeLast();

    /**获取并移除双端队列的第一个元素,当双端队列中没有元素时返回 null**/
    E pollFirst();

    /**获取并移除双端队列最后一个元素,当双端队列中没有元素时返回 null**/
    E pollLast();

    /**返回双端队列的第一个元素,但是不移除,这个方法和 peekFirst 方法的唯一区别在于它会在双端队列中没有元素时抛出一个异常**/
    E getFirst();

    /**返回双端队列的第一个元素,但是不移除,这个方法和 peekLast 方法的唯一区别在于它会在双端队列中没有元素时抛出一个异常**/
    E getLast();

    /**返回双端队列的第一个元素,但是不移除,或者当双端队列中没有元素时返回 null**/
    E peekFirst();

    /**返回双端队列的最后个元素,但是不移除,或者当双端队列中没有元素时返回 null**/
    E peekLast();

    /**移除双端队列中的第一个与指定参数相同的元素。如果双端队列不包含这种元素,它将保持不变。**/
    boolean removeFirstOccurrence(Object o);

    /**移除双端队列中最后一个与指定参数相同的元素。如果双端队列不包含这种元素,它将保持不变。**/
    boolean removeLastOccurrence(Object o);

    // 查询操作

    /**在这个双端队列提供的队列中插入指定元素(换一种说法,在双端队列的末尾)如果它能不考虑容量限制,就能快速地避开违反容量限制,操作成功时返回 true,如果当前没有剩余空间时抛出一个 IllegalStateException**/
    boolean add(E e);

    /**在这个双端队列提供的队列中插入指定元素(换一种说法,在双端队列的末尾)如果它能不考虑容量限制,就能快速地避开违反容量限制,操作成功时返回 true,如果当前没有剩余空间时返回 false。当使用容量有限的双端队列时,建议使用 add 方法,它(add)只有在抛出异常时才插入失败**/
    boolean offer(E e);

    /**获取并移除当前双端队列提供的队列的头部元素(换句话说,双端队列中的第一个元素)。这个方法与 poll 方法唯一的区别是它会在队列中没有元素时抛出一个异常**/
    E remove();

    /**获取并移除当前双端队列提供的队列的头部元素(换句话说,双端队列中的第一个元素)。当双端队列中没有元素时候返回 null**/
    E poll();

    /**获取当前双端队列提供的队列的头部元素(换句话说,双端队列中的第一个元素),但不移除。这个方法和 peek 方法唯一的区别是它会在双端队列中没有元素时抛出一个异常**/
    E element();

    /**获取当前双端队列提供的队列的头部元素(换句话说,双端队列中的第一个元素),但不移除。当双端队列中没有元素时返回 null**/
    E peek();


    // 栈方法

    /**向当前双端队列提供的队列的栈(换句话说,双端队列的头部)压入一个元素,如果它能不考虑容量限制,就能快速地避开违反容量限制,当没有剩余空间时抛出一个 IllegalStateException**/
    void push(E e);

    /**从当前双端队列提供的栈弹出一个元素。换句话说,移除并返回双端队列的第一个元素**/
    E pop();


    // 集合方法

    /**移除双端队列中第一个与指定参数相同的元素。如果双端队列中不包含这样的元素,则保持不变**/
    boolean remove(Object o);

    /**判断当前双端队列是否包含与指定参数相同的元素,如果包含,返回 true**/
    boolean contains(Object o);

    /**返回这个双端队列的元素数量**/
    public int size();

    /**以正确的顺序返回双端队列的一个串行迭代器**/
    Iterator<E> iterator();

    /**按反向顺序返回双端队列的一个串行迭代器**/
    Iterator<E> descendingIterator();

}

从 Deque 的源码可以看到,Deque 扩展了 Queue 接口的方法,使得 Queue 中的队列操作可以既可以从头也可以从尾部进行,并提供了栈相关方法的定义与获取反向迭代器方法。

然后来认识一下 AbstractSequentialList

AbstractSequentialList

/**
本类提供一个 List 接口的实现框架来最小化它的支持“顺序访问”数据存储(比如关联)的实现类所需要的实现。如果要随机访问数据(比如一个数组),应该先考虑 AbstractList 类而不是本类。继承自 AbstractList 类。
**/
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
   
    /**唯一的构造方法,通常是隐式的被子类构造方法调用**/
    protected AbstractSequentialList() {
   
    }

    /**返回链表中 int 参数指定的位置的元素**/
    public E get(int index) {
   
        try {
   
            return listIterator(index).next(); //首先返回一个带 index 参数的串行迭代器,然后返回迭代器中的下一个元素
        } catch (NoSuchElementException exc) {
   
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

    /**在 index 指定位置替换 e 参数(可选操作)**/
    public E set(int index, E element) {
   
        try {
   
            ListIterator<E> e = listIterator(index); //获得链表的带参串行迭代器
            E oldVal = e.next(); //缓存下一个元素
            e.set(element); //设置元素
            return oldVal; //返回缓存的旧值
        } catch (NoSuchElementException exc) {
   
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

    /**在 index 指定位置添加 e 参数(可选操作),移动当前和其后的元素至右边(下标 + 1)**/
    public void add(int index, E element) {
   
        try {
   
            listIterator(index).add(element); //先获取带参迭代器,然后调用迭代器的添加方法
        } catch (NoSuchElementException exc) {
   
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

    /**移除链表中指定位置的元素(可选操作),左移其后的元素(通过递减它们的下标)。返回被移除的元素**/
    public E remove(int index) {
   
        try {
   
            ListIterator<E> e = listIterator(index); //先获取带参迭代器
            E outCast = e.next(); //获得下一个元素并缓存
            e.remove(); //移除
            return outCast; //返回缓存的元素
        } catch (NoSuchElementException exc) {
   
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

    //扩展

    /**在指定参数位置添加指定容器参数中的所有元素(可选操作),右移原位置元素与其后的元素(增加下标)。新的元素的顺序与它们在迭代器中的顺序相同。如果容器参数在过程中更改了,那么这个操作的行为是无法定义的。(注意当容器参数是自己并且它非空时候会发生。这句话不是很明白。。)**/
    public boolean addAll(int index, Collection<? extends E> c) {
   
        try {
   
            boolean modified = false; //初始化修改标志
            ListIterator<E> e1 = listIterator(index); //获取迭代器
            Iterator<? extends E> e2 = c.iterator(); //后去容器参数迭代器
            while (e2.hasNext()) {
    //遍历判断游标后是否还有元素
                e1.add(e2.next()); //获取后一个元素,加入 e1
                modified = true; //修改标志赋值为 true
            }
            return modified; //返回修改标志
        } catch (NoSuchElementException exc) {
   
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }


    // 迭代器

    /**返回一个包含正确排序的元素的串行迭代器**/
    public Iterator<E> iterator() {
   
        return listIterator();
    }

    /**返回一个带参链表串行迭代器**/
    public abstract ListIterator<E> listIterator(int index);
}

由 AbstractSequentialList 的源码可以看到,它实现了几个在 AbstractList 中定义了但是如果自己不重写就会抛出异常的方法,它们的实现方式都是需要先获得迭代器,所以可以认为 AbstractSequentialList 类的所有方法都是针对迭代器的。

现在可以来看 LinkedList 的源码了

/**
双链接链表实现了 List 和 Deque 接口。实现了所有可选链表操作,并且允许所有元素(包括 null)。

所以的操作作为可以表现为符合一个双链接链表的期待。链表中的位置操作将从开始到结束贯穿整个链表,知道靠近参数指定位置。

注意这个实现类不是线程安全的。如果读线程同时访问一个链接链表,并且至少有一个线程建构性的改变了链表,它必须从外部保证线程安全。一个结构性的修改是指任何添加或者删除一个或多个元素的操作,或者明切重定义了支持数组的大小,仅仅为一个元素设值不是一个结构性的修改。)这通常是通过使某些封装了链表的对象实现线程安全来做到的。

如果没有类似的对象存在,链表应该被使用 Collection。sychronizedList 方法进行“包装”。这最好在创建时完成,来防止意外的对链表的非线程安全的访问。比如:
List list = Collections.synchronizedList(new LinkedList(...));

通过 iterator 方法或者 listIterator 返回的迭代器是 fail-fast 的,如果在迭代器创建后的任意时间点链表被结构性的改变了,只要不是通过迭代器自己的 ListIterator#remove 或者 ListIterator#add 方法,迭代器将会抛出一个同时修改异常,迭代器将快速干净的失败,

注意一个迭代器的 fail-fast 行为是不能被保证的,通常来说,不可能对出现的非线程安全的同时修改操作做任何硬性的保证。基于最佳性能的基础考虑,Fail-fast 迭代器抛出一个 ConcurrentModificationException。因此,建立在这中异常上写出的程序的正确性将会是不健壮的:迭代器的 fail-fast 行为应当植被用于检查 bugs。
**/
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
   
		/**长度,不被序列化**/
    transient int size = 0;

    /**首节点
     * 永恒不变的:首节点和尾节点都为 null 或者首节点的前节点为 null 并且首节点内容不为 null**/
    transient Node<E> first;

    /**尾节点
     * 永恒不变的:首节点和尾节点都为 null 或者尾节点指针的后节点为 null 并且尾节点内容不为 null**/
    transient Node<E> last;

    /**构造一个空的链表**/
    public LinkedList() {
   
    }

    /**构造一个包含指定组数据结构中元素的的链表,换句话说它们被指定数据结构的迭代器返回**/
    public LinkedList(Collection<? extends E> c) {
   
        this();
        addAll(c);
    }

    /**将指定参数 E 链接为首节点**/
    private void linkFirst(E e) {
   
        final Node<E> f = first; //首节点缓存给 f
        final Node<E> newNode = new Node<>(null, e, f); //调用 Node 构造器,传入参数与首节点,构造一个新的节点
        first = newNode;  //新节点赋值给首节点
        if (f == null) //如果 f 为 null,说明原来没有首节点
            last = newNode; //新节点赋值给尾节点
        else //如果 f 不为 null,说明原来有首节点
            f.prev = newNode; //新节点赋值给 f 的前节点
        size++; //更新长度
        modCount++; //更新 modCount
    }

    /**将制定参数 E 链接为尾节点**/
    void linkLast(E e) {
   
        final Node<E> l = last; //缓存尾节点给 l
        final Node
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值