源码分析:逐行分析LinkedList
概述:底层数据结构是链表 双向链表
非同步的
LinkedList是一个基于链表实现的双向队列 所以链表也是双向链表
特性:查改慢
、增删快
、非线程安全、效率高
注意:
- 除了基础的List接口的功能外 在LinkedList中还维护了 头尾两个对象 可以直接对头尾进行操作
而LinkedList也继承了队列的接口 头尾对象是根据队列的接口维护的 队列接口中还提供了对头尾进行操作的方法
除了头尾操作外push、pop、poll....等方法也都是有实现 不过这里是作为List接口下的LinkedList来介绍 关于队列的用法 后面可能会补充吧 - 由于是双向链表所以 LinkedList 不能随机访问 因为链表结构决定LinkedList只能通过节点的信息去循环查找(没有索引所以不能直接找到节点)
优势:
增删比较快 时间复杂度是O(1)
因为不管是头、尾、中间那个地方操作 都影响的节点最多就是前后两个节点
不过需要注意的是 LinkedList 在删除前需要找到需要删除的对象 在指定位置添加前也需要找到指定位置的元素
而查询的过程需要循环查找 所以并不是说LinkedList的增删就一定快
缺点:
查改比较慢 时间复杂度是O(n)
LinkedList不能随机访问 因为他没有索引 不能根据索引直接找到数据 必须通过循环查找
画图解析:
LinkedList结构:
堆中的结构:
源码分析:
构造函数;
// LinkedList的两个构造函数没什么好说的 比较简单 public LinkedList() { } public LinkedList(Collection<? extends E> c) { this();// 调用无参构造 addAll(c);// 把传进来的collection参数添加进链表对象 } // 构造函数没啥好说的 说说LinkedList的属性 // 链表的逻辑长度(跟数组不同逻辑长度就是实际长度) transient int size = 0; // 头节点对象 transient Node<E> first; // 尾节点对象 transient Node<E> last; // 这个Node就是链表的一个个节点对象 是LinkedList的一个内部类 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; } }
添加方法:
// 直接添加 相当于尾插入 public boolean add(E e) { linkLast(e);// 直接调了尾插入 return true; } // 头插入 public void addFirst(E e) { linkFirst(e);// 直接调了头插入 } // 尾插入 public void addLast(E e) { linkLast(e);// 直接调了尾插入 } // 指定位置插入 并不是说是中间插入 // 因为如果指定的位置是最后一个 实际就相当于是尾插入 // 不过不能进行头插入 会抛出异常:IndexOutOfBoundsException public void add(int index, E element) { checkPositionIndex(index);// 指定的位置 必须大于零 小于链表的size //index >= 0 && index <= size if (index == size)// 判断指定位置和逻辑长度 linkLast(element);// 相等直接调用尾插入 else linkBefore(element, node(index));// 不相等 说明为中间插入 } // 根据指定位置取节点对象 Node<E> node(int index) {// LinkedList中取元素 除了直接取头尾节点 都是通过这个方法循环取指定位置的节点 // 性能相对ArrayList的直接根据索引取来说 是比较慢的 if (index < (size >> 1)) { Node<E> x = first;// 先赋头节点的值 for (int i = 0; i < index; i++)// 循环取到index位置的节点对象 x = x.next; return x; } else {// 逻辑同上 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } 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++;// 逻辑长度加1 modCount++;// 记录修改次数 } 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++; } // 中间插入 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++; }
删除方法:
// 删除指定位置 public E remove(int index) { checkElementIndex(index);// 校验index 保证是正确数据 错误数据抛出异常:IndexOutOfBoundsException // 校验规则:index >= 0 && index < size return unlink(node(index));// 校验通过 直接调用nulink(node方法在上面说了 不再重复) } // 删除指定对象 需要equals方法支持 public boolean remove(Object o) {// 参数o是需要删除的对象 if (o == null) {// 如果需要删除的对象是null for (Node<E> x = first; x != null; x = x.next) {// 从头节点开始 取下一节点 一直取到下一节点是null if (x.item == null) {// 当前节点元素为null 直接调用unlink移除元素 unlink(x); return true; } } } else {// 逻辑基本同上 唯一不同是判断对象相同的手段 这里用的是equals方法 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; } // 移除指定节点 E unlink(Node<E> x) {// 参数x是需要移除的节点对象 // assert x != null; final E element = x.item;// 取出需要移除节点的元素 final Node<E> next = x.next;// 取处需要移除节点的下一节点引用 final Node<E> prev = x.prev;// 取出需要移除节点的上一节点引用 if (prev == null) {// 如果上一节点为null 说明需要移除的节点是头节点 first = next;// 直接将LinkedList中的头节点对象设置为 需要移除节点的下一节点 } else {// 如果不是头节点 prev.next = next;// 设置上一节点的下一节点为需要移除节点的下一节点 x.prev = null;// 置空需要移除节点的上一节点 } //处理next的逻辑同上面的prev 不再重复 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null;// 最后置空需要移除节点的元素本身 (到此为止 链表中已经不存在对item的引用 GC会在何时的时候处理掉item) size--;// 逻辑长度更新 modCount++;// 操作记录加一 return element;// 返回之前保存的item的引用 }
查改方法:
// 查改放在一起是因为逻辑比较简单 这里简单提下 // 取指定位置的元素 public E get(int index) { checkElementIndex(index);// 这个校验在删除的时候提过 不在重复 return node(index).item;// 通过node方法取到 节点对象 返回节点中的元素 } public E getFirst() {// 直接取头节点 final Node<E> f = first; if (f == null)// 头节点为空抛出异常 throw new NoSuchElementException(); return f.item; } public E getFirst() {// 直接取尾节点 final Node<E> f = first; if (f == null)// 尾节点为空抛出异常 throw new NoSuchElementException(); return f.item; } // 修改方法 修改指定位置的元素 public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
总结一下:
LinkedList就是一个实现了双向链表双向对列结构的容器
并对这个双向链表结构的数据实现了:插入、删除、修改、查询、序列化等一系列操作的包装
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构