工作快一年了,近期打算研究一下JDK的源码,也就因此有了死磕java系列

  • LinkedList 是一个继承于AbstractSequentialList的双向链表,链表不需要capacity的设定,它也可以被当作堆栈、队列或双端队列进行操作。
  • LinkedList 实现 List 接口,能对它进行队列操作,提供了相关的添加、删除、修改、遍历等功能。
  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。

  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输,包括网络传输与本地文件序列化。
  • LinkedList 是非同步的,如若要在并发情况下使用建议选取java.util.concurrent包下的集合类型。

LinkedList的UML图

LinkedList的成员变量及其含义


public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0; /**
* 指向头指针的节点.
* transient关键字扫盲,在实现Serilizable接口后,
* 将不需要序列化的属性前添加关键字transient,
* 序列化对象的时候,这个属性就不会序列化到指定的目的地中
*/
transient Node<E> first; /**
* 指向尾节点。
*/
transient Node<E> last; /**
* 构造方法的空实现。
*/
public LinkedList() {
} /**
* 按照集合迭代器返回的顺序构造包含指定集合元素的列表。
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
} /**
* 链表的节点,私有实现。
*/
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;
}
}
}

聊聊LinkedList的主要方法实现

我们主要看研究一下下面的几个方法,LinkedList其他方法都是通过调用这几个方法来实现功能,包括LinkedList的双端队列的方法也是。


/**
* 在链表头部插入元素.
*/
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 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++;
} /**
* 在指定节点前面插入元素.
*/
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++;
} /**
* 移除链表的头部元素.
*/
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 将元素置为空,让jvm在gc时回收资源
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
} /**
* 移除链表的尾部元素.
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = 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;
} /**
* 通过indexOf方法来定位给定的元素,若LinkedList中存在元素则返回该元素对应的index,
* 反之返回-1.
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
} /**
* 在链表尾部追加元素.
*/
public boolean add(E e) {
linkLast(e);
return true;
} /**
* 移除链表中的指定元素,分两种情况进行处理,当元素为空时与元素不为空时,时间复杂度为O(n).
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
} /**
* 将一个集合的所有元素追加到链表的尾部.
*/
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;
} /**
* 清空链表.
*/
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
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++;
} // Positional Access Operations 以下是位置访问操作 /**
* 获取链表中对应索引的节点,时间复杂度为O(n)
*/
public E get(int index) {
// 检查index是否越界
checkElementIndex(index);
return node(index).item;
} /**
* 更新对应index的元素,并返回旧值,时间复杂度为O(n).
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
} /**
* 在指定索引添加元素,时间复杂度为O(1).
*/
public void add(int index, E element) {
checkPositionIndex(index); if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
} /**
* 移除指定索引的元素,时间复杂度为O(1).
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
} /**
* 查找指定index位置的元素.
*/
Node<E> node(int index) {
// assert isElementIndex(index); // 在查找对应index位置的元素的时候,java开发人员做了一层优化
// 当index大于size的一半时从前向后查
// 当index小于size的一半时从后向前查
// 这样的话时间复杂度就变成了index/2
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;
}
} // Search Operations /**
* 查找指定元素的index,从前向后查
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
} /**
* 查找指定元素的index,从后向前查
*/
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}

动手实现LinkedList

由于代码过长,故不在此处一一贴出来,感兴趣的小伙伴可以到我的github查看[github]: https://github.com/haifeiWu/interview-collect/tree/master/src/main/java/com/haifeiwu/interview/structure/list

小结

  • 与ArrayList相比,LinkedList的长处在于当频繁的增加或者删除元素时效率会很高,但是LinkedList空间占用比较大,是一种典型的用空间换时间方案,在我们平时做代码优化时这也不失是种折中的方案。

  • LinkedList并发插入时节点覆盖的问题,就是当多个线程同时获取到相同的尾节点的时候,然后多个线程同时在此尾节点后面插入数据的时候会出现数据覆盖的问题,因此在并发量大的情况下应该使用java的加锁机制,或者采用java.util.concurrent包下的集合类型。

死磕Java之聊聊LinkedList源码(基于JDK1.8)的更多相关文章

  1. 死磕Java之聊聊HashSet源码(基于JDK1.8)

    HashSet的UML图 HashSet的成员变量及其含义 public class HashSet<E> extends AbstractSet<E> implements ...

  2. 死磕Java之聊聊HashMap源码(基于JDK1.8)

    死磕Java之聊聊HashMap源码(基于JDK1.8) http://cmsblogs.com/?p=4731 为什么面试要问hashmap 的原理

  3. 死磕Java之聊聊ArrayList源码(基于JDK1.8)

    工作快一年了,近期打算研究一下JDK的源码,也就因此有了死磕java系列 ArrayList 是一个数组队列,相当于动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractLis ...

  4. 死磕Java之聊聊ThreadLocal源码(基于JDK1.8)

    记得在一次面试中被问到ThreadLocal,答得马马虎虎,所以打算研究一下ThreadLocal的源码 面试官 : 用过ThreadLocal吗? 楼主答 : 用过,当时使用ThreadLocal的 ...

  5. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

  6. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  7. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  8. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  9. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

随机推荐

  1. Vue.js:循环语句

    ylbtech-Vue.js:循环语句 1.返回顶部 1. 循环语句 循环使用 v-for 指令. v-for 指令需要以 site in sites 形式的特殊语法, sites 是源数据数组并且 ...

  2. Lambda表达式中使用正则表达式

    某语句如果不用正则表达式: string[] names = { "Tom", "Dick", "Harry", "Mary&qu ...

  3. dubbo学习 三 dubbox概述

    当当网根据自身的需求,对dubbo进行了扩展就叫成了dubbox.具体的使用方法可以参照官网各种例子:http://dangdangdotcom.github.io/dubbox/   支持rest风 ...

  4. 深度剖析collections模块

    namedtuple OrderedDict deque tuple defaultdict Counter ChainMap

  5. 清除stoped impdp/expdp job的方法

    stoped impdp/expdp job会在dba_datapump_jobs中留下一条记录,显示为not running. 清除stopped job分两种情况: 1) job能够attach ...

  6. ORACLE各版本下载地址

    ORACLE 10g下载|ORACLE 10g下载地址|ORACLE 10g官网下载地址 ORACLE 10g下载地址 oracle 下载还需要用户名我自己注册了个方便大家使用下载 user:1603 ...

  7. leetcode893

    class Solution { public: int numSpecialEquivGroups(vector<string>& A) { set<string> ...

  8. Delphi.NET

    Delphi.NET Borland.VclDSnap.dll Borland.Vcl.TCustomClientDataSet TFieldType.ftBCD Borland.Vcl.TField ...

  9. 斐波那契数列,跳台阶(dp思想)

    一 . 斐波那契数列:1,1,2,3,5,8,13,21 即后一项是前两项的和. class Solution { private: ]; public: Solution() { memset(ar ...

  10. 国庆前执行更新承诺SO交期 FP_SO2SAP

    每年9月20日到30号执行以下程序:创建日期为昨天的订单,且承诺交期为10月1到3号,则承诺交期需加7天:创建日期为昨天的订单, 承诺交期为4号到11月1日,承诺交期需加4天 存储过程:FP_SO2S ...