一组由节点组成的数据结构,每个元素指向下一个元素,是线性序列。
最简单的链表结构:
不适合的场景:
链表结构:
链表结构:
链表结构:
实现链表节点:
public class Node<E> {
E item;
Node<E> prev;
Node<E> next;
public Node(E item, Node<E> prev, Node<E> next) {
this.item = item;
this.prev = prev;
this.next = next;
}
}
在头节点之前插入节点:
void insertNodeBeforeHead(E e){
final Node<E> oldHeadNode=head;
final Node<E> newHeadNode=new Node<E>(e,null,oldHeadNode);
head=newHeadNode;
if(oldHeadNode==null){
// 说明原先链表中没有元素
tail=newHeadNode;
}else{
// 如果有元素,则需要改变头节点的指针指向
oldHeadNode.prev=newHeadNode;
}
size++;
}
在尾节点之后插入节点:
void insertNodeAfterTail(E e){
final Node<E> oldTailNode=tail;
final Node<E> newTailNode=new Node<E>(e,oldTailNode,null);
tail=newTailNode;
if(oldTailNode==null){
head=newTailNode;
}else{
oldTailNode.next=newTailNode;
}
size++;
}
拆除链表:
E unlinkByNode(Node<E> node){
final E element=node.item;
final Node<E> prevNode=node.prev;
final Node<E> nextNode=node.next;
// 改变前一个元素的next指针指向的元素
if(prevNode==null){
// 说明是头节点
head=nextNode;
}else{
prevNode.next=nextNode;
node.prev=null;
}
// 改变后一个元素的prev指针指向的元素
if(nextNode==null){
// 说明是尾节点,没有下一个元素
tail=prevNode;
}else{
nextNode.prev=prevNode;
node.next=null;
}
size--;
node.item=null;
return null;
}
移除元素:
public boolean removeNodeByElement(E e){
if(e==null){
for(Node<E> start=head;start!=null;start=start.next){
if(start.item==null){
unlinkByNode(start);
return true;
}
}
}else{
for(Node<E> start=head;start!=null;start=start.next){
if(start.item.equals(e)){
unlinkByNode(start);
return true;
}
}
}
return false;
}
transient int size=0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
Node
其中节点 Node 的数据结构如下,是 LinkedList 的内部类:
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 的作用
首先,需要理解 Java 中序列化和反序列化的作用:
如何实现序列化和反序列化:
什么情况下不需要序列化:
LinkedList 将 first 和 last 修饰成 transient 的原因:
LinkedList 重写了 writeObject 和 readObject 方法,自定义了序列化和反序列化的过程,用于重新链接节点:
序列化:writeObject
/**
* Saves the state of this {@code LinkedList} instance to a stream
* (that is, serializes it).
*
* @serialData The size of the list (the number of elements it
* contains) is emitted (int), followed by all of its
* elements (each an Object) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out any hidden serialization magic 调用默认的序列化方法
s.defaultWriteObject();
// Write out size 指定序列化的容量,单位:32 bit int
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);
}
反序列化:readObject
/**
* Reconstitutes this {@code LinkedList} instance from a stream
* (that is, deserializes it).
*/
@SuppressWarnings("unchecked")
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()); // linkLast是向链表中的尾部插入节点的方法
}
核心流程:
/**
* 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++;
}
核心流程:
/**
* 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++;
}
核心流程:
/**
* 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;
}
默认插入方法,尾部插入:boolean add(E e)
直接插入链表尾部
/**
* 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;
}
指定位置插入元素:add(int index,E element)
流程:
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index,E element){
checkPositionIndex(index);
if(index==size)
linkLast(element);
else
linkBefore(element,node(index));
}
核心流程:
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index){
checkElementIndex(index);
return node(index).item;
}
核心流程:
/**
* 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;
}
}
核心流程:
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index,E element){
checkElementIndex(index);
Node<E> x=node(index);
E oldVal=x.item;
x.item=element;
return oldVal;
}
移除指定元素的节点:boolean remove(Object o)
核心流程:
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If this list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* (o==null ? get(i)==null : o.equals(get(i)))
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
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;
}
移除指定索引位置的节点:remove(int index)
核心流程:
/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index){
checkElementIndex(index);
return unlink(node(index));
}
从 first 节点开始遍历,将所有的节点的 item、next、prev 值设置为 null。
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;
modCount++;
}
Node 的源码:
LinkedList 的 linkLast 向尾元素后插入元素的方法源码:
结论:非循环双向链表
获取:O(n)
插入:
在不确定数据量且需要频繁插入和删除操作的场景下。
707 设计链表