类图
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
}
- 实现Serializable
- 实现Cloneable
- 实现Deque
- 实现Queue
- 继承AbstractList
- 继承AbstractSequentialList
LinkList
可以复制, 可以序列化, 拥有双端队列的性质。
内部属性
-
transient int size = 0 列表大小
-
transient Node last 最后一个元素
-
transient Node first 第一个元素
-
其中的元素为一个链表节点
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
- 构造出来一个LinkedList包含指定的元素(使用给定集合元素构造出来一个LinkedList)
/**
* 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);
}
// 方法的重载, 需要函数名相等, 参数类型和参数个数必须不完全相等,返回值类型和修饰符可以相等也可以不相等。
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
添加元素
添加首部、尾部
add(E e)
在列表的尾部添加一个元素,这个方法等效于 addLast方法。
/**
* Appends the specified element to the end of this list.
*
* <p>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;
}
linkLast(e);
元素 e 将作为最后一个元素。 如果最后一个元素是空的, 也就是说第一次添加, 那添加的元素就是第一个元素, 否则的话, 就需要用之前最后一个元素和当前添加的元素连接起来。
- transient Node last 指向最后一个元素的指针
- transient Node first 指向第一个元素的指针
- newNode 新建一个节点
- 把当前最后一个节点设置为新插入节点的前驱节点
看完这个方法之后就可以很清楚的看到LinkedList是使用双向循环链表来作为自己的底层实现。
/**
* Links e as last element.
*/
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++;
}
linkFirst(E e)
在列表的开头添加一个新的节点。步骤和在尾部添加类似。
/**
* Links e as first element.
*/
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++;
}
添加到指定位置
add(int index, E element)
如果指定插入的位置刚好是末尾的话,使用 linkLast(element); 插入
否则的话,使用 linkBefore(element, node(index));
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
linkBefore(E e, Node succ)
- 位运算 左移右移
- size >> 1 除以2
- size << 1 乘以2
- e 要插入的元素
- succ 要位置的元素
插入过程如下:得到要插入index位置的元素后, 在此得到它的前驱节点。并且新建一个节点(设置好相应的前驱节点和后继节点关系)最后调整它的前驱节点和后继节点的前驱和后继关系,修改元素个数,插入完毕。
/**
* Inserts element e before non-null Node 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++;
}
node(int index)
此方法的作用就是返回 index 索引 位置的节点。会先判断当前要插入的位置在列表的前半部分还是后半部分,然后返回 index 索引 位置的节点
/**
* Returns the (non-null) Node at the specified element 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;
}
}
改变指定位置的元素
- 第一步检查当前位置是否越界
- 然后这个位置的节点
- 替换原有节点的值
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
得到元素
getFirst() getLast()
first last一直在记录着链表的首节点和尾部节点。直接返回该节点的值。
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
删除元素
删除头部、尾部元素
removeFirst
删除并且返回链表的第一个元素
/**
* Removes and returns the first element from this list.
*
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
unlinkFirst
取消首部的链接, 删除第一个元素。第一步得到首部节点的元素值,然后新建next指向首部元素的下一个节点,第二步把 f 指针(也就是first指针指向的节点)置为空(消除强引用)然后把first指针指向next节点,如果next节点不是空的(当前列表中元素个数大于1 )就把next的前驱节点置为 null (next是first节点的后继节点, 此操作就相当于删除first指针指向的节点)。如果next节点是空的话(当前列表只有一个元素 )那就直接把 Last指针也置为空(此时列表中所有的元素都没了)。
- f 指向首部节点的指针
/**
* Unlinks non-null first node 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;
}
removeLast
删除并且返回链表的最后一个元素
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
unlinkLast
步骤和删除第一个节点类似。
/**
* Unlinks non-null last node 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;
}
删除指定元素
remove(int index)
检查当前索引的范围, 随后删除当前位置的元素。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
E unlink(Node x)
/**
* Unlinks non-null node 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;
}
remove(Object o)
如果要删除的元素不是 null 的话, 就遍历链表,找到当前的元素,调用unlink(Node x) 方法删除元素。
- 使用 o.equals(x.item) 来判断是否相等
如果要删除的元素是 null 的话, 就遍历链表,找到为 null 元素,调用unlink(Node x) 方法删除元素。
- 使用 x.item == null 来判断是否相等
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;
}
检查包含
contains
public boolean contains(Object o) {
return indexOf(o) != -1;
}
indexOf(Object o)
返回当前元素第一次出现位置的索引, 如果找不到的话返回 -1 方法实现和 boolean remove(Object o) 相同, 只是每次遍历链表的时候 index 自增记录当前的下标。
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;
}
lastIndexOf(Object o)
- 得到当前列表的大小
- 开始从后面遍历这个链表
- 使用 == 、 equals 来判断元素是否相等
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
其他操作
E peek()
得到当前的头部元素但是不删除该元素。
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
E peekLast()
得到当前的尾部元素但是不删除该元素。
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
E poll()
得到当前头部元素,并且删除头部元素
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
E pollLast()
得到当前尾部元素,并且删除头部元素
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
E pollFirst()
得到当前首部元素,并且删除头部元素
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
offer(E e)
在尾部添加一个元素
public boolean offer(E e) {
return add(e);
}
offerFirst(E e)
在首部添加一个元素
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
push(E e)
在首部添加一个元素
public void push(E e) {
addFirst(e);
}
pop(E e)
删除首部元素
public E pop() {
return removeFirst();
}
转化为数组
- 创建一个对象数组
- 然后逐个遍历链表给数组赋值
public Object[] toArray() {
Object[] result = new Object[size];
int i = 0;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
- 使用反射得到传递进来的类型
- 使用上面的类型创建一个数组
- 然后逐一赋值元素
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
序列化操作
void writeObject(java.io.ObjectOutputStream s)
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
常用内部函数
- Node node(int index)
- 得到当前位置的节点
- boolean isElementIndex(int index)
- 判断当前位置是否在范围之内
小总结
-
使用双向循环链表来作为底层的实现
-
使用First \ Last指针来保存列表中的首部节点和尾部节点
-
添加和删除操作都可以在首部、尾部进行