1、LinkedList概述
LinkedList类是双向列表,列表中的每个节点Node都包含了对前一个和后一个元素的引用.
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
}
继承了AbstractSequentialList,它又继承了AbstractList,则是列表的升级版,称为双向链表
实现了List接口,可以进行队列操作
实现了Deque接口,双向链表操作
实现了Cloneable接口,浅拷贝操作
实现了java.io.Serializable,支持序列化。
2、常用api
添加元素
新增元素到末尾
public boolean add(E e) {}
新增元素到特定位置
public void add(int index, E element) {
新增集合到末尾
public boolean addAll(Collection<? extends E> c) {}
新增集合到特定位置
public boolean addAll(int index, Collection<? extends E> c) {}
新增元素到第一个
public void addFirst(E e) {
新增元素到最后一位
public void addLast(E e) {
删除元素
删除特定位置的元素
public E remove(int index) {}
删除第一个元素
public E removeFirst() {}
删除最后一个元素
public E removeLast() {}
删除指定元素
public boolean remove(Object o) {}
删除全部
public void clear() {}
查询
查询第一个元素
public E getFirst() {
查询最后一个元素
public E getLast() {
是否包含元素
public boolean contains(Object o) {
查询长度
public int size() {}
根据下标查询
public E get(int index) {}
查询某个元素第一次出现的下标
public int indexOf(Object o) {}
查询某个元素最后一个出现的下标
public int lastIndexOf(Object o) {
修改
修改特定元素
public E set(int index, E element) {
队列操作
检索,但是不删除第一个元素
public E peek() {
检索,但是不删除第一个元素
public E element() {
检索并且删除第一个元素
public E poll() {
检索并且删除第一个元素
public E remove() {
添加元素
public boolean offer(E e) {
添加到第一个元素
public boolean offerFirst(E e) {
插入元素到最后一个
public boolean offerLast(E e) {
检索,但是不删除第一个元素
public E peekFirst() {
检索,但是不删除最后一个元素
public E peekLast() {
检索并且删除第一个元素
public E pollFirst() {
检索并且删除最后一个元素
public E pollLast() {
插入元素到第一个位置
public void push(E e) {
删除一个元素
public E pop() {
删除第一次出现的元素
public boolean removeFirstOccurrence(Object o) {
删除最后一个出现的元素
public boolean removeLastOccurrence(Object o) {
迭代器
public ListIterator<E> listIterator(int index) {
转换成数组
public Object[] toArray() {
转换成特定数组
public <T> T[] toArray(T[] a) {
以上所有的用法应该来说很简单,就不介绍api的用法了。
3、源码分析
/**
* 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) {
this();
addAll(c); //这里调用的事addAll方法,这是一个公共方法
}
1、新增,add源码
public boolean add(E e) {
linkLast(e); // 这里是新增的具体工作
return true; // 这里都是true,好像说只要执行了add方法,就肯定会返回true
}
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++;
}
//看看linkLast这个方法中的实现原理,
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//这里做了个交换,把原来最后一个节点赋给l,在新建一个节点newNode,在把这个新街点newNode赋给新集合最后一个节点,其实就是在原有集合上在末尾追加一个新节点。
if (l == null)
first = newNode;
else
l.next = newNode;
//判断l是不是空,也就是原有集合是不是空,如果是空,那么新增的节点newNode就赋给首个节点,如果不是空,则把上个节点指向最后一个节点,这样就在原有集合添加了一个新节点。
2.新增整个集合addAll
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; //检查添加的集合是否有数据,如果没有,则直接返回false
Node<E> pred, succ; //定义两个节点
if (index == size) {
succ = null;
pred = last; //如果下标等于长度,则把最后一个节点赋给pred
} else {
succ = node(index);
pred = succ.prev; //否则把下标所在的节点赋给succ,这个节点的前节点引用赋给pred
}
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; //把新节点赋给pred
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred; //互相引用,这里可以看出链表是双向的,互相引用
}
size += numNew;
modCount++;
return true;
}
//这里在介绍下node()方法,其实是根据下标查询这个节点
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { //与size>>1比较,其实大概就是size长度的一半,这样其实加快了查询速度,两头查询。
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;
}
}
2、删除
public E remove(int index) {
checkElementIndex(index); //检查是否合法
return unlink(node(index));
}
// 以上用了unlink()方法,这介绍下unlink做了哪些工作。
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;
}
清空数据
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
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; //长度为0
modCount++;
}
3、查询
public E get(int index) {
checkElementIndex(index); //检查下标合法性
return node(index).item; //用了node方法
}
4、修改
public E set(int index, E element) {
checkElementIndex(index); //检查合法性
Node<E> x = node(index); //查询到index所在的数据
E oldVal = x.item; //获取当前元素
x.item = element; //把新元素赋给当前位置
return oldVal;
}
4、总结:
以上的增删改查中所有方法都会涉及到一个很重要的类Node,这个类其实是LinkedList的核心了。我们所有操作都会围绕这个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;
}
}
看Node类代码很清晰,有一个具有三个参数的构造函数,当我们
新增一个元素时,我们会new一个Node,这个Node必须具有前后两个元素的引用,还有这个元素本身,这样,除了首尾两个节点,每个节点都会跟前后两个节点产生关联,我们增删改查,都是围绕这个节点的引用来进行操作。当新增元素时,先生成一个有带元素的前后引用的节点,然后断开具体某处的引用,然后在把新节点加进去,这有点像水管不够,然后在中间加水管一样的道理。
这个跟ArrayList是不一样的。