Java集合源码分析之LinkedList
Posted linteresting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合源码分析之LinkedList相关的知识,希望对你有一定的参考价值。
1. LinkedList简介
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
可以看到LinkedList类继承AbstractSequentialList类,实现了List, Deque, Cloneable, java.io.Serializable接口。实现List接口,实现对列表的增删改查操作,并且元素可以为null,实现Deque接口,为add,poll提供先进先出队列操作,以及其他堆栈和双端队列操作。LinkedList是不同步的,多线程不安全,迭代器是快速失败的(fail-fast),不能再迭代过程中对集合进行修改操作。
2. 成员变量
//集合的元素个数
transient int size = 0;
//头节点
transient Node<E> first;
//尾节点
transient Node<E> last;
LinkedList的成员变量很简单只有三个集合:size元素个数,first头节点,last尾节点。
3. 构造方法
空参构造方法:
public LinkedList() {
}
传入一个集合:
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
可以看到调用了addAll()方法将传入集合的所有元素添加到了LinkedList中。
4. 内部方法
4.1 linkFirst(E e)将e链接到链表的头
private void linkFirst(E e) {
//保存当前集合的头节点到f
final Node<E> f = first;
//e保存到newNode中,关于 Node<E>的源码分析在后面
final Node<E> newNode = new Node<>(null, e, f);
//头节点赋为newNode
first = newNode;
//判断原集合是否为空(判断原头节点是否为null)
if (f == null)
//如果原集合为空,将集合的last尾节点也赋为newNode
last = newNode;
else
//如果原集合不为空(first不为null),将原头节点的前驱节点赋为newNode
f.prev = newNode;
//结合长度自加
size++;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
}
4.2 linkLast(E e)将e链接到链表的尾
void linkLast(E e) {
//保存当前集合的尾节点到l
final Node<E> l = last;
//e保存到newNode中
final Node<E> newNode = new Node<>(l, e, null);
//尾节点赋为newNode
last = newNode;
//判断尾节点是否为null(集合是否为空)
if (l == null)
//如果集合是空,头节点赋为newNode first=last
first = newNode;
else
//集合不为空,将新的节点追加到原集合的last尾节点后作为新的尾节点
l.next = newNode;
//集合个数自加
size++;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
}
4.3 linkBefore(E e, Node succ)在一个非空节点前插入元素
void linkBefore(E e, Node<E> succ) {
//取得要插入的节点的前驱节点pred
final Node<E> pred = succ.prev;
//插入节点的前驱节点为pred,后继节点为当前节点succ
final Node<E> newNode = new Node<>(pred, e, succ);
//succ的前驱节点赋为新节点
succ.prev = newNode;
//判断succ即要插入的位置的节点的前驱节点是否为null
if (pred == null)
//如果为空那么原节点就是头节点,所以将新节点置为头节点
first = newNode;
else
//如果不为空,succ不是集合的头节点,那么将前驱节点的后继节点置为新节点
pred.next = newNode;
//集合个数自加
size++;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
}
4.4 unlinkFirst(Node f)解除头节点并且返回该节点保存的值
private E unlinkFirst(Node<E> f) {
//取出头节点的值存到element中
final E element = f.item;
//将头节点的后继节点存到next中
final Node<E> next = f.next;
//将头节点的值和下个节点的地址值置为null
f.item = null;
f.next = null; // help GC
//原头节点的后继节点置为集合的头节点
first = next;
//判断原头节点的后继节点是否为空
if (next == null)
//如果为空,那么原集合就只有一个节点,那么尾节点置为空
last = null;
else
//不为空,将新的头节点的前驱节点置为null
next.prev = null;
//集合长度自减
size--;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
//返回原头节点保存的数据
return element;
}
4.5 unlinkLast(Node l)移除尾节点并将尾节点保存的数据返回
private E unlinkLast(Node<E> l) {
//取出位节点保存的数据
final E element = l.item;
//取出尾节点的前驱节点
final Node<E> prev = l.prev;
//尾节点保存数据置为null
l.item = null;
//尾节点的前驱节点置为null
l.prev = null; // help GC
//原尾节点的前驱节点置为当前结合的尾节点
last = prev;
//判断原尾节点的前驱节点是否为null
if (prev == null)
//如果是那么头节点置为null,集合为空
first = null;
else
//否,那么原尾节点的前驱节点的后继节点置为null
prev.next = null;
//集合长度自减
size--;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
//返回原尾节点保存的数据
return element;
}
4.6 nulik(Node x)移除一个非空节点
E unlink(Node<E> x) {
//取出要移除节点保存的数据
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;
//移除节点的后继节点置为null
x.prev = null;
}
//移除节点的后继节点为null即要移除的节点为集合的尾节点
if (next == null) {
//将移除节点的前驱节点值为集合的尾节点
last = prev;
} else {
//将原节点的前驱节点置为后继节点的前驱节点
next.prev = prev;
//移除节点的后继节点置为null
x.next = null;
}
//移除节点保存的数据置为null
x.item = null;
//集合长度自减
size--;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
//返回移除节点保存的数据
return element;
}
4.7 isElementIndex(int index) 判断指定索引位置是否为元素索引位置
private boolean isElementIndex(int index) {
//判断index是否在0到size之间 包括0 但是不包括size
return index >= 0 && index < size;
}
4.8 isPositionIndex(int index) 判断指定索引位置是否为可用位置(对于迭代器和add方法)
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
4.9 node(int index) 返回指定位置的节点
Node<E> node(int index) {
//判断传入的index是否在集合的前半部分
if (index < (size >> 1)) {
Node<E> x = first;
//从0开始遍历到到指定的index处
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
//如果index在集合的后半部分
Node<E> x = last;
//倒着遍历集合
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
尽管知道索引index,但是由于LinkedList没有实现RandomAccess接口,所以不能依靠索引快速查询,只能通过遍历的方法,node通过先判断index是在前半部分还是在后半部分,将遍历的时间复杂度从O(n)降低到了O(n/2)但是对于数据庞大的集合,这样的查找效率还是太低了。
5. 查找方法
5.1 getFirst()获取集合的头节点
public E getFirst() {
//将集合头节点取出
final Node<E> f = first;
//判断头节点是否为空
if (f == null)
throw new NoSuchElementException();
//返回头节点的数据
return f.item;
}
5.2 getLat()获取集合的尾节点
public E getLast() {
//取出集合的尾节点
final Node<E> l = last;
//尾节点是否为空
if (l == null)
throw new NoSuchElementException();
//返回尾节点的数据
return l.item;
}
5.3 contains()是否包含某个元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
5.4 size返回集合长度
public int size() {
return size;
}
5.5 indexOf(Object o) 返回指定数据的索引
public int indexOf(Object o) {
//index初始化index
int index = 0;
//如果传入o为null
if (o == null) {
//遍历集合找到为村川数据为null的元素返回其索引
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
//遍历集合找到存储数据和传入的o相同的元素返回其索引
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
//没有找到返回-1
return -1;
}
##### 5.6 lastIndexOf(Object o)返回指定数据最后出现的索引
public int lastIndexOf(Object o) {
//倒着遍历,所以初始化index为size
int index = size;
//如果穿的数据为null,倒着遍历找到数据为null的节点
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
//倒着遍历找到数据为o的节点
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
//没有找到返回-1
return -1;
}
5.7 peek() 返回头节点
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
5.8 element() 返回头节点
public E element() {
return getFirst();
}
5.9 peekFirst() 返回头节点不删除
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
5.10 peekLast() 返回尾节点不删除
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
6. 增删方法
6.1 removeFirst()删除头节点
public E removeFirst() {
//取出头节点
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
//直接调用unlikFirst方法删除头节点
return unlinkFirst(f);
}
6.2 removeLast()删除尾节点
public E removeLast() {
//取出尾节点
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
//调用unlinkLast方法删除尾节点
return unlinkLast(l);
}
6.3 addFirst()在集合头部插入节点
public void addFirst(E e) {
//直接调用linkFirst方法添加头节点
linkFirst(e);
}
6.4 addLast()在集合尾部添加节点
public void addLast(E e) {
//调用linkLast添加节点
linkLast(e);
}
6.5 add()集合尾部追加一个元素
public boolean add(E e) {
//调用linkLast方法在更换尾节点
linkLast(e);
return true;
}
6.6 remove()移除集合中第一次出现的某个数据的节点
public boolean remove(Object o) {
//判断传入数据是否为null
if (o == null) {
//遍历集合
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//找到元素,用unlink方法删除
unlink(x);
return true;
}
}
} else {
//遍历集合
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
//找到元素,用unlink方法删除
unlink(x);
return true;
}
}
}
//没有找到这个元素返回false
return false;
}
6.7 addAll()在集合尾部添加指定集合
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
6.8 addAll(int index, Collection<? extends E> c)向指定位置添加集合
public boolean addAll(int index, Collection<? extends E> c) {
//检查下标是否越界
checkPositionIndex(index);
//将传入的c转化为数组
Object[] a = c.toArray();
//获取数组的长度
int numNew = a.length;
//如果数组为空返回false
if (numNew == 0)
return false;
//pred succ节点 index节点
Node<E> pred, succ;
//如果插入的索引位置为集合尾部
if (index == size) {
//succ节点置null
succ = null;
//将集合的尾节点赋给pred
pred = last;
} else {
//如果不是在尾部插入指定的集合,那么用node方法找到要插入位置的节点
//赋给succ
succ = node(index);
//pred就为succ的前驱节点
pred = succ.prev;
}
//遍历数组
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//数组的值放置到节点中newNode前驱节点为pred
Node<E> newNode = new Node<>(pred, e, null);
//如果前驱节点为null
if (pred == null)
//将插入的节点置为集合头节点
first = newNode;
else
//前驱节点不为null,将前驱节点的后继节点置为插入的节点
pred.next = newNode;
//pred置为插入的节点,进入下个循环
pred = newNode;
}
//succ是否为null,说明在结合尾部添加的所以尾节点就是pred
if (succ == null) {
//将最后的新节点置为尾节点
last = pred;
} else {
//否则是在集合中插入的 那么前驱节点的后继节点就是succ succ是index处的节点
pred.next = succ;
//插入的最后的一个节点为succ的前驱节点
succ.prev = pred;
}
//集合个数增加插入节点个数
size += numNew;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
return true;
}
6.9 clear() 清除所有节点
public void clear() {
//遍历所有节点将所有节点前驱节点,保存数据,后继节点都置为null
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
//头节点和尾节点置为null
first = last = null;
//集合长度为0
size = 0;
//对集合做出修改modCount自加保证集合版本一致
modCount++;
}
6.10 get(int index) 获取指定索引处的节点
public E get(int index) {
//检查index是否在0到size范围
checkElementIndex(index);
//调用node方法获取index处的节点的数据
return node(index).item;
}
6.11 set(int index, E element) 对指定index处的节点修改
public E set(int index, E element) {
//检查index是否在0到size范围
checkElementIndex(index);
//取得index处的节点
Node<E> x = node(index);
//取得原index处节点的数据
E oldVal = x.item;
//index处的节点数据置为element
x.item = element;
//将index处的原数据返回
return oldVal;
}
6.12 add(int inedx, E element) 在指定索引位置添加数据
public void add(int index, E element) {
//检查index是否在0到size范围内
checkPositionIndex(index);
//如果index等于size,那么在集合尾添加
if (index == size)
linkLast(element);
else
//调用linkBefore方法,指定位置前添加节点
linkBefore(element, node(index));
}
6.13 remove(int index) 移除指定索引处的节点
public E remove(int index) {
//检查index是否在0到size范围内
checkElementIndex(index);
//调用unLink方法解除一个节点
return unlink(node(index));
}
6.14 poll() 返回头节点并且删除
public E poll() {
final Node<E> f = first;
//判断头节点是否为null,如果为null的话直接返回null,不是的话调用nulinkFirst方法删除并返回头节点
return (f == null) ? null : unlinkFirst(f);
}
6.15 remove() 删除头节点并返回删除的头节点
public E remove() {
return removeFirst();
}
6.16 offer() 在集合尾部添加元素
public boolean offer(E e) {
return add(e);
}
6.17 offerFirst() 添加头节点
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
6.18 offerLast() 添加尾节点
public boolean offerLast(E e) {
addLast(e);
return true;
}
6.20 pollFirst() 返回并移除头节点
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
6.21 pollLast() 返回并删除尾节点
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
6.22 push() 将元素插入到头节点
public void push(E e) {
addFirst(e);
}
6.23 pop() 移除头节点并返回
public E pop() {
return removeFirst();
}
6.24 removeFirstOccurrence() 移除第一次出现的某个元素
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
6.25 removeLastOccurrence() 移除最后一次出现的某个元素
public boolean removeLastOccurrence(Object o) {
//当要删除的元素为null
if (o == null) {
//从尾节点向前遍历集合
for (Node<E> x = last; x != null; x = x.prev) {
if (x.item == null) {
//调用nulink方法删除节点
unlink(x);
return true;
}
}
} else {
//从后向前遍历集合
for (Node<E> x = last; x != null; x = x.prev) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
//没有找到这个元素返回false
return false;
}
7. 总结
7.1 底层实现
LinkedList底层是通过双向链表来实现的
7.2 线程安全?
LinkedList线程不安全。
7.3 增删和查找效率
对于双向链表来说增删效率很高,对于单向链表删除某个节点还需要从头遍历来说,效率高很多,对于要操作的节点直接使用unlink()方法就可以实现删除。而对于查找来说,由于LinkedList没有索引,所以无法快速获取某个节点,必须要遍历,尽管遍历的时候已经可以降低到O(N/2),但是相比数组直接通过索引快速获取来说还是效率低了很多。因此,需要经常插入增删的数据采用LinkedList保存更好。
7.4 LinkedList与ArrayList
- ArrayList使用可变数组实现,LinkedList底层是通过双向链表来实现的。
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
- 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
- ArrayList适合用于需要频繁查找和修改的环境,LinkedList则适合用于需要频繁增删的环境。
关于ArrayList的源码分析可以看这里。
以上是关于Java集合源码分析之LinkedList的主要内容,如果未能解决你的问题,请参考以下文章