一、LinkedList的成员变量
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;
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类中有一个静态内部类Node,集合中的元素值就是存储在Node中的item。
LinkedList的成员变量first和last维护了该链表的首节点和尾节点,然后还维护了该链表的长度size。
由此可以看出LinkedList可以双向链表,不循环。
二、LinkedList常见api
1、add(E e)方法
如图所示往一个非空链表中插入O3元素需要做的就是维护指针a,指针b,指针last指向O3
public boolean add(E e) {
linkLast(e);
return true;
}
// 从方法的命名来看,新增一个元素的时候,是往链表的最后一个节点插入的。
void linkLast(E e) {
final Node<E> l = last; // 新开辟一个变量l指向尾节点,防止当前尾节点指向丢失
final Node<E> newNode = new Node<>(l, e, null); //创建节点时,维护了节点的prev,即上图的指针b,元素e,next指向空
last = newNode; // 该对象的尾指针指向新元素
if (l == null)
first = newNode; // 链表为空的时候,该对象的头指针也指向了这个新元素
else
l.next = newNode; // 链表不为空的时候,维护了上图的指针a
size++; //链表长度加一
modCount++; //链表修改次数加一
}
2、add(int index, E element)方法 往指定的位置插入一个元素
学过数据结构都知道,想要让上图O1和O2插入一个元素O3,需要维护以下a,b,c,d四个指针
public void add(int index, E element) {
checkPositionIndex(index); // 校验元素插入的位置,不能小于0.也不能大于对象的成员变量size,不然就断链了
if (index == size) // 如果元素插入的位置刚好等于成员变量size,相当于就是往链表的最后一个节点插入,代码如上续
linkLast(element);
else
linkBefore(element, node(index)); // 如果元素插入的位置小于成员变量size,相当于是往链表的中间位置插入的
// node(index)方法就是找到当前这个位置的元素
}
void linkBefore(E e, Node<E> succ) { // 这里元素e就是我们要插入的03 succ就是上图的O2
// assert succ != null;
final Node<E> pred = succ.prev; // 取O2的前置节点,防丢失,有人说first不是指向了O1吗,咋能丢失勒,这里是我画的图刚好3个元素,导致first指向了O1。
final Node<E> newNode = new Node<>(pred, e, succ); //存入元素值e,维护了上图指针b,指针c
succ.prev = newNode; // 维护了上图的指针d
if (pred == null)
first = newNode; // 前置节点为空的话,就直接对象的first指针指向newNode
else
pred.next = newNode; // 前置节点不为空的话,维护指针a
size++; //链表长度加一
modCount++; //链表修改次数加一
}
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;
}
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
3、addAll(Collection<? extends E> c) 和 addAll(int index, Collection<? extends E> c)
这两个方法都是往指定位置插入一批元素,还是借用上面的图
public boolean addAll(Collection<? extends E> c) {
return addAll(size, 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为空就行
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); // 存入元素值e,维护了上述指针b
if (pred == null)
first = newNode; // 前置节点为空的话,就直接对象的first指针指向newNode
else
pred.next = newNode; // 前置节点不为空的话,维护指针a
pred = newNode; // pred指向newNode,这里有人会问c,d指针不管了吗,这里pred指向newNode,后面又会有新的newNode2去维护newNode2的a,b指针,即newNode的c,d指针(实在是妙哇)。
}
// 这里维护最后一个newNode的c,d指针
if (succ == null) { // succ为空,即上续说的往链表的最后一个元素后面插入,那就不用维护c,d指针了,last直接指向pred即可
last = pred;
} else { // succ不为空
pred.next = succ; // 维护指针c
succ.prev = pred; // 维护指针d
}
size += numNew;
modCount++;
return true;
}
4、addFirst(E e)和addLast(E e)
public void addLast(E e) { // 往最后面插入元素,和add(E e)方法一致,不赘诉了
linkLast(e);
}
addFirst相当于是往链表的最前面插入一个元素,就是数据结构链表的头插法。
想要将O3插入到O1前面,得到如下链表,就需要维护a,b指针,并将first指向O3
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first; // 新定义一个变量f指向O1,防止O1的指针丢失
final Node<E> newNode = new Node<>(null, e, f); // 存入元素值e,维护指针a
first = newNode; // first指向新节点newNode
if (f == null)
last = newNode; // f为空说明链表为null,first和last同时指向newNode即可
else
f.prev = newNode; // f不为空,维护指针b
size++;
modCount++;
}
5、get(int index)
public E get(int index) {
checkElementIndex(index);
return node(index).item; // 这个很简单,上面讲过根据index取节点,这里返回节点的属性item即可
}
6、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;
}
7、indexOf(Object o)
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;
}
8、contains(Object o)
public boolean contains(Object o) { // 判断一个链表是否包含某个元素就是调用上面的定位元素的坐标
return indexOf(o) != -1;
}
9、remove()和removeFirst()
public E remove() {
return removeFirst(); // LinkedList的remove方法居然是移除第一个元素,我也才知道,一直以为是最后一个,我丢
}
removeFirst()方法移除第一个元素,如下图所示,我们需要做的就是断开a,b指针,first指向O2,O1释放内存
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item; // 取f的元素值,用于最后返回
final Node<E> next = f.next; // 找到O1的下一根节点O2
f.item = null; // 元素值置为null,元素值所在堆空间等待垃圾回收
f.next = null; // 断开指针a
first = next; // first指向O2
if (next == null)
last = null; // 如果next为空,即删除完这个元素,整个链表为空了,first和last都指向了null
else
next.prev = null; // 断开指针b
size--;
modCount++;
return element;
}
// 思考一下这里为什么没有将f置为null,不置为空的话,f指向的Node数据堆内存能被垃圾回收吗。
// 这里f指向的Node堆空间,目前仅被f引用,当这个方法执行完,f从栈帧弹出,那么堆那块空间就0引用了,就是垃圾,能够被回收了
10、remove(int index)
根据指定下标删除节点,如下图所示,删除O3,需要断开指针b,指针c,指针a指向O2,指针d指向O1
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
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; // 前置节点为空,说明它自身就是first,自身要被删除了,那first指向next
} else {
prev.next = next; // 指针a指向O2
x.prev = null; // 断开指针b
}
if (next == null) {
last = prev; // 后置节点为空得话,自身要被删除了,那last指向prev
} else {
next.prev = prev; // 指针d指向O1
x.next = null; // 断开指针c
}
x.item = null; // 元素值置为null,元素值所在堆空间等待垃圾回收
size--;
modCount++;
return element;
}
三、最后
LinkedList里面的一些方法核心的一些实现应该都差不多了,还有一些poll(),offer(E e)底层也都是add和remove,大家可以自行查看。本文中很多地方提到了指针这个概念,正常java里面是没有指针这个东西的,java里面叫引用,其实都一个意思吧,指针结合数据结构更好理解吧。
喜欢文章点赞,收藏,分享,么么哒! 原创不易,转载请贴出处。