Java中List是一个必须要掌握的基础知识,List是一个接口,实现List接口的基础类有很多,其中最具有代表性的两个:ArrayList和LinkedList。
今天主要讲解一下LinkedList的基本数据接口和源码分析。
LinkedList的底层则是链表的结构,它可以进行高效的插入和移除的操作,基于一个双向链表的结构。
源码变量定义可以看到:
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node last;
用一个图理解一下:
LinkedList的Node节点结构
prev是存储的上一个节点的引用。
element是存储的具体的内容。
next是存储的下一个节点的引用。
LinkedList的整体结构图
从图解中可以看出,有好多的Node,并且还有first和last这两个变量保存头部和尾部节点的信息。
还有就是它不是一个循环的双向链表,因为他前后都是null,这个也是我们需要注意的地方。
3.1 构造方法:
/**
* Constructs an empty list.
*/
public LinkedList() {
//无参构造方法
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection extends E> c) {
//以集合c形式,把所有元素插入链表中
this();
addAll(c);
}
3.2 添加元素方法:
/**
* Appends the specified element to the end of this list.
*
* This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* 将集合插入到链表的尾部
*/
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;
//如果新增元素为0,则不添加,并且返回false
if (numNew == 0)
return false;
//定义index节点的前置节点,后置节点
Node pred, succ;
//判断是不是链表的尾部,如果是,那么就在链表尾部追加数据
//尾部的后置节点一定是null,前置节点是队尾
if (index == size) {
succ = null;
pred = last;
} else {
//如果不是在链表的末尾而是在中间位置的话,
//取出index节点,作为后继节点
succ = node(index);
//index节点的前节点,作为前驱的节点
pred = succ.prev;
}
//链表批量的增加,去循环遍历原数组,依次去 插入节点的操作
for (Object o : a) {
@SuppressWarnings("unchecked")
//类型转换
E e = (E) o;
// 前置节点为pred,后置节点为null,当前节点值为e的节点newNode
Node newNode = new Node<>(pred, e, null);
// 如果前置节点为空, 则newNode为头节点,否则为pred的next节点
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
// 循环结束后,如果后置节点是null,说明此时是在队尾追加的
if (succ == null) {
last = pred;
} else {
//否则是在队中插入的节点 ,更新前置节点 后置节点
pred.next = succ;
succ.prev = pred;
}
// 修改数量size
size += numNew;
//修改modCount
modCount++;
return true;
}
3.3 Node节点:
private static class Node {
//值
E item;
//后者 指向下一节点引用
Node next;
//前者 指向前一节点引用
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
从上述Node节点源码可以看出,LinkedList的链表数据结构,是双向的每个元素既存储着上一个元素的引用又存储着下一个元素的引用。
addAll方法之后我们再看看其他的添加元素的方法,分为了头部addFist和尾部addLast。
addFist(E e)
将e元素添加到链表并且设置其为头节点Fist
public void addFirst(E e) {
linkFirst(e);
}
/**
* Links e as first element.
* 将e元素弄成链接列表的第一个元素
*/
private void linkFirst(E e) {
final Node f = first;
//链表开头前驱为空,值为e,后继为f
final Node newNode = new Node<>(null, e, f);
first = newNode;
//若f为空,则表明列表中还没有元素,last也应该指向newNode
if (f == null)
last = newNode;
else
//否则,前first的前驱指向newNode
f.prev = newNode;
size++;
modCount++;
}
3.4 Poll()方法:
/**
* Retrieves and removes the head (first element) of this list.
* 返回链表中第一个元素,如果链表是空,则返回空
* @return the head of this list, or {@code null} if this list is empty
* @since 1.5
*/
public E poll() {
final Node f = first;
return (f == null) ? null : unlinkFirst(f);
}