1、LinkedList的基础知识
1.1、基础概念
一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
1.2、链表存储
基于双向链表实现
prev:指向前一个节点
next:指向后一个节点
first和last分别指向链表的头节点和尾节点,当进行添加操作时,只需要将删除元素的前一个节点的next指向其后一个节点的prev,将后一个节点的prev指向前一个节点的next。
1.3、 适用场景
LinkedList适用于数据频繁添加删除操作,写多读少的情况。
1.4、与ArrayList的区别
ArrayList | LinkedList | |
获取指定元素 | 速度很快 | 需要从头开始查找元素 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置位置添加/删除 | 需要移动元素 | 不需要移动元素 |
内存占用 | 少 | 较大 |
2、LinkedList源代码阅读
2.1、继承类与接口实现
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
继承
AbstractSequentiaList<E>:所以LinkedList可以实现其父类的所有方法。
实现接口
- List接口:LinkedList是List接口的双向链表的实现
- Deque接口:可以看作是双端队列的一种实现
- Cloneable接口:可以实现clon()方法,对数据进行复制
- Serializable接口:LinkedList是可序列化的
2.2、构造方法
LinkedList一共有两种构造方法
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkekList():无参的构造方法,可以初始化一个空的LinkedList
LinkedList(Collection<? extends E> c):在这个构造方法方法中调用了addAll()方法,目的是在初始化LinkedList对象时将传入的集合中的元素也添加进去
2.3、基础方法
LinkedFirst(E e)
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
在调用LinkedFirst时,会创建一个新的节点并且将新节点添加在链表的头部,过程中首先判断链表中是否有头节点(即链表是否为空);若为空,则直接将last指向新节点;不为空,则将原头节点的prev与指向新节点。
LinkedLast<E e>
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++;
}
与LinkedFiest类似,在调用时,会创建一个新的节点并且将新节点添加在链表的尾部,过程中首先判断链表中是否有尾节点(即链表是否为空);若为空,则直接将first指向新节点;不为空,则将原头节点的next与指向新节点。
LinkedBeore(E e,Node<E> succ)
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
在某个节点前添加元素,在调用LinkedBefore时,首先获取传进来的节点prev指针,并使其指向新节点,然后判断新节点的prev指针是否为空,则新节点为头节点;若不为空,则使新节点的next指针指向传进来的节点prev指针
UnLinkedFirst(Node<E> f)
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
调用UnLinkedList方法时,首先将头元素中的元素和next指针置为空,然后将first指向下一个节点,判断下一个节点的next指针是否为空,为空则整个链表为空,将last指向null,不为空则将下一个节点的prev指针置为空
UnLinkedLast(Node<E> l)
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
与UnLinkedFirst方法类似,不过是判断需前一个节点的prev指针,为空则是空链表;不为空则将前一个节点的next指针置为空
UnLinked(Node<E> x)
E unlink(Node<E> x) {
// assert x != null;
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;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
类似于添加元素,在删除时,获取删除元素的前后节点,并将前节点的next指针指向后一个节点的prev指针。
2.4核心方法
对于add(),remove(),set(),get()方法做解释
对于linkedList的方法,基本就是通过修改指针来进行方法的完成,不需要复制元素,更加高效
add()大致就是创建新的节点,然后去改变first、last、prev,next等指针即可;remove()方法类似,删除节点置空,并修改prev,next等指针,修改效率高。
2.4.1、 add方法
LinkedList的添加一共有6种
addFirst(E e)
public void addFirst(E e) {
linkFirst(e);
}
内部调用LinkedFirst方法,在首部添加元素,参照基础方法
addLast(E e)
public void addLast(E e) {
linkLast(e);
}
内部调用LinkedLast方法,在最后添加元素,参照基础方法
add(E e)
public boolean add(E e) {
linkLast(e);
return true;
}
内部调用LinkedLast方法,在最后添加元素,参照基础方法,返回true或false
add(int index,E element)
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
根据下边添加元素,如果传入的下标等于链表长度,则在尾部添加元素,调用 LinkedLast方法;不为空则调用LinkBerore方法,将元素添加在index对应的node节点前
这里值得注意的是,链表是没有下标的,所以对应下标的位置是通过node()方法获取index的值
addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
调用另一个addAll(int index,Collection c) 方法,依次添加集合中的所有元素
addAll(int index,Collection c)
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 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) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
2.4.2、删除方法(内部调用方法在基础方法中已介绍,这里不做过多解释)
LinkedList的删除一共有9种,与add方法区别不大,我主要对其中5中做简单介绍
remove()
public E remove() {
return removeFirst();
}
删除链表中的头元素,调用removeFirst(),在下边做介绍
remove(Object o)
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
移除列表中一个指定元素o,遍历链表,如果o不存在,则调用unlin方法删除节点,但是此时删除的时元素为null的节点,即链表不变;如果不为null,则遍历链表后,调用unlink方法删除与传入对象相等的节点。
remove(int index)
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
删除指定下标的节点,判断下标处的元素,并调用unlink方法
removeFirst()
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
删除头元素,定义first指针,如果没有,则抛异常,有则调用unlinkFirst方法
2.4.3 获取元素
获取元素共有三种方法,获取头元素,获取尾元素,遍历链表获取指定元素
这里与上边基本相同
get(int index)
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
通过node()方法取得index对应的节点后返回元素值
2.4.5、set方法
set(int index,E element)
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
基本还是获取下标以及node()方法,让新的元素去替换旧的元素即可