继续分析List的整体框架结构:
从上图可以看出,LinkedList与ArrayList和Vector不同,它直接继承的父类是AbstractSequentialList而不是AbstractList。 我们再来看一看它的体系结构:
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>,Deque<E>,Cloneable,java.io.Serializable
从上图可以看出,LinkedList实现的接口除了ArrayList和Vector都实现过的List、Cloneable和Serializeable接口外,还实现了一个Deque接口,那来分析分析其父类AbstractSequentialList类和额外接口Deque到底有什么作用,为什么要继承这个父类和实现这个额外接口。
首先来分析一下AbstractSequentialList和AbstractList的区别,上源码。
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
public E set(int index, E element) {
throw new UnsupportedOperationException();
}
abstract public E get(int index);
简单截取一部分AbstractList的增删改查代码,发现四个方法都没有给出具体实现,如何实现都是让其子类或者子类的子类自己决定。
再来看AbstractSequentialList:
public void add(int index, E element) {
try {
listIterator(index).add(element);
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
public E remove(int index) {
try {
ListIterator<E> e = listIterator(index);
E outCast = e.next();
e.remove();
return outCast;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
public E set(int index, E element) {
try {
ListIterator<E> e = listIterator(index);
E oldVal = e.next();
e.set(element);
return oldVal;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
从上述源代码可以看出,AbstractSequentialList将增删改查都基本实现了一遍,而且都使用的是双向迭代器进行实现,也就是说继承了AbstractSequentialList这个父类,就要实现ListIterator这个接口,并且用迭代器来实现增删改查操作。从这里我们就能清楚了,以这种规定限制了继承AbstractSequentialList的子类只能是链表这样的数据结构了。因为顺序表也使用这种迭代的方式进行增删改查会极大的降低效率。
接下来再看一看新实现的Deque
public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
void push(E e);
E pop();
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
从上面可以看出Deque是队列Queue的一个子接口,也被称为双向队列,是一个集大成者的数据结构,既可以实现先进后出模式的队列,也可以实现先进先出的栈,还可以实现顺序表中的某些方法,因此Deque的增删改查都对应着多种方法,分别用于不同的场景。所以实现了Deque接口的ArrayList也有着这么多的功能,既可以从头部插入或删除,也可以从尾部插入和删除。因此功能十分强大。
好了,框架结构基本梳理完了,接下来看LinkedList的成员变量:
transient int size = 0;
//设置链表的长度,不可序列化
transient Node<E> first;
//设置链表的第一个结点,不可序列化
transient Node<E> last;
//设置链表的最后一个结点,不可序列化
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成员变量有三个,分别存储着链表的长度,链表的第一个结点和链表的最后一个结点,且三个成员变量都使用transient修饰,即表明该类对象序列化时不包括这些成员变量。此处还要介绍一下链表的底层存储结构Node,如上面代码所示,Node是LinkedList的私有静态内部类,其成员变量有三个,分别是本结点的内容item,下一个结点next,上一个结点prev,这两个也就相当于指针的,分别作为前置指针和后置指针,将链表中所有的结点联系起来。因此LinkedList的底层数据结构应该是双向链表。
再继续看一下LinkedList的构造器:
public LinkedList(){
//构造器1,空参构造器
}
public LinkedList(Collection<? extends E> c){
//构造器2,传入一个集合进行构造链表
this();
addAll(c);
}
LinkedList构造器只有两个,一个空参不做任何其他处理,另外一个传入一个集合,则首先调用空参构造器构造一个空的链表,再调用addAll方法将集合c插入到空链表中。因此链表最初始的时候应该是空的,当加入第一个元素进来时,它就创建一个结点将该元素存储下来,然后first和last都指向该元素,再从尾部添加一个元素时,链表再创建一个Node结点将元素保存,然后last指向新结点,从而形成了一个简单链表。
好,接下来上其余源码:
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>,Deque<E>,Cloneable,java.io.Serializable
{
transient int size = 0;
//设置链表的长度,不可序列化
transient Node<E> first;
//设置链表的第一个结点,不可序列化
transient Node<E> last;
//设置链表的最后一个结点,不可序列化
public LinkedList(){
//构造器1,空参构造器
}
public LinkedList(Collection<? extends E> c){
//构造器2,传入一个集合进行构造链表
this();
addAll(c);
}
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++;
}
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++;
}
void linkBefore(E e, Node<E> succ){
//在结点succ之前插入一个结点
final Node<E> pred = succ.prev;//获取succ之前的结点
final Node<E> newNode = new Node<>(pred, e, succ);//创建新结点
succ.prev = newNode;//将succ前置指针指向新结点
if(pred == null)//如果原前结点为空,说明succ为链表头部结点
first = newNode;//故将新结点设置为头结点
else
pred.next = newNode;//否则将原前结点的后置指针指向新结点
size++;//链表长度增加一
modCount++;
}
private E unlikFirst(Node<E> f){
//删除头结点
final E element = f.item;//获取头结点中的内容
final Node<E> next = f.next;//获取头结点后一个结点
f.item = null;//将头结点设置为空,方便回收
f.next = null;//将头结点的后置指针设置为空,方便回收
first = next;//将后一个结点设置为头结点
if(next == null)//如果后一个结点为空,说明此时链表为空,设置尾指针为空
last = null;
else
next.prev = null;//否则设置后一个结点的前置指针为空
size--;//链表长度减一
modCount++;
return element;//返回被删除的结点的内容
}
private E unlinkLast(Node<E> l){
//删除尾结点,和删除头结点基本一致,注意
//两个方法都是私有的,不能公共调用
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null;
last = prev;
if(prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
E unlink(Node<E> x){
//删除某一个结点
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 E getFirst(){
//获得链表头结点的内含元素
final Node<E> f = first;
if(f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast(){
//获取链表尾结点的内含元素
final Node<E> l = last;
if(l == null)
throw new NoSuchElementException();
return l.item;
}
public E removeFirst(){
//移除链表第一个结点,调用的unlinkFirst方法,
//主要是检查了该结点的安全性,即验证结点是否为空
final Node<E> f = first;
if(f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
public E removeLast(){
//和移除头结点类似,移除链表的尾结点
final Node<E> l = last;
if(l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
public void addFirst(E e){
//在链表头部插入结点,对外方法
linkFirst(e);
}
public void addLast(E e){
//在链表尾部插入结点,对外方法
linkLast(e);
}
public boolean contains(Object o){
//查询链表中是否含有某元素
return indexOf(o) != -1;
}
public int size(){
//获得链表长度
return size;
}
public boolean add(E e){
//在链表尾部插入结点,如果成功返回true
linkLast(e);
return true;
}
public boolean remove(Object o){
//移除某一个结点,该结点的内容是o
//此处应用for循环进行链表遍历,强,从没想过
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;
}
public boolean addAll(Collection<? extends E> c){
//在链表末尾增加一个集合c
return addAll(size,c);
}
public boolean addAll(int index, Collection<? extends E> c){
//在某一个索引结点后面插入一个集合c
checkPositionIndex(index);
//检查索引的合法性
Object[] a = c.toArray();
//将集合转变为数组
int numNew = a.length;
//获得数组的长度
if(numNew == 0)
return false;
//如果数组长度为0,无法插入,返回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结点
succ.prev = pred;//并且将succ节点的前置指针指向最后一个元素结点
}
size += numNew;//增加链表长度
modCount++;
return true;//成功插入,返回
}
public void clear(){
//清空链表中的所有结点,即将所有的结点都释放
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++;
}
public E get(int index){
//获得索引中的结点内的内容
checkElementIndex(index);
//核查索引是否合法
return node(index).item;
//此处使用的是node的方法,即先判断索引的大小
//如果比链表的长度的一半小,则从前遍历寻找
//否则从链表的末尾开始向前寻找,提高的效率
}
public E set(int index, E element){
//修改某个索引指向的结点的信息
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
public void add(int index, E element){
//在某个索引所指的结点前面添加一个结点
checkPositionIndex();
//核查索引是否合法,此处方法和checkElementIndex方法
//的不同之处在于此处索引可以是链表的长度而c方法不可以
if(index == size)
linkLast(element);
else
linkBefore(element,node(index));
}
public E remove(int index){
//移除索引所指向的链表结点
checkElementIndex(index);
return unlink(node(index));
}
private boolean isElementIndex(int index){
//判断索引是否在链表索引的范围内
return index >= 0 && index < size;
}
private boolean isPositionIndex(int index){
//判断索引是否在链表索引的范围内,与上一个
//方法的不同之处是可以包含链表的长度的
return index >= 0 && index <= size;
}
private String outOfBoundsMsg(int index){
//设置异常提示信息
return "Index: " + index + ", Size" + size;
}
private void checkElementIndex(int index){
//抛出索引越界异常,为何要分开写,而不直接写在一起??????
if(!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void checkPositionIndex(int index){
//抛出索引越界异常
if(!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Node<E> node(int 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 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;
}
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;
}
public E peek(){
//类似于栈的取栈顶元素,此处也是取链表头部结点中的元素
//但是不会移除头结点,且当头结点为空时返回空,不会抛出异常
//此处实现栈中的方法
final Node<E> f = first;
return (f == null)
? null
: f.item;
}
public E element(){
//也是取头结点元素,但是如果头结点为空时会抛出异常
return getFirst();
}
public E poll(){
//取出链表头结点元素并且删除头结点,即头结点向后移动
//如果头结点为空则返回空,不抛出异常
final Node<E> f = first;
return (f == null) ? null : unlikFirst(f);
}
public E remove(){
//取出链表头结点并删除头结点
//如果头结点为空则抛出无此元素异常
return removeFirst();
}
public boolean offer(E e){
//offer是队列中的方法,即快速插入元素,此处调用add进行插入
//此处实现双向队列中的方法
return add(e);
}
public boolean offerFirst(E e){
//将元素插入到链表头部
addFirst(e);
return true;
}
public boolean offerLast(E e){
//将元素插入到链表尾部
addLast(e);
return true;
}
public E peekFirst(){
//此处实现双向队列中的方法
final Node<E> f = first;
return (f == null)
? null
: f.item;
}
public E peekLast(){
//双向队列中取出尾部元素,不删除
final Node<E> l = last;
return (l == null)
? null
: l.item;
}
public E pollFirst(){
//双向队列中取出头部元素并删除头部结点
final Node<E> f = first;
return (f == null)
? null
: unlinkFirst(f);
}
public E pollLast(){
//双向队列中取出尾部元素并删除尾部结点
final Node<E> l = last;
return (l == null)
? null
: unlinkLast(l);
}
public void push(E e){
//实现栈中压栈操作
addFirst(e);
}
public E pop(){
//实现栈中弹栈操作
return removeFirst();
}
public boolean removeFirstOccurrence(Object o){
//删除第一次出现的某元素,如果该元素不存在,则不做任何动作
return remove(o);
}
public boolean removeLastOccurrence(Object o){
//删除最后一次出现的某元素,如果该元素不存在,则不做任何动作
if(o == null){
for(Node<E> x = last; x != null; x = x.prev){
if(x.item == null){
unlink(x);
return true;
}
}
}else{
for(Node<E> x = last; x != null; x = x.prev){
if(o.equals(x.item)){
unlink(x);
return true;
}
}
}
return false;
}
public ListIterator<E> listIterator(int index){
//获得列表迭代器,此处就没有普通的构造器了
checkPositionIndex(index);
return new ListItr(index);
}
private class ListItr implements ListIterator<E>{
private Node<E> lastReturned;//记录当前的结点
private Node<E> next;//记录下一个结点
private int nextIndex;//记录下一个结点索引
private int expectedModCount = modCount;
ListItr(int index){
//构造器
next = (index == size)
? null
: node(index);
nextIndex = index;
}
public boolean hasNext(){
//判断是否有下一个元素
return nextIndex < size;
}
public E next(){
//遍历下一个结点
checkForComodification();
//范围检查
if(!hasNext())
throw new NoSuchElementException();
//查看是否有下一个元素,没有就抛出异常
lastReturned = next;//否则,将下一个结点赋给当前结点
next = next.next;//指针向后移动一个
nextIndex++;//索引增加一个
return lastReturned.item;//返回当前结点的元素
}
public boolean hasPrevious(){
//判断是否有上一个结点
return nextIndex > 0;
}
public E previous(){
//遍历上一个结点,原理相同
checkForComodification();
if(!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null)
? last
: next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex(){
//获得下一个结点索引值
return nextIndex;
}
public int previousIndex(){
//获得上一个结点索引值,为何只要减一就行了???
return nextIndex - 1;
}
public void remove(){
//移除当前结点
checkForComodification();
if(lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if(next == lastReturned)//如果原结点不是尾节点,则将原结点的下一个结点赋值给next
next = lastNext;
else
nextIndex--;//否则如果原结点是尾节点,那么直接将索引减一即可
lastReturned = null;//将原结点释放,便于GC回收
expectedModCount++;
}
public void set(E e){
//更新当前结点的信息
if(lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;//为什么可以直接修改?不需要抛出异常吗???
}
public void add(E e){
//在本结点后面插入一个新结点
checkForComodification();
lastReturned = null;//首先将当前结点指针只向空
if(next == null)//如果下一个结点为空,说明当前结点是最后一个,故插入链表尾就行
linkLast(e);
else
linkBefore(e, next);//否则就将元素插入到当前结点后面
nextIndex++;//将指针后移一位
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action){
//遍历链表中的各个元素,使用lambda表达式
Objects.requireNonNull(action);
while(modCount == expectedModCount && nextIndex < size){
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification(){
//同步检查
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
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;
}
}
public Iterator<E> descendingIterator(){
//应该是获取一个从链表末尾开始遍历的迭代器
return new DescendingIterator();
}
private class DescendingIterator implements Iterator<E>{
//私有内部类,主要是用ListItr类实现从尾部向前迭代的功能
private final ListItr itr = new ListItr(size());
public boolean hasNext(){
return itr.hasPrevious();
}
public E next(){
return itr.previous();
}
public void remove(){
itr.remove();
}
}
@SuppressWarnings("unchecked")
private LinkedList<E> superClone(){
//私有方法,调用父类的克隆方法
try{
return(LinkedList<E>)super.clone();
}catch(CloneNotSupportedException e){
throw new InternalError(e);
}
}
public Object clone(){
//将链表克隆,返回一个克隆对象
LinkedList<E> clone = superClone();
clone.first = clone.last = null;
clone.size = 0;
clone.modCount = 0;
for(Node<E> x = first; x != null; x = x.next)
clone.add(x.item);
return clone;
}
public Object[] toArray(){
//将链表转变为数组,此处和ArrayList和vetor并不一样
//这里是遍历链表,将链表的每一个结点的元素提取出来
//放入一个新创建的数组中,最后返回这个数组
Object[] result = new Object[size];
int i = 0;
for(Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
@SuppressWarnings("unchecked")
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;
}
private static final long serialVersionUID = 876323262645176354L;
//验证版本是否一致,反序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
//序列化,写入
s.defaultWriteObject();
s.writeInt(size);
for(Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException{
//序列化,读取
s.defaultReadObject();
int size = s.readInt();
for(int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
@Override
public Spliterator<E> spliterator(){
//获取分割器
return new LLSpliterator<E>(this, -1, 0);
}
static final class LLSpliterator<E> implements Spliterator<E>{
static final int BATCH_UNIT = 1 << 10;
static final int MAX_BATCH = 1 << 25;
final LinkedList<E> list;
Node<E> current;
int est;
int expectedModCount;
int batch;
LLSpliterator(LinkedList<E> list, int est, int expectedModCount){
//分割器构造器
this.list = list;
this.est = est;
this.expectedModCount = expectedModCount;
}
final int getEst(){
int s;
final LinkedList<E> lst;
if((s = est) < 0){
if((lst = list) == null)
s = est = 0;
else{
expectedModCount = lst.modCount;
current = lst.first;
s = est = lst.size;
}
}
return s;
}
public long estimateSize(){
return (long) getEst();
}
public Spliterator<E> trySplit(){
Node<E> p;
int s = getEst();
if(s > 1 && (p = current) != null){
int n = batch + BATCH_UNIT;
if(n > s)
n = s;
if(n > MAX_BATCH)
n = MAX_BATCH;
Object[] a = new Object[n];
int j = 0;
do{
a[j++] = p.item;
}while((p = p.next) != null && j < n);
current = p;
batch = j;
est = s - j;
return Spliterators.spliterator(a, 0,j,Spliterator.ORDERED);
}
return null;
}
public void forEachRemaining(Consumer<? super E> action){
Node<E> p;
int n;
if(action == null)
throw new NullPointerException();
if((n = getEst()) > 0 && (p = current) != null){
current = null;
est = 0;
do{
E e = p.item;
p = p.next;
action.accept(e);
}while(p != null && --n > 0);
}
if(list.modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public boolean tryAdvance(Consumer<? super E> action){
Node<E> p;
if(action == null)
throw new NullPointerException();
if(getEst() > 0 && (p = current) != null){
--est;
E e = p.item;
current = p.next;
action.accept(e);
if(list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
public int characteristics(){
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
}
}
再来分析一下LinkedList的常用方法:
1.size
public int size(){
//获得链表长度
return size;
}
获取链表长度,不用多说。
2.contains与indexOf
public boolean contains(Object o){
//查询链表中是否含有某元素
return indexOf(o) != -1;
}
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;
}
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;
}
此处contains和indexOf的实现方式和ArrayList、Vector没有什么太大的差别,只是将Integer的遍历变成了Node遍历。
3.查询
public E get(int index){
//获得索引中的结点内的内容
checkElementIndex(index);
//核查索引是否合法
return node(index).item;
//此处使用的是node的方法,即先判断索引的大小
//如果比链表的长度的一半小,则从前遍历寻找
//否则从链表的末尾开始向前寻找,提高的效率
}
Node<E> node(int 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 getFirst(){
//获得链表头结点的内含元素
final Node<E> f = first;
if(f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast(){
//获取链表尾结点的内含元素
final Node<E> l = last;
if(l == null)
throw new NoSuchElementException();
return l.item;
}
public E peek(){
//类似于栈的取栈顶元素,此处也是取链表头部结点中的元素
//但是不会移除头结点,且当头结点为空时返回空,不会抛出异常
//此处实现栈中的方法
final Node<E> f = first;
return (f == null)
? null
: f.item;
}
public E element(){
//也是取头结点元素,但是如果头结点为空时会抛出异常
return getFirst();
}
public E peekFirst(){
//此处实现双向队列中的方法
final Node<E> f = first;
return (f == null)
? null
: f.item;
}
public E peekLast(){
//双向队列中取出尾部元素,不删除
final Node<E> l = last;
return (l == null)
? null
: l.item;
}
从上面源代码可以看出,LinkedList在查找某个元素这个功能上提供了很多的方法,而且有很多方法感觉都是一样的,那这样算不算一种代码冗余呢?嗯,首先来给这些方法分一下类,我们知道LinkedList既实现了List接口又实现Deque接口,所以两个体系中的方法都需要实现。那么get和element两个方法其实是属于List体系的方法,而getFirst与getLast和peekFirst与peekLast这四个方法是Deque中的双向队列的方法,而getFirst和getLast方法在面对头部结点或尾部结点为空时会抛出异常,而peekFirst和peekLast这两个方法面对头部或尾部为空时会返回null。而另外一个peek方法则是Deque中的栈的方法。
简单来说,即:
List | get(index) | 根据索引获取索引处的元素内容 |
List | Element() | 获取表头的内容 |
Deque(双向队列) | peekFirst() | 获取队列头部的内容,如果队列头部为空,返回null |
Deque(双向队列) | peekLast() | 获取队列尾部的内容,如果队列尾部为空,返回null |
Deque(双向队列) | getFirst() | 获取队列头部的内容,如果队列头部为空,抛出无此元素异常 |
Deque(双向队列) | getLast() | 获取队列尾部的内容,如果队列尾部为空,抛出无此元素异常 |
Deque(栈) | peek() | 获取栈顶内容,如果栈为空,返回null |
4.增加
public boolean add(E e){
//在链表尾部插入结点,如果成功返回true
linkLast(e);
return true;
}
public void add(int index, E element){
//在某个索引所指的结点前面添加一个结点
checkPositionIndex();
//核查索引是否合法,此处方法和checkElementIndex方法
//的不同之处在于此处索引可以是链表的长度而c方法不可以
if(index == size)
linkLast(element);
else
linkBefore(element,node(index));
}
public void addFirst(E e){
//在链表头部插入结点,对外方法
linkFirst(e);
}
public void addLast(E e){
//在链表尾部插入结点,对外方法
linkLast(e);
}
public boolean offer(E e){
//offer是队列中的方法,即快速插入元素,此处调用add进行插入
//此处实现双向队列中的方法
return add(e);
}
public boolean offerFirst(E e){
//将元素插入到链表头部
addFirst(e);
return true;
}
public boolean offerLast(E e){
//将元素插入到链表尾部
addLast(e);
return true;
}
public void push(E e){
//实现栈中压栈操作
addFirst(e);
}
增加方法也类似,其中add方法属于List体系中的,但是Deque接口中也有相同的方法,两者都需要实现。offerFirst、offerLast、addFirst、addLast都是属于Deque体系中的双向队列,而offer属于Deque体系中的队列,push属于Deque体系中的栈。
List | add(e) | 在链表尾部插入一个元素 |
List | add(index,element) | 在链表index索引处插入一个元素 |
Deque(双向队列) | addFirst(e) | 在双向队列头部插入一个元素,插入失败时抛出异常 |
Deque(双向队列) | addLast(e) | 在双向队列尾部插入一个元素,插入失败时抛出异常 |
Deque(双向队列) | offerFirst(e) | 在双向队列头部插入一个元素,插入失败时返回false |
Deque(双向队列) | offerLast(e) | 在双向队列尾部插入一个元素,插入失败时返回false |
Deque(队列) | offer(e) | 在队列尾部插入一个元素 |
Deque(栈) | push(e) | 将元素压入栈 |
5.修改
public E set(int index, E element){
//修改某个索引指向的结点的信息
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
由于Deque接口没有提供与修改相关的方法,因此LinkedList只实现了List接口中的修改方法,首先检查传入的索引的合法性,然后再遍历到索引处,将索引指向的原元素取出,并修改成新元素,然后返回原元素。由此可见ArrayList的修改比较麻烦,必须从链表头部一个个遍历下去,找到元素之后才能进行修改。
6.删除
public boolean remove(Object o){
//移除某一个结点,该结点的内容是o
//此处应用for循环进行链表遍历,强,从没想过
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;
}
public E remove(int index){
//移除索引所指向的链表结点
checkElementIndex(index);
return unlink(node(index));
}
public boolean removeFirstOccurrence(Object o){
//删除第一次出现的某元素,如果该元素不存在,则不做任何动作
return remove(o);
}
public boolean removeLastOccurrence(Object o){
//删除最后一次出现的某元素,如果该元素不存在,则不做任何动作
if(o == null){
for(Node<E> x = last; x != null; x = x.prev){
if(x.item == null){
unlink(x);
return true;
}
}
}else{
for(Node<E> x = last; x != null; x = x.prev){
if(o.equals(x.item)){
unlink(x);
return true;
}
}
}
return false;
}
remove方法既是List接口的也是Deque接口的,所以一起实现,且在LinkedList中也重载了一次,当参数是索引时,那就从头部遍历到该索引处并删除该结点,当参数是元素时,则从头部遍历找到该元素第一次出现时的结点,将该结点删除。除此之外,Deque接口还有另外两个删除方法,即removeFirstOccurence和removeLastOccurrence,功能分别是删除正序出现的第一个元素和逆序出现的第一个元素,当没找到该元素时,返回false.
7.弹出
public E removeFirst(){
//移除链表第一个结点,调用的unlinkFirst方法,
//主要是检查了该结点的安全性,即验证结点是否为空
final Node<E> f = first;
if(f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
public E removeLast(){
//和移除头结点类似,移除链表的尾结点
final Node<E> l = last;
if(l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
public E pollFirst(){
//双向队列中取出头部元素并删除头部结点
final Node<E> f = first;
return (f == null)
? null
: unlinkFirst(f);
}
public E pollLast(){
//双向队列中取出尾部元素并删除尾部结点
final Node<E> l = last;
return (l == null)
? null
: unlinkLast(l);
}
public E pop(){
//实现栈中弹栈操作
return removeFirst();
}
所谓弹出,也就是将元素从该容器中删除并且取出来,这个方法List体系中是没有的,但是Deque体系中有,如pollFirst、pollLast、removeFirst、removeLast是属于Deque中的双向队列的;而pop既是Deque中的栈的也是其中队列的。
Deque(双向队列) | removeFirst() | 弹出双向队列中头部元素,如果头部元素不存在,则抛出无此元素异常 |
Deque(双向队列) | removeLast() | 弹出双向队列中尾部元素,如果尾部元素不存在,则抛出无此元素异常 |
Deque(双向队列) | pollFirst() | 弹出双向队列中头部元素,如果头部元素不存在,则返回null |
Deque(双向队列) | pollLast() | 弹出双向队列中尾部元素,如果尾部元素不存在,则返回null |
Deque(队列) | poll() | 弹出队列中的头部元素,如果头部元素不存在,抛出无此元素异常 |
Deque(栈) | poll() | 弹出栈中的头部元素,如果头部元素不存在,抛出无此元素异常 |
8.其他常用方法
public void clear(){
//清空链表中的所有结点,即将所有的结点都释放
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++;
}
public Object[] toArray(){
//将链表转变为数组,此处和ArrayList和vetor并不一样
//这里是遍历链表,将链表的每一个结点的元素提取出来
//放入一个新创建的数组中,最后返回这个数组
Object[] result = new Object[size];
int i = 0;
for(Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
@SuppressWarnings("unchecked")
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;
}
其他的一些常用方法也和ArrayList差不多,此处就不再赘述,另外需要提到的一点是,为什么LinkedList源代码中没有isEmpty方法呢?难道LinkedList不能判断是否为空么?
其实它的祖类(父类的父类的父类,应该可以这么说吧:)AbstractCollection已经有这个方法的实现了,只是ArrayList和Vector都重写了一遍而已,而LinkedList并没有再重写,所以就继承下来了,也可以用的。
好了,说了这么多,最后总结一下吧,LinkedList与ArrayList完全不一样,它的直接父类是AbstractSequentialList,而且还额外实现了一个接口Deque,这些不同使得它具备的功能远比ArrayList多,再加上其底层是使用结点一个一个指向连接所形成的,所以它可以当作链表来使用,也可以当作队列、栈和双向队列来使用。由于是链表结构,自然也就没有扩容之类的说法,并且由于这种结构,使得它具有增删快,查改慢的特点。总之,LinkedList的方法很多,而且类似的方法也很多,因此需要弄清楚每一个方法都是怎么实现的,都可以应用在哪种情况下是很有必要的。
参考博客:
https://www.cnblogs.com/xujian2014/p/4630785.html
https://www.cnblogs.com/bushi/p/6681543.html
https://blog.youkuaiyun.com/u011240877/article/details/52834074
https://www.cnblogs.com/skywang12345/p/3308807.html