前言
分别总是在九月,回忆是思念的愁。深秋嫩绿的垂柳,亲吻着我额头。在那座阴雨的小城里,我从未忘记你。济南,带不走的,只有你——天桥下鸡公煲的老板娘。
LinkedList这东西,我没用过,不过今天想看看源码,能不能发现有意思的东西呢?
——2020年09月24日
LinkedList这东西,我天天在用,它竟然被用来作为消息队列,作为JAVA缓存使用。关于这个用法,我还是第一次见,但是在单线程的情况下是可行的。
——2020年11月24日
LinkedList
LinkedList的底层是双端链表,大体就是下图这个样子的吧!(每个节点有pre、next属性,分别指向它的上个节点和下一个节点,并且,从理论上讲,链表可以无限长)
1.构造方法
概述:1.无参构造器,返回一个寂寞
2.传参数为集合的构造器,将集合转换成Object[],并遍历数组,放入链表中
无参构造方法: //返回一个寂寞
public LinkedList() {
}
带参构造方法://传入一个集合类型参数,
public LinkedList(Collection<? extends E> c) {
this();//调用无参的寂寞
addAll(c);//调用addAll();
}
//去看最后调用的方法
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);//判断要添加的位置是否超过链表长度或小于0
Object[] a = c.toArray();//将集合c转换为 Object[]
int numNew = a.length;//新建一个int变量 “新数字” 为 数组长度
if (numNew == 0)//判断“新数字”是否为0,为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) {//遍历这个数组,调整每个节点的pre和next指针指向
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)//如果元素的pred为null,则它(newNode)为新的头结点。否则pred的next指向newNode,重新赋值pred为newNode
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {//遍历完了,看看一开始赋值的元素succ 为null的情况,也就是放在最末尾的情况,新的尾结点为新加的节点
last = pred;
} else {//否则调整新加的元素指针
pred.next = succ;
succ.prev = pred;
}
size += numNew;//长度+1
modCount++;
return true;
}
//这个构造方法还是很nb的,看的一懵逼一懵逼的,也讲不明白,自己一画图就懂了。
2.添加元素–>add(E e)
概述:在链表的尾部添加新的节点并调整相关指针
public boolean add(E e) {
linkLast(e);//又调了这个方法
return true;
}
//这个方法的意思是获取尾结点,然后把新加入的节点设置为尾结点,并调整原尾结点的next指针
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++;
}
3.获取元素–>get(int index)
概述:如果没有下标越界,就去遍历链表,找到结果
public E get(int index) {
checkElementIndex(index);//看看是不是越界
return node(index).item;//调用下面的方法
}
//这个方法的作用是:进行一次二分查找
//如果index小于长度的1/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;
}
}
4.其他
remove()方法咱就不看了哈,LinkedList 继承了 AbstractSequentialList 实现了 Deque。所以说它是一个栈,remove方法移除的是头结点。另外值得一提,这个东西不存在扩容机制,这和ArrayList是完全不同的。
总结
LinkedList查询速度比较慢,需要从头一直遍历,时间复杂度为O(n);
LinkedList增删比较快,只需要调整原链表中个别元素的pre和next指向,add(E) 添加到末尾时间复杂度O(1),add(index, E) 添加第几个元素后,需要先查找到第几个元素,调整指针指向操作,复杂度O(n),remove()时间复杂度为O(1);
至此,我们基本看完了LinkedList中常用的方法了。本人水平有限,如果有不对的地方,敬请指正!