源码分析:逐行分析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就是一个实现了双向链表双向对列结构的容器
并对这个双向链表结构的数据实现了:插入、删除、修改、查询、序列化等一系列操作的包装