重拾Java之LinkedList源码阅读

本文详细剖析了LinkedList的内部结构和实现原理,包括链表节点定义、构造方法、增删改查及遍历方法等,并对比了LinkedList与ArrayList的不同应用场景。

     上文我们查看ArrayList的源码(重拾Java之ArrayList源码阅读),接着我们来瞅瞅LinkedList有什么神奇之处。ArrayList的数据存储方式是数组,LinkedList里面储存数据的方式是链表,什么是链表了?你可以将其理解为一列火车,每一节车厢就是一个节点(Node)。Node是一个类,如下:

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;
        }
    }

item是存储的数据对象;prev是该节点指向的上一个节点;next是该节点指向的下一个节点。接着我们看看LinkedList的中的几个非常重要的全局变量:

//LinkedList中元素的个数
transient int size = 0;
//LinkedList中链表的头节点
transient Node<E> first;
//LinkedList中链表的尾节点
transient Node<E> last;

 一、LinkedList的构造方法

  无参数的就一个空方法,没啥好看的,我们看看其有参构造函数:

public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

addAll这个方法写的很出彩,考虑的非常的全面;这个方法是一个public方法,开发者可以直接外部调用的。首先该方法对添加的索引进行了异常的判断,接着对添加的集合进行了异常的判断。接着代码18行到25行考虑的很全面:当index==size 成立的时候表示新增的集合数据是从原来链表的链表的尾部开始添加的;不成立的时候表示是从链表的中间进行添加的。紧接着27到35行是向链表中添加数据。第30行成立的时候表示时原来的链表是空链表。37行到42行代码;当37行代码成立的时候表示的是向空链表中添加数据的,比如我们用集合初始化LinkedList。modCount和在ArrayList中一样,同样记录的是集合大小改变的次数。


二、LinkedList的增删改查以及遍历方法

    1.增加数据 add方法

public boolean add(E e) {
        linkLast(e);
        return true;
    }

void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

可见这个方法非常的单纯的,首先创建了一个新的节点,然后判断last指向节点是不是为空。如果为空的话,那表示之前的链表是空的。

2.删除数据 remove方法

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

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;
        }
    }

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;
    }

remove方法首先检查要移除的节点的索引是否合法;接着使用node方法进行该索引位置节点的查找;node方法中 size>>1表示size/2;如果索引小于该值就从first指向的节点找,大于的话就从last指向的节点查找(有点二分法查找的意思)。然后使用unlink方法进行删除节点(其中该方法需要考虑,要删除的节点是头节点或者尾节点)。

3.修改数据  set方法

public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

同样是先检查该索引是否合法;然后使用node方法找到该索引对应的节点。然后直接操作该节点即可。

4.查找数据 get方法

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

同样get方法,没什么亮点,非常的善良。

5.遍历数据 迭代器方法:

public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }

    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;

        ListItr(int index) {
            // assert isPositionIndex(index);
            next = (index == size) ? null : node(index);
            nextIndex = index;
        }

        public boolean hasNext() {
            return nextIndex < size;
        }

        public E next() {
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

        public boolean hasPrevious() {
            return nextIndex > 0;
        }

        public E previous() {
            checkForComodification();
            if (!hasPrevious())
                throw new NoSuchElementException();

            lastReturned = next = (next == null) ? last : next.prev;
            nextIndex--;
            return lastReturned.item;
        }

        public int nextIndex() {
            return nextIndex;
        }

        public int previousIndex() {
            return nextIndex - 1;
        }

        public void remove() {
            checkForComodification();
            if (lastReturned == null)
                throw new IllegalStateException();

            Node<E> lastNext = lastReturned.next;
            unlink(lastReturned);
            if (next == lastReturned)
                next = lastNext;
            else
                nextIndex--;
            lastReturned = null;
            expectedModCount++;
        }

        public void set(E e) {
            if (lastReturned == null)
                throw new IllegalStateException();
            checkForComodification();
            lastReturned.item = e;
        }

        public void add(E e) {
            checkForComodification();
            lastReturned = null;
            if (next == null)
                linkLast(e);
            else
                linkBefore(e, next);
            nextIndex++;
            expectedModCount++;
        }

        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            while (modCount == expectedModCount && nextIndex < size) {
                action.accept(next.item);
                lastReturned = next;
                next = next.next;
                nextIndex++;
            }
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

比较简单,没啥亮点.

LinkedList实现了Deque接口,该接口是继承Queue接口的;所以LinkedList可以充当队列Queue的实现类。

public interface Queue<E> extends Collection<E> {
    //添加元素
    boolean add(E e);

    //添加元素
    boolean offer(E e);

    //返回移除的元素,并将该元素移除
    E remove();

    //返回队列头部元素并将其删除
    E poll();

    //返回队列头部元素,不删除队列的头部元素
    E element();

    //返回队列头部元素,不删除队列的头部元素
    E peek();
}

offer方法没啥亮点,其实就是调用add方法。我们看看poll、element、peek这三个方法,首先可以排除poll方法;它返回对头但是会把队头删掉。而element和peek方法不会。下面来看看element和peek方法的源码:

public E element() {
        return getFirst();
    }

public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

可以看出这两个方法并没有太大的区别。仅仅只是element方法如果队列的头部是空的,即如果是空列表的话,会抛出异常!peek方法了?如果队列是空的话,只会返回null而不会抛出异常。类似的LinkedList中含有很多重载的方法;都差不多就不一一的分析,读者可以自己看看;当然还有一些JDK 1.8中新增的方法;这个和ArrayList中的类似,读者可以看我之前的分析ArrayList的源码的文章。


三、小结

   到目前为止,我们已经分析完ArrayList和LinkedList的源码;对比之前的ArrayLsit源码,我们不难发现以前的结论:ArrayList做查询很快,但不是适合做删除和插入操作;LinkedList做查询操作比ArrayList慢,但是做插入和删除操作是优于ArrayLsit的。

备注:有什么写的有问题的,欢迎读者在评论区斧正,谢谢。为了我们面试不被面试官按到地上摩擦,我们还是要多看看源码。



本指南详细阐述基于Python编程语言结合OpenCV计算机视觉库构建实时眼部状态分析系统的技术流程。该系统能够准确识别眼部区域,并对眨眼动作与持续闭眼状态进行判别。OpenCV作为功能强大的图像处理工具库,配合Python简洁的语法特性与丰富的第三方模块支持,为开发此类视觉应用提供了理想环境。 在环境配置阶段,除基础Python运行环境外,还需安装OpenCV核心模块与dlib机器学习库。dlib库内置的HOG(方向梯度直方图)特征检测算法在面部特征定位方面表现卓越。 技术实现包含以下关键环节: - 面部区域检测:采用预训练的Haar级联分类器或HOG特征检测器完成初始人脸定位,为后续眼部分析建立基础坐标系 - 眼部精确定位:基于已识别的人脸区域,运用dlib提供的面部特征点预测模型准确标定双眼位置坐标 - 眼睑轮廓分析:通过OpenCV的轮廓提取算法精确勾勒眼睑边缘形态,为状态判别提供几何特征依据 - 眨眼动作识别:通过连续帧序列分析眼睑开合度变化,建立动态阈值模型判断瞬时闭合动作 - 持续闭眼检测:设定更严格的状态持续时间与闭合程度双重标准,准确识别长时间闭眼行为 - 实时处理架构:构建视频流处理管线,通过帧捕获、特征分析、状态判断的循环流程实现实时监控 完整的技术文档应包含模块化代码实现、依赖库安装指引、参数调优指南及常见问题解决方案。示例代码需具备完整的错误处理机制与性能优化建议,涵盖图像预处理、光照补偿等实际应用中的关键技术点。 掌握该技术体系不仅有助于深入理解计算机视觉原理,更为疲劳驾驶预警、医疗监护等实际应用场景提供了可靠的技术基础。后续优化方向可包括多模态特征融合、深度学习模型集成等进阶研究领域。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值