集合源码学习(五):LinkedList

何为LinkedList?

LinkedList本身是一个,由Java中的链表思想的数据结构对象,类似于链表,而在Java里面,没有指针,是通过引用来连接的。是一个链表,同时也实现了队列和栈的操作。另外,它也是可以随机访问的,只是和前两篇的ArrayList不同,它的随机访问是通过遍历从而获取特定元素的。接下来从特定方面来讲。

定义头:

/**
 * 
 * 基于双向链表实现,允许所有元素包括null
 * 在java中的链表又称为动态数组连接而成的链表。
 * 实现不是同步的。
 * 同样,它的iterator也是fail-fast的
 */
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

    transient int size = 0;


    transient Node<E> first;


    transient Node<E> last;

Deque:支持两端元素插入和移除的线性集合。名称deque是“双端队列”的缩写
默认长度为0,并且保存有头节点和尾节点。

Node节点

  /**
     * 内部类,也就是链表的节点信息,双向链表,即一个指向前一个节点,一个指向后一个节点。
     * @author anla7856
     *
     * @param <E>
     */
    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;
        }
    }

Node就是代表链表的一个节点,节点里存有item:信息,next:下一个节点,prev:前一个节点。

LinkedList的主要操作

1):插入节点

/**
     * 作为首节点插入
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

    /**
     *把元素e插入为数组末尾元素。
     */
    void linkLast(E e) {
        final Node<E> l = last;
        //这一句已经把newnode的pre指针接上了。
        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 succ) ,将e插入到succ的前面

2):删除节点

/**
     * 把f的下一个节点作为头节点,返回f节点,可以类似于从头开始遍历
     */
    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;
        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;
    }

删除节点操作,如果e是边界节点,如unlinkFirst方法,则删除时,只需要把一个引用(prev或next)置为null即可,当然还要注意gc,把不要的节点置为null。
而如果删除e是中间节点,如unlink方法,则需要把e的前后两个节点串起来,也就是他们的prev和next方法需要连起来。

清空链表:

   public void clear() {
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }

把相应的位置引用置为null,便于垃圾回收。

3):随机访问

    /**
     * 返回特殊顺序号的node节点,
     * 实现思路,二分链表,然后遍历其中之一,
     * 然后找到这个节点
     * 
     */
    Node<E> node(int 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;
        }
    }

    /**
     * 随机设置值。
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

所以在LinkedList的随机访问里面,其实是通过遍历整个集合,再根据特定判断条件(index,或者object)来找到特定的Node节点,1再对Node节点进行操作的。

ListItr迭代器

具体的和一般的迭代器差不多,里面有除了有访问方法next,previous之外,还有remove和add方法

    /**
     * LinkedList的iterator,注意有fast-fail
     * fast-fail中,只有遍历(访问)的时候才会检查ConcurrentModificationException异常
     * 而在iterator中删除或者增加时,是不会检查的,因为此时,expectedModCount会自增,而modCount也会++
     * 所以二者还是相等。
     */
      private class ListItr implements ListIterator<E> {
          ...省略

除此之外,LinkedList还有一个能够以相反顺序遍历集合的DescendingIterator,文章开头讲了LinkedList具有双向队列功能,所以当然也具有从后往前遍历利用prev引用从后往前遍历的功能。
而里面的实际操作,则是通过ListItr来实现的。

   /**
     * 能够以相反的顺序遍历的iterator,主要代码也是通过ListItr实现的。
     */
    private class DescendingIterator implements Iterator<E> {
        private final ListItr itr = new ListItr(size());
        public boolean hasNext() {
            return itr.hasPrevious();
        }
        public E next() {
            return itr.previous();
        }
        public void remove() {
            itr.remove();
        }
    }

LLSpliterator

当然,LinkedList里面也有分割迭代器,大体的和其他集合类的迭代器相似,当然,由于是以链表的方式实现,所以数据量可能很大,所以在分割表获取迭代器方面,LLSpliterator迭代器有两个额外字段:

        static final int BATCH_UNIT = 1 << 10;  // 批处理数组的一次增加长度
        static final int MAX_BATCH = 1 << 25;  // 批处理数组最大长度,即a的

LLSpliterator也具有fast-fail特性,另外,在trySplit方法,也与其他的集合不同:

        /**
         * 只有这个类返回是父类的spliterator,前几天看得两个都是直接返回自定义的linkedlist
         * @return
         */
        public Spliterator<E> trySplit() {
            Node<E> p;
            int s = getEst();
            if (s > 1 && (p = current) != null) {
                int n = batch + BATCH_UNIT;
                if (n > s)
                    n = s;
                if (n > MAX_BATCH)
                    n = MAX_BATCH;
                Object[] a = new Object[n];
                int j = 0;
               // 把linkedlist都装到a里面。
                do { a[j++] = p.item; } while ((p = p.next) != null && j < n);
                current = p;
                batch = j;
                est = s - j;
                return Spliterators.spliterator(a, 0, j, Spliterator.ORDERED);
            }
            return null;
        }

首先该方法返回的是父类的Spliterator而不是LLSpliterator,另一方面,当返回父类的迭代器后,进行操作的也有原LinkedList变为了Object[] a,所以最终相当于copy了一份LinkedList中数据,利用父类的Spliterator默认实现方法来进行并行迭代的操作。
虽然这样做了,但这样并不会出错,因为
1、LinkList没有变,则Object[] a 和原LinkedList相同,无所谓。
2、LinkList通过add或者remove等方法变了,那在进行tryAdvance和forEachRemaining就会抛出ConcurrentModificationException错误,因为是快速失败的。

总体来说,LinkedList实现比较简单的,但首先得熟练长或链表的操作,包括链表的建立(头插法,尾插法),删除,遍历,以及双向链表的结构特性等等。

感觉有收获喔^^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值