LinkedList学习
这篇文章主要讲的是LinkedList。从数据结构,增删改查的原理入手。有错误的地方,欢迎大家指出,谢谢!
开始学习LinkedList首先要了解它所使用的数据结构。LinkedList使用的是Node,先了解LinkedList中的私有静态类Node。
Node中共有三个字段:
item:存储数据
next:该字段存储下一个Node的地址
prev:该字段存储上一个Node的地址
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;
}
}
首先先来了解add方法和存储数据的示意图
先看add方法代码。add方法中直接调用linkLast方法,意思是将e添加到链表的末尾。
public boolean add(E e) {
linkLast(e);
return true;
}
再来看linkLast方法代码。这里我用三张图来演示它的存储过程。
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++;
}
第一次添加元素:
第二次添加元素:
第三次添加元素:
查找元素
直接查看查找元素的代码:首先判断index是否小于size/2,如果是,就从first开始正向遍历,一个一个的通过next向后找到对应的Node。。如果不小于size/2就反向遍历。一个一个的通过prev向前找到对应的Node。
Node<E> node(int 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;
}
}
再来看add的另一个方法
//在指定位置插入元素
public void add(int index, E element) {
checkPositionIndex(index);//检查index,大于等于0并且小于size
if (index == size)//如果index == size其实就是和add(E e)方法一致
linkLast(element);
else//node(index)就是查找出目前index所在的那个元素
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {//e为即将插入的元素,succ为已经插入的元素
final Node<E> pred = succ.prev;//取出原来的前一个元素
final Node<E> newNode = new Node<>(pred, e, succ);//创建新元素
succ.prev = newNode;//因为succ在新元素的前面,所以succ的prev指向新元素
if (pred == null)//如果前一个元素为null,说明这个是first,就把新元素设置为first
first = newNode;
else//否则将前一个元素的next指向新元素
pred.next = newNode;
size++;//增加数量
}
直接看图:
addAll方法
addAll(int index, Collection<? extends E> c)和addAll(Collection c)其实也是调用的addAll(int index, Collection c)
首先看源码,可以得出大概的流程就是找到index对应的元素,以及index对应的前一个元素,因为要在这两个元素中添加c中的所有元素。找到这两个元素后就遍历c,新建元素,将新建元素的地址赋值给前一个元素的prev。遍历完成后,判断succ是否为null,如果为null就将c的最后一个元素设置为last
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);//检查index是否越界
Object[] a = c.toArray();
int numNew = a.length;//总的需要添加的数量
if (numNew == 0)
return false;
LinkedList.Node<E> pred, succ;
if (index == size) {//如果index==size说明在最后添加
succ = null;//index对应的位置的元素就是null
pred = last; //前一个为last也就是index-1对应的元素
} else {
succ = node(index);//如果不是在最后就取出index对应的元素
pred = succ.prev;//前一个就是对应元素的前一个
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
//接在元素的后面
LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, null);
if (pred == null)//说明原来没有元素,就将新建的元素设置为first
first = newNode;
else//就把前面一个元素的next指向新建的元素
pred.next = newNode;
pred = newNode;
}
//此时pred为最后一个需要添加的元素
if (succ == null) {
//如果最开始找到的index对应的元素为null,就是从最后面开始添加,那么此时的pred就是最后一个
last = pred;
} else {
//如果最开始找到的index对应的元素不为null,就将此时的pred的next指向最开始index对应的元素
//然后将最开始index对应的元素的prev指向此时的pred
pred.next = succ;
succ.prev = pred;
}
size += numNew;//增加size
modCount++;
return true;
}
addFirst方法
addFirst方法是将元素添加到列表第一个。
public void addFirst(E e) {
linkFirst(e);//直接调用linkFirst方法
}
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);//将新元素的next指向f
first = newNode;//因为要添加到第一个,所以first需要是这个新元素
if (f == null)//如果原来的first为null表示列表中没有元素,所以这个新元素也是最后一个
last = newNode;
else//如果原来的first不是null,就将原来的first的prev指向新元素
f.prev = newNode;
size++;//增加size
}
addLast方法
addFirst方法是将元素添加到列表最后一个。
public void addLast(E e) {
linkLast(e);//直接调用linkLast方法
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);//将新元素的prev指向原来的last
last = newNode;//将新元素赋值给last,因为是添加到列表最后,所以新元素为最后一个元素
if (l == null)//如果原来的lastweinull,说明原来列表没有元素,所以新元素也应该是first
first = newNode;
else//如果原来的last不为null,就将原来的last的next指向新元素
l.next = newNode;
size++;//增加size
}
clear()
从此列表中删除所有元素。 此调用返回后,列表将为空。循环将所有元素都设置为null,并且将first和last都设置为null,方便GC回收。将size设置为0.
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;
}
contains方法
查找链表中是否存在指定的元素。查找列表中是指定元素的第一个下标。如果为null就返回第一个为null的下标,如果不为null就返回第一个equals为true的下标。
其中lastIndexOf其实就是反向遍历
public boolean contains(Object o) {
return indexOf(o) != -1;
}
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;
}
get方法
get其实就是调用了查找元素的方法,前面有解释。
getFirst和getLast必须保证列表中至少存在1个元素不然会抛出异常直接返回first和last的item。
remove方法
public E remove(int index) {//通过下标删除
checkElementIndex(index);//检查下标是否正常
return unlink(node(index));//调用unlink方法,删除都是调用此方法
}
E unlink(Node<E> x) {//x不能为null
final E element = x.item;
final Node<E> next = x.next;//前一个元素
final Node<E> prev = x.prev;//后一个元素
if (prev == null) {//如果前一个元素为null,说明x是first,因为要删除x,所以就要让next为first
first = next;
} else {//如果前一个元素不为null,就把前一个元素的next指向下一个元素的地址
prev.next = next;
x.prev = null;
}
if (next == null) {//如果下一个元素为null那么就要把前一个元素设置为last
last = prev;
} else {//如果不为null,就要把下一个元素的prev指向x前面的元素
next.prev = prev;
x.next = null;
}
x.item = null;
size--;//减少size
return element;//返回删除的元素
}
//删除指定元素,如果存在就删除第一次出现的元素并返回true,如果没有就返回false
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
//如果需要删除的元素为null,就遍历找到第一个null的下标对应的元素
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
//如果需要删除的元素不是null,就删除第一个equals为true的下标对应的元素
unlink(x);
return true;
}
}
}
return false;
}
set方法
public E set(int index, E element) {
checkElementIndex(index);//判断index范围
Node<E> x = node(index);//查询出index对应的元素
E oldVal = x.item;//设置新的值并且返回原来的值
x.item = element;
return oldVal;
}
获取列表长度
public int size() {
return size;
}
常见面试题
ArrayList与LinkedList的区别?
1、ArrayList的查找和访问元素的速度较快,但新增,删除的速度较慢,因为需要确保容量足够,所以有可能需要扩容有,操作还会移动元素位置,所以速度较慢。LinkedList的查找和访问元素的速度较慢,但是他的新增,删除的速度较快,因为是使用链表的形式。
2、ArrayList需要分配一块连续的内存空间,LinkedList不需要连续的内存空间
3、两个线程都不安全