LinkedList 简介
linkedList : 简单来说就是一个双向循环链表
如下图
分析一:LinkedList实质结构
先看源码
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;
从成员变量中可以看出 一个 头节点first,一个尾节点last,还有一个长度size,变量只有3个,可以看出,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节点 中包含了一个item ,一个prev指针,一个next指针
分析二: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);
}
linkedList构造方法很简单,一个空构造,一个是导入式构造,addAll函数后面会讲到
OK我们下面看增删改查
分析三:LinkedList的增删改查
LinkedList : 增
增加方法中,都是基于三个基本方法实现的
代码
/**
* 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++;
}
/**
* 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++;
}
一:先看第一个函数 linkFirst(E e) ,添加到头节点
这个分为两种情况:
1.LinkedList 里面还没有元素,size = 0
不难看出 当f == null 的时候 就直接将 first 和 last 都指向 newNode
从这里就可以看出这种情况 ,直接让头尾节点相同
2.LinkedList 里面有元素的情况 长度为size
这个从图例中看,更直观一点,如下图:
从上面图中可以看出第一步 是将现有的头节点 first 赋值给 f ,第二步 创建一个新的节点newNode
并且让它的next指针指向f,第三步 将first指向newNode ,第四步 ,将f的prev指向 newNode 节点 ,
新的链表 就形成了
二 : 添加到尾部 linkList(E e)
跟添加到头节点一样也是有两种情况
1. linkedList没有元素 l == null , 就直接将 first 和 last 都指向 newNode
2. linkedList有元素 ,先看图
从图中来看,第一步 先将 last 赋值给 l ,第二步,创建新节点 newNode 并且 将 newNode 的prev 指向 l
第三步 将last 指向 newNode ,第四步 将l.next 指向 newNode 新的链表形成
三 : 在某个元素之前插入一个元素 linkBefore(E e, Node<E> succ)
分两种情况 1. succ.prev = null 代表succ是第一个位置节点 就直接添加到first节点
2.succ.prev != null 在数据中间插入 先看图
图看着有点花,将就点看吧, 第一步 ,先将succ.prev 赋值给pred
第二步,创建一个新的节点,并让它的prev指针指向-->pred,
它的next指针指向--> succ
第三步, 将succ的prev指针指向 --> newNode
第四步,将 pred的next指针指向 --> newNode
OK新的链表形成newNode 就插入到了pred 和 succ 中间
来看具体提供给外部调用的方法
方法一 : 添加到first 这个很简单 就是调用了上面说的linkFirst
/**
* Inserts the specified element at the beginning of this list.
*
* @param e the element to add
*/
public void addFirst(E e) {
linkFirst(e);
}
方法二 : 直接添加一个元素到末尾 同样直接调用了 linkLast
/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #add}.
*
* @param e the element to add
*/
public void addLast(E e) {
linkLast(e);
}
方法三 : 直接添加一个元素 代码中可以看出 默认添加一个元素是添加到末尾,并返回ture
/**
* 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;
}
方法四 : 插入一个元素
/**
* 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));
}
插入一个元素 比较特别,它先是判读下标index ,方法checkPositionIndex(index),该方法判断 index >=0 && index<=size 否则抛异常 那么后面if(index == size) 只有一种情况满足 index =0&& size =0,说明LinkedList还没有添加元素,就直接执行linkLast添加到末尾,否则就执行添加到该下标之前 node(index)后面查询会将到
添加中还有一个方法 addAll(int index, Collection<? extends E> c)
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element
* from the specified collection
* @param c collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ;
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);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
那些非正常情况,比如没有值,当前元素只有一个的情况,图上就不展示了
第一步,先将 要添加的集合转换成数组 a
第二步,查找出index下标的节点succ ,并找出它的prev指针 pred
第三步,将要添加的数组遍历循环
第四步,每次循环将创建一个Node节点newNode 并让它的prev指向pred ,并让pred的next指向newNode
第五步, 让newNode赋值给pred引用,让当前循环的newNode成为新的pred
第六步,循环完之后,将最新的pred的next指向succ
第七步,将succ的prev指向最新的pred也就是数组最后一次的newNode
就这样将整个集合添加到LinkedList当中
LinkedList添加所有除此之外还有一个方法,它是默认添加到末尾
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the specified
* collection's iterator. The behavior of this operation is undefined if
* the specified collection is modified while the operation is in
* progress. (Note that this will occur if the specified collection is
* this list, and it's nonempty.)
*
* @param c collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
LinkedList : 删除
LinkedList对外开放的删除方法有很多,归根接低都是调用下面3个方法进行删除的
方法一 : 删除 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;
}
步骤不多:
第一步,将找出first的next指针 赋值给next
第二步,将first节点的数据置空,并将它的next指针置空
第三步,将next节点赋值给first 让next成为新的first节点
第四步,将next的prev指针指向=null
这样就能成功将first节点删除
方法二 : 删除尾节点
/**
* 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;
}
步骤 一. 先将lsat的prev指针指向的节点 赋值给prev
二.将last的数据置空,并且将它的prev指向置空
三.让prev成为新的last节点
四.将prev的next指向置空
方法三 : 删除指定节点
/**
* 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;
}
步骤 一 : 找到x的next指针指向的节点 next
二 : 找到x的prev指针指向的节点 prev
三 : 将prev的next指针指向节点 next
四 : 断开x的prev指向 置空
五 : 将next的prev指针指向 节点 prev
六 : 将x的next指针指向置空
LinkedList : 改
修改代码比较简单
/**
* 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;
}
具体思路就是先查找到index下标的节点,然后将item改掉就可以了
LinkedList : 查
LinkedList查主要就看看下面这个函数方法
/**
* 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;
}
}
大体思路就是 判断index为这个链表的前半段还是后半段,然后根据头结点和尾节点,遍历到index下标的元素
总结
LinkedList 内部属于链表结构,删除 增加 只需要将对应的节点的头指针很尾指针改变就可以了
而查询某个元素就需要遍历这个list , 修改某个元素也需要通过遍历查询这个元素,所以查询和修改效率偏低
这样可以看出虽然ArrayList 和 LinkedList虽然都是实现List这个接口,但其实内部结构完全不同,从增删改查来看
恰恰是相反的结构特点
画图水平有待提高:o(* ̄︶ ̄*)o