1、LinkedList 概要
LinkedList 是基于链表的数据结构实现的。 链表可分为单向链表和双向链表。
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。
一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。
我们看源码可以明显看出 LinkedList 是属于双向链表
2、LinkedList 源码
2.1、字段
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;
}
}
问题一:transient 是啥,有何作用?
答:transient 标识不会被序列化(除非方法内手动重写了序列化的方法,然后指定使用相关字段去序列化,例如 arrayList 的writeObject)
问题二:E 代表什么?
答:E 是泛型中通配符, 常用的 T,E,K,V,?本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:
- ?表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
2.2、构造方法
/**
* 没做什么动作
*/
public LinkedList() {
}
/**
* 构造一个包含指定元素的列表
* 集合,按照集合返回的顺序
* 迭代器。
*
* @param c 将其元素放入此列表的集合
* @throws NullPointerException 如果指定的集合为空
*/
public LinkedList(Collection<? extends E> c) {
// 不知道有啥用
this();
addAll(c);
}
问题一:this() 在此处的作用是啥?
猜想可能是为了执行父类的构造方法? 非无参数构造方法不会自动执行父类的构造方法吗?
public class Person {
public Person() {
System.out.println("Person.Person()");
}
}
public class PersonMan extends Person{
/**
* 参数的构造方法里啥也没做
*/
public PersonMan() {
}
public PersonMan(String name) {
this();
System.out.println("PersonMan.PersonMan(name) --- " + name);
}
public PersonMan(int age) {
// 没有 this()
System.out.println("PersonMan.PersonMan(age) -- " + age);
}
}
public void test2() {
PersonMan a = new PersonMan("aaa");
System.out.println("==========");
PersonMan b = new PersonMan(1);
}
test()的执行结果:
Person.Person()
PersonMan.PersonMan(name) --- aaa
==========
Person.Person()
PersonMan.PersonMan(age) -- 1
可见:
没有 this() 也会执行父类的构造方法
无参数的构造方法 内容如果为空,实际this() 是可以省略的
结论:为了执行父类的构造方法的猜想不成立,this() 在此处仅仅是个编码习惯问题。
2.3、addAll 方法
/**
* 往末尾批量添加数据
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* 往指定位置批量添加数据
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 检查边界
checkPositionIndex(index);
// toArray 将集合转成数组
Object[] a = c.toArray();
int numNew = a.length;
// 为0 则为插入任何元素,返回false
if (numNew == 0)
return false;
// pred 上一个节点, succ 当前节点
Node<E> pred, succ;
// index == size 表示从末尾插入
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);
// pred == null 说明从头部插入
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
// succ == null 相当于 index == size, 表示从末尾插入
// 遍历完数组后pred为数组里的最后一个节点,即整个列表的最后一个节点故赋值给last
if (succ == null) {
last = pred;
} else {
// 反之则是从开头/中间插入
// 则遍历完数组后pred为数组里的最后一个节点
// pred.next = succ , succ.prev = pred 将链表重新衔接了起来
pred.next = succ;
succ.prev = pred;
}
// 变更size , 记录当前链表长度
size += numNew;
modCount++;
return true;
}
问题一:@SuppressWarnings("unchecked") 的作用是什么?
@SuppressWarnings("unchecked") E e = (E) o;
告诉编译器忽略 unchecked 警告信息: Unchecked cast: 'java.lang.Object' to 'E'
该强制类型转换并未做类型校验,强制转换并不安全,可能会抛出异常导致程序崩溃
因为入参已经决定了 c 一定是继承 E 的, 所以不需要做类型校验
2.4、remove 方法
/*
* 删除某个项 (只移除匹配到的第一个)
*/
public boolean remove(Object o) {
// null 单独处理
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
// 因为 o 和 x.item 都有可能为null
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
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;
// prev == null 说明是头部,直接将头部改为下一节点即可
if (prev == null) {
first = next;
} else {
// 否则说明是中间/末尾移除,需要将前一个节点next 改为 当前的next
// 断开前部当前的链接
prev.next = next;
x.prev = null;
}
// next == null 说明是从尾部移除,那么尾部需要改为上一个节点即可
if (next == null) {
last = prev;
} else {
// 否则说明是中间/末尾移除,需要将下一个节点prev 改为 当前的prev
// 断开当前的后部链接
next.prev = prev;
x.next = null;
}
// 当前项置为null, size 减一
x.item = null;
size--;
modCount++;
return element;
}
2.5、 get 方法
/**
* 获取某个位置的节点内容
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* 根据index 返回对应的节点的对象(分成了两半进行查找,提高效率)
*/
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;
}
}
总结
1、LinkedList 基于链表数据结构实现,属于双向链表
2、LinkedList 增加和删除是通过修改节点的前后指向实现的
3、LinkedList 的查询会将链表分寸两边,进行查找