LinkedList
在分析源码之前先分析一下LinkedList的数据结构,
首先进入源码看介绍或者看LinkedList的全局变量都可以看到LinkedList是基于双链表实现的,数据存储是存储在节点Node中的下面是数据结构类型的源码:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
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;
}
}
由上面源码可以分析出,LinkedList确实是基于双链表实现的,同时因为LinkedList的节点是不可序列化的,注意是LinkedList序列化的时候这两个节点是不能进行序列化,所以LinkedList还是可以序列化的,还有看LinkedList继承的类可以发现和ArrayList有所不同就是少了个随机访问类接口,因此也可以知道LinkedList是不可用随机访问的(链表本身就不支持随机访问).
性能分析:LinkedList是基于链式存储(双链表)的数据结构,由数据结构的知识我们可以知道链表的插入、删除、查询时间复杂度基本都在O(n),有部分甚至达到了O(1)(比如头节点,为节点,或者给定前驱节点等等操作),它的cud性能比ArrayList要好,比较ArrayList删除、添加等操作,需要移动数据,在查询方面ArrayList速度要快可以随机访问,而链表需要遍历一遍
**注:**下面是对LinkedList的一些比较复杂的操作进行分析,不分析的基本与ArrayList实现是基本一致的
首先分析addAll(int index, Collection<? extends E> c)方法
数据结构如图
先看一些双链表的添加节点的几种情况
-
在尾节点(lastNode)添加
实现思路是直接遍历添加元素的数组,构建新节点newNode,并且节点的前继节点是尾节点,尾节点的后继节点是该新节点,把尾节点指向新添加的节点,lastNode.next=newNode,newNode.pred=lastNode; lastNode=newNode;
-
在头节点添加
实现思路和尾节点添加类似
-
在中间添加,下标为index的节点前
实现步骤:先定义两节点一个作为前继节点pred,一个作为后继节点succ,先遍历index一次一直找到index节点,然后把index作为后继节点,index的前继节点作为pred,然后在遍历对象数组新建节点newNode,把新建节点的前继节点设置为pred,pred节点的后继节点设置为
newNode. succ=indexNode,pred=index.Node.pred, newNode.pred=pred,pred.succ=newNode
这个方法是LinkedList进行批量添加时调用的,下面看源码
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
public boolean addAll(int index, Collection<? extends E> c) {
//首先检查是否越界
checkPositionIndex(index);
//这里调用c类实现的toArray返回一个带c中数据的对象数组
Object[] a = c.toArray();
//记录需要元素的长度
int numNew = a.length;
if (numNew == 0)
return false;
//定义一个前继节点 和后继节点(或者说指针)
Node<E> pred, succ;
//添加有多种情况,在头节点 或者在尾节点 或者在中间
//判断index是否等于size如果等于则在最后一个节点插入
if (index == size) {
succ = null;
pred = last;
} else {
//利用二分思想获取idex节点,并且作为后继节点,这里就相当于从index这个节点开域前面的节点断开,在前面添加完数据后再人最后一个数据的后继节点指向succ,succ的前继节点指向它(不为null的前提下)
succ = node(index);
//index这个节点的前继节点作为前继节点。
pred = succ.prev;
}
//遍历c数据对象进行元素添加进链表中
for (Object o : a) {
//将对象o转换尾E类型
@SuppressWarnings("unchecked") E e = (E) o;
//以e为value,pred作为前继节点构建一个新节点
Node<E> newNode = new Node<>(pred, e, null);
//如果是pred是空节点,说明index节点是头节点,则直接让头节点指向这个新节点
if (pred == null)
first = newNode;
else//如果不是头节点则让头节点的后继节点指向这个新节点,
pred.next = newNode;
//把pred指向新节点继续在新节点后继续添加一直到遍历完成
pred = newNode;
}
//上面添加完数据后开始把后面部分的数据接回,如果succ是空当情况(即index节点就是尾节点)下则让尾节点指向添加的最后一个元素
if (succ == null) {
last = pred;
} else {
//如果不是尾节点则让最后添加的节点的后继节点指向succ,succ的前继节点指向它
pred.next = succ;
succ.prev = pred;
}
//修改size的个数
size += numNew;
//修改的操作次数+1 这里和ArrayList是一致的
modCount++;
return true;
}
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
//下面的方法是检查idnex是否越界
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//根据index遍历链表,这里利用了二分的思想
Node<E> node(int index) {
// assert isElementIndex(index);
//判断idnex是在size的中间值的左右那一边就走那一边去遍历。
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;
}
}
-
先看一些在头节点添加元素的方法
private void linkFirst(E e) { //先把f指向头节点 final Node<E> f = first; 创建一个新节点,并且新节点的后继节点指向原码头节点f final Node<E> newNode = new Node<>(null, e, f); //在哪把头节点指向新节点 first = newNode; //如果f是空则证明链表无数据,则为节点也指向该新节点 if (f == null) last = newNode; //否则把原头节点的前继节点指向新节点 else f.prev = newNode; size++; //操作次数+1 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) { //pred指向 succ节点的前继节点 final Node<E> pred = succ.prev; 创建一个新的节点其前继节点是succ的前继节点,后继节点是succ final Node<E> newNode = new Node<>(pred, e, succ); //然后让succ节点的前继节点指向该新节点 succ.prev = newNode; 如果pred是空证明succ是头节点 if (pred == null) first = newNode; 否则把succ前继节点的后继节点指向newNode else pred.next = newNode; size++; modCount++; }
-
获取头节点并删除头节点
private E unlinkFirst(Node<E> f) { // 获取头节点的值 final E element = f.item; //然后把头节点的后继节点指向next final Node<E> next = f.next; //下面是指控头节点 f.item = null; f.next = null; // help GC //把头节点first指向next first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; }
-
获取尾节点并删除尾节点
private E unlinkLast(Node<E> l) { // 获取尾节点的数值 final E element = l.item; //获取尾节点的前继节点 final Node<E> prev = l.prev; //置空等待GC回收 l.item = null; l.prev = null; // help GC //把尾节点指向prev last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; }
-
删除某个节点
E unlink(Node<E> x) { // 获取x节点的值 final E element = x.item; //获取x的前继节点和后继节点 final Node<E> next = x.next; final Node<E> prev = x.prev; 如果preve是空在头节点指向x的后继节点 if (prev == null) { first = next; } else {//否则用x的前继节点指向它的后继节点,以达到删除该节点 prev.next = next; x.prev = null; } //如果x的后继节点是空则后继节点指向前继节点 if (next == null) { last = prev; } else {//和前继节点类似 next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
-
在看看根据对象返回下标的方法,这个方法有中情况一种是空一种是非空。都是根据遍历链表,一直到找到数值相等然后返回位置。
//该方法是顺序遍历链表查找下标
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;
}
//这个方法是逆序遍历查找对象的下标,这和上面的有点区别它是根据前继节点来实现逆序遍历的
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;
}
接着看基本的功能方法Set/get/clear/remove
下面的方法的是基本操作了无法是遍历链表或者调用前面介绍的方法
public void clear() {
//循环遍历置空即可
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++;
}
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/*
*
*/
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
/*
*
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/*
*
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
最后还有一些关于迭代器的实现都是和ArrayList是相同的就不分析了,除外还实现了queue接口,通过相关队列的方法实现是基于上面分析那些方法来实现的。
总结:LinkedList是基于双链表数据结构,继承了queue接口通过双链表的特点模拟实现了队列的,同时它也是非线程安全的,分析了几篇基于list的子类感觉都大同小异都类似于ArrayList
HashTable源码分析
LinkedList源码分析
Vector源码分析
CopyOnWriteArrayList源码分析
SynchorincedList源码分析