吐槽
今天看到别人说的一句话感触蛮深的一个人把时间用到哪里,他的成就在那里 自己最近真的蛮浮躁的,对自己的能力莫名其妙的错误估计,但实际上自己的真实的水平什么的自己还是要有点B数,既然选择这条路的话,还是好好的自己一步一步走下去吧。
LinkedList
这个是Collection阵营的一个集合
其实和ArrayList类似,但是其底层的实现是用双向链表来进行实现的
我们还是先想几个问题带着问题去学习
- LinkedList的底层用什么实现的?
- LinkedList的插入删除的时候的高效如何实现?
- LinkedList的线程是否安全
- LinkedList和ArrayList的比较
-List,ArrayList,LinkedList的区别
源码分析
成员变量和继承关系
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable {
//集合元素的数量
transient int size;
//链表的头结点
transient LinkedList.Node<E> first;
//链表的尾结点
transient LinkedList.Node<E> last;
private static final long serialVersionUID = 876323262645176354L;
先看下继承关系
LinkedList 继承自 AbstractSequentialList 接口,同时了还实现了 Deque, Queue 接口。
我们看到成员变量就三个,简单明了
- 头结点
- 尾结点
- 集合元素的个数
然后我们去看下结点类
private static class Node<E> {
//元素的值
E item;
//后继结点
LinkedList.Node<E> next;
//前驱结点
LinkedList.Node<E> prev;
Node(LinkedList.Node<E> var1, E var2, LinkedList.Node<E> var3) {
this.item = var2;
this.next = var3;
this.prev = var1;
}
}
发现这块是两个结点
一个前驱结点和一个后继结点
所以,LinkedList是一个双向链表
构造方法
这个比ArrayList简单清晰多了
如果是传入的空的话,集合的元素为0
public LinkedList() {
this.size = 0;
}
//如果传入的是集合的话
//用addAll把集合全部加入链表中
public LinkedList(Collection<? extends E> var1) {
this();
this.addAll(var1);
}
allAll()全部添加
看下这块的过程
//传入一个集合的时候
public LinkedList(Collection<? extends E> var1) {
this();
this.addAll(var1);
}
//以size为下标,插入集合的所有元素
public boolean addAll(Collection<? extends E> var1) {
return this.addAll(this.size, var1);
}
//传入下标,和集合对象
public boolean addAll(int var1, Collection<? extends E> var2) {
//先检查是否越界 看了下调用 就是在区间【0,size】中没
this.checkPositionIndex(var1);
//然后把集合对象全部转换成数组
Object[] var3 = var2.toArray();
//获取到数组的长度
int var4 = var3.length;
//如果数组是0的话放回false,不添加了
if (var4 == 0) {
return false;
} else {
//前置结点
LinkedList.Node var5;
//后置结点
LinkedList.Node var6;
//在链表尾部追加数据
if (var1 == this.size) {
var6 = null;//尾部数据一定是null
var5 = this.last;//前置结点是队尾
} else {
var6 = this.node(var1);
var5 = var6.prev;
}
Object[] var7 = var3;
int var8 = var3.length;
//批量添加
for(int var9 = 0; var9 < var8; ++var9) {
Object var10 = var7[var9];
LinkedList.Node var12 = new LinkedList.Node(var5, var10, (LinkedList.Node)null);
if (var5 == null) {
this.first = var12;
} else {
var5.next = var12;
}
var5 = var12;
}
if (var6 == null) {
this.last = var5;
} else {
var5.next = var6;
var6.prev = var5;
}
this.size += var4;
++this.modCount;
return true;
}
}
private void checkPositionIndex(int var1) {
if (!this.isPositionIndex(var1)) {
throw new IndexOutOfBoundsException(this.outOfBoundsMsg(var1));
}
}
private boolean isPositionIndex(int var1) {
return var1 >= 0 && var1 <= this.size;
}
//根据index 查询出Node,
LinkedList.Node<E> node(int var1) {
LinkedList.Node var2;
int var3;
if (var1 < this.size >> 1) {
var2 = this.first;
for(var3 = 0; var3 < var1; ++var3) {
var2 = var2.next;
}
return var2;
} else {
var2 = this.last;
for(var3 = this.size - 1; var3 > var1; --var3) {
var2 = var2.prev;
}
return var2;
}
}
get和set方法
public E get(int var1) {
this.checkElementIndex(var1);
return this.node(var1).item;
}
public E set(int var1, E var2) {
this.checkElementIndex(var1);
LinkedList.Node var3 = this.node(var1);
Object var4 = var3.item;
var3.item = var2;
return var4;
}
逻辑都是很简单的那种,我们发现这块就是调用node()方法
//函数会以O(n/2)的性能去获取一个节点
Node<E> node(int index) {
// assert isElementIndex(index);
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;
}
}
就是判断index是在前半区间还是后半区间,如果在前半区间就从head搜索,而在后半区间就从tail搜索。而不是一直从头到尾的搜索。如此设计,将节点访问的复杂度由O(n)变为O(n/2)。
真的是设计的很巧妙
删除
public boolean remove(Object var1) {
LinkedList.Node var2;
if (var1 == null) {
for(var2 = this.first; var2 != null; var2 = var2.next) {
if (var2.item == null) {
this.unlink(var2);
return true;
}
}
} else {
for(var2 = this.first; var2 != null; var2 = var2.next) {
if (var1.equals(var2.item)) {
this.unlink(var2);
return true;
}
}
}
return false;
}
查询
//这个是挨个从头开始遍历,获取第一次出现的位置
public int indexOf(Object var1) {
int var2 = 0;
LinkedList.Node var3;
if (var1 == null) {
for(var3 = this.first; var3 != null; var3 = var3.next) {
if (var3.item == null) {
return var2;
}
++var2;
}
} else {
for(var3 = this.first; var3 != null; var3 = var3.next) {
if (var1.equals(var3.item)) {
return var2;
}
++var2;
}
}
return -1;
}
//倒着遍历,然后找到出现的最后一次的位置
public int lastIndexOf(Object var1) {
int var2 = this.size;
LinkedList.Node var3;
if (var1 == null) {
for(var3 = this.last; var3 != null; var3 = var3.prev) {
--var2;
if (var3.item == null) {
return var2;
}
}
} else {
for(var3 = this.last; var3 != null; var3 = var3.prev) {
--var2;
if (var1.equals(var3.item)) {
return var2;
}
}
}
return -1;
}
toArray
public Object[] toArray() {
//又建立一个新的数组
Object[] var1 = new Object[this.size];
int var2 = 0;
//挨个遍历,,,然后把美国元素fang放到新的数组里面233
for(LinkedList.Node var3 = this.first; var3 != null; var3 = var3.next) {
var1[var2++] = var3.item;
}
return var1;
}
回答问题
问: LinkedList的底层用什么实现的?
答:LinkedList的底层是用双向链表来实现的
问:LinkedList的插入删除的时候的高效如何实现?
答:首先是因为LinkedList的底层是用双向链表实现的,所以插入和删除比较高效,不需要大量的数据移动,其次,在获取结点的时候,核心的方法是node()方法,采用的思想是折半查找。判断index是在前半区间还是后半区间,如果在前半区间就从head搜索,而在后半区间就从tail搜索。而不是一直从头到尾的搜索,将将节点访问的复杂度由O(n)变为O(n/2)。
问:LinkedList线程安全吗?为什么?
答:线程不安全
问:LinkedList和ArrayList的比较
答:1 对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
3当进行频繁的插入删除的操作的时候,LinkedList的性能会更加好一点。
问:ArrayList,LinkedList与ArrayList的不同
答:第一点:List是接口类,LinkedList和ArrayList是实现类
第二点:ArrayList是动态数组(顺序表)的数据结构。顺序表的存储地址是连续的,所以在查找比较快,但是在插入和删除时,由于需要把其它的元素顺序向后移动,耗时操作。
第三点:LinkedList是链表的数据结构。链表的存储地址是不连续的,每个存储地址通过指针指向,在查找时需要进行通过指针遍历元素,所以在查找时比较慢。由于链表插入时不需移动其它元素,所以在插入和删除时比较快。