LinkedList类详解
LinkedList类中的方法与实现原理
目录
- 一.数据结构
- 二.类标题
- 三.字段
- 四.构造函数
- 五.方法分析
- public boolean add(Object o)
- public boolean addAll(Collection c)
- public boolean contains(Object elem)
- public boolean remove(Object o)
- public void add(int index,Object element)
- public boolean addAll(int index,Collection c)
- public void clear()
- public boolean containsAll(Collection c)
- public boolean equals(Object o)
- public Object get(int index)
- public int hashCode()
- public int indexOf(Object elem)
- public boolean isEmpty()
- public int lastIndexOf(Object elem)
- public ListIterator listIterator(final int index)
- public ListIterator listIterator()
- public Iterator descendingIterator()
- public Object remove(int index)
- public boolean removeAll(Collection c)
- public boolean retainAll(Collection c)
- public Object set(int index,Object element)
- public List subList(int fromIndex,int toIndex)
- public Object[] toArray()
- public Object[] toArray(Object []a)
- public void addFirst(Object element)
- public void addLast(Object element)
- public Object getFirst()
- public Object getLast()
- public Object removeFirst()
- public Object removeLast()
- 其他方法
- 5.1 共有方法
一.数据结构
LinkedList与ArrayList一样实现List接口,只是ArrayList是List接口的大小可变数组的实现,LinkedList是List接口链表的实现[2],LinkedList在内存中开辟的内存不连续。基于链表实现的方式使得LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList逊色些.
【注1】JDK1.7之前版本LinkedList采用循环双向链表实现,1.7及之后版本采用双向链表实现
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节点的结构图如下:
LinkedList是由很多这样的节点构成,其中:
☞ prev存储的是上一个节点的引用
☞ item存储的是具体内容
☞ next存储的是下一个节点的引用
LinkedList底层数据结构图如下图所示:
二.类标题
LinkedList类的标题如下:
public class LinkedList extends AbstractSequentialList implements List,Deque,Cloneable.java.io.Serializable
这个标题说明LinkedList类是AbstractSequentialList类的子类,并且实现了四个接口:List、Deque、Cloneable和Serializable。如下图所示:
1.LinkedList是一个继承于AbstractSequentialList的双向链表。它也可以被当做堆栈、队列或者双端队列进行操作
2.LinkedList实现List接口,能对它进行队列操作。
3.LinkedList实现了Deque接口,即能将LinkedList当做双端队列使用。
4.LinkedList实现了Cloneable接口,即覆盖了函数clone(),能克隆。
5.LinkedList实现java.io.Serializable接口,LinkedList支持序列化,能通过序列化去传输。
6.LinkedList是非同步的[2]。
【注2】在这里的非同步指的是,当使用线程的时候,对于这个集合对象进行操作,那么不同的线程所获取的这个集合对象是不同的.所以是说不同步,在多线程的形式是不安全的.
三.字段
transient Node first
指向第一个节点的指针
transient Node last
指向最后一个节点的指针
private transient int size
记录节点的个数
protected transient int modCount = 0;
继承自AbstractList,用于记录集合修改次数
四.构造函数
4.1 无参的构造方法,源码如下:
public LinkedList() {
}
4.2 将现有集合元素C加入链表进行构造,源码如下:
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
传入一个集合Collection作为参数初始化LinkedList.addAll方法源码分析详见下述public boolean addAll(Collection c)
五.方法分析
共有方法
public boolean add(Object o)
作用:在LinkedList的末尾插入元素
源码如下:
public boolean add(E e) {
linkLast(e);
return 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++;
}
实现步骤如下:
① 因为我们需要把该元素设置为为节点,所以需要新建一个变量把为节点存储起来。
② 然后新建一个节点,把last指向l,然后设置为尾结点。
③ 在判断一下l是否为空,如果为空的话,说明原来的LinkedList为空,所以同时也需要把新节点设置为头结点,否则就把l的next设置为newNode。
④ size和modCount自增。
源码分析:
一.当LinkedList为空时,即当没有头结点时:
二.当LinkedList不为空,即有头结点时:
【注】局部变量需要显式设置初始化值。成员变量不需要
public boolean addAll(Collection c)
作用:将作为参数传递给次函数的集合中的所有元素追加到列表的末尾
源码如下:
//通过调用addAll(int index,Collection <? extends E> c)完成集合的添加
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//当索引值不合法时,抛出越界异常
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//判断索引值是否合法,在0~size之间,合法返回1,否则返回-1
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//计算指定索引上的节点(返回Node)
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {//比较index更靠近链表(LinkedList)的头结点还是为节点
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 boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);//几乎所有的涉及在指定位置添加或者删除修改操作都需要判断传进来的参数是否合法。checkPositionIndex(index)就是这个作用。
Object[] a = c.toArray();//将集合转化为数组,然后为该数组添加一个新的引用(Object []a)
int numNew = a.length;//新建变量存储数组长度
if (numNew == 0)//如果待添加的集合为空,直接返回,无须进行后面的步骤,后面都是用来把集合中的元素添加到LinkedList中
return false;
Node<E> pred, succ;//pred:指代待添加节点的前一个节点。succ:指代待添加节点的位置
if (index == size) {//待添加的元素位置位于LinkedList最后一个元素后面
succ = null;
pred = last;
} else {//待添加的元素位置位于LinkedList中
succ = node(index);
pred = succ.prev;
}
//遍历数组中的每个元素,在每次遍历的时候,都会新建一个节点,该节点的值存储数组a中遍历的值,该节点的prev用来存储pred节点,next设置为空,接着判断一下该节点的前一个节点是否为空,如果为空的话,则把当前节点设置为头结点,否则的话就把当前节点的前一个节点的next值设置为当前节点,最后那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;
}
//当新添加的节点位于LinkedList集合的最后一个元素后面,通过遍历上面的a的所有元素,此时pred指向的是LinkedList中的最后一个元素,所以把last指向pred指向的节点。
//当不为空的时候,表明在LinkedList集合中添加的元素,需要把pred的next指向succ上,succ的prev指向pred
//最后把集合的大小设置为新的大小
//modCount(修改的次数)自增
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
源码分析:
(1) Node node(int index)源码分析:
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)//进行遍历,获取相应的节点
x = x.next;
return x;
}
作用:当索引值更靠近头结点使,从头结点开始遍历
原理:size >> 1 等价于size/2,即当索引值小于size/2时(意味着距离头结点更近),因此从头结点开始遍历
(2) addAll(int index,Collection c)源码分析:
addAll添加集合方法分为两种情况,一种是在LinkedList列表的末尾添加,一种是在列表中添加。所分情况具体如下图所示:
一.在LinkedList列表尾部添加集合
1.1 待添加节点位置前一个节点为空,此场景处理方式如下图所示:
1.2 待添加节点位置前一个节点非空,此场景处理方式如下图所示:
二. 在LinkedList列表中添加集合
2.1 待添加节点位置前一个节点为空,(同上1.1)
2.2 待添加节点位置前一个节点非空,此场景处理方式如下图所示:
public boolean contains(Object elem)
作用:判断LinkedList是否包含某一个元素。
源码如下:
public boolean contains(Object o) {
return indexOf(o) != -1;
}
源码分析:
底层通过调用indexOf().(源码分析详见下述public int indexOf(Object elem))。该方法主要用于计算元素在LinkedList中的位置。indexOf()方法非常简单:首先根据object是否为空,分为两种情况;然后通过在每种情况下,从头结点开始遍历LinkedList.判断是否有于object相等的元素,如果有,则返回对应的位置index,如果找不到,则返回index,则contains()返回true,否则返回false.
public boolean remove(Object o)
作用:移除第一次出现的元素。(从前往后遍历集合)
源码如下:
public boolean remove(Object o) {
//如果对象为null值
if (o == null) {
//从第一个节点开始,逐一往后遍历,直到x节点为null
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {//如果节点item值为null值,调用unlink(x)方法
unlink(x);
return true;
}
}
} else {
//遍历这个链表,找出链表中的元素值item与o相同的,移除掉
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;//若集合中没有此元素,返回false
}
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;
}
一.unlink(Node x)源码分析:
当要unlink()的是头结点(prev == null),将first指向头结点的后一个节点。若unlink()是尾结点,将last指向尾结点的前一个节点,否则,将当前节点x的前一个节点的next直接指向X的下一个节点,后一个节点的prev直接指向X的前一个节点。
二.remove(Objec o)源码分析:
分待移除元素为空与非空为两种情况进行处理,通过遍历找出目标节点,然后底层调用unlink(Node x)进行移除。
实现List接口方法
public void add(int index,Object element)
作用:在LinkedList链表指定位置添加元素
源码如下:
public void add(int index, E element) {
checkPositionIndex(index);//检查索引是否越界,源码详见上述:public boolean addAll(Collection c)
if (index == size)//若新添加的元素位于LinkedList的最后,调用linkLast()添加
linkLast(element);//linkLast()源码详见上述:public boolean add(Object o)
else
linkBefore(element, node(index));//在LinkedList链表的index位置插入包含元素
}
//在非空节点succ之前插入元素e
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++;
}
一.linkBefore(E e,Node succ)源码分析:
☞ 首先需要新建一个变量指向succ节点的前一个节点,因为我们要在succ前面插入一个节点。
☞ 接着新建一个节点,它的prev设置为我们刚才新建的变量,后置节点设置为succ。
☞ 然后修改succ的prev为新节点。
☞ 接着判断一个succ的前一个节点是否为空,如果为空的话,需要把新节点设置为为头结点。
☞ 如果不为空,则把succ的前一个节点的next设置为新节点。
实现原理如下图所示:
二.add(int index,E element)源码分析:
当在LinkedList链表表尾添加元素,底层调用linkLast()方法
在LinkedList链表其他索引处添加元素,底层调用linkBefore()方法
public boolean addAll(int index,Collection c)
作用:在指定位置添加集合元素。
源码及源码分析详见上述public boolean addAll(Collection c)
public void clear()
作用:清空LinkedList中的所有元素
源码如下:
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++;
}
源码分析:
直接遍历整个LinkedList,然后把每个节点都置空。最后要把头结点和尾结点设置为空,size也设置为空,但是modCount仍然自增。
public boolean containsAll(Collection c)
作用:检测LinkedList是否包含指定集合中的所有元素
源码如下(同ArrayList):
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))//contains(Object)源码详见上述:public boolean contains(Object elem)
return false;
return true;
}
源码分析:
LinkedList类的containsAll(Collection c)继承自AbstractCollection类。
该方法用for-each遍历c集合,同时查看c集合中是否包含e元素。
若包含,则返回ture.否则返回false.
public boolean equals(Object o)
作用:这里调用的equals方法并不是Object类中的。LinkedList继承了AbstractList< E>,它的equals方法应该从这个抽象类中来的。发现该类确实重写了equals方法,就是使用迭代器遍历两个List每个元素是否相等。 那么LinkedList调用equals方法就是元素进行比较了。
源码如下(同ArrayList):
//inheried from java.util.AbstractList
public boolean equals(Object o) {
if (o == this)//如果o为调用此方法的对象,则返回true
return true;
if (!(o instanceof List))//若对象o不是List对象,则返回false
return false;
//定义两个List迭代器用来遍历元素
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {//当两个迭代器均有下一个元素
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
源码分析:
if (!(o1 == null ? o2==null : o1.equals(o2)))
return false
若o1为空,返回o2 == null.则变为if(!(o2null));此时,若o2不为null,则直接返回false.
若o1非空,返回o1.equals(o2).则变为if(!(o1.equals(o2)))
【注】此处的equals()方法应为Object类中的equals()方法.若o1与o2不等,则返回false.
return !(e1.hasNext() || e2.hasNext());
若e1和e2都遍历完了,未返回false,则认定equals()返回true.
public Object get(int index)
作用:返回链表中指定位置的元素
源码如下:
public E get(int index) {
checkElementIndex(index);
return node(index).item;//调用node()返回目标节点的值。node(int index)源码详见上述:public boolean addAll(Collection c)
}
//判断参数index是否是元素的索引(如果不是则抛出异常)
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//判断index索引值是否合法(0 <= index < size)
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
源码分析:
首先通过checkElementIndex()检查索引值是否合法。然后通过node()获取目标节点的元素值。
【注】在LinkedList集合中checkElementIndex()与checkPositionIndex()均为判断索引值知否合法,但是判断的索引范围有细微区别:checkPositionIndex()包含index<=size,而checkElementIndex()不包含
public int hashCode()
作用:求LinkedList的哈希值,这个方法是LinkedList的抽象父类AbstractList中的方法
源码如下(同ArrayList):
//inherited from java.util.AbstractList
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
源码分析:
其实这段代码就是如下数学表达式的实现:
s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1]
在JAVA语言中,判断两个对象是否相等,一般有两种方法,一种是hashcode(),另一种是equals(),这两个方法在判断准确性和效率上有很大的区别。hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equal()里一般比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠。
public int indexOf(Object elem)
作用:返回链表中元素第一次出现的位置下标,如果没有此元素则返回-1.
源码如下:
public int indexOf(Object o) {
int index = 0;
//根据Object是否为空,分为两种情况
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {//遍历,查找链表中是否包含null元素
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;
}
源码分析:
首先依据obejct是否为空,分为两种情况:
然后通过在每种情况下,从头节点开始遍历LinkedList,判断是否有与object相等的元素,如果有,则返回对应的位置index,如果找不到,则返回-1。
public boolean isEmpty()
作用:判断此列表中是否为空,空则返回true,否则返回false.
源码如下:
public boolean isEmpty() {
return size == 0;
}
源码分析:
继承AbstractCollection中的isEmpty()方法,如果记录数组元素个数的size==0,表示数组中没有元素,所以返回true.否则,返回false.
public int lastIndexOf(Object elem)
作用:返回此链表中元素最后一次出现的位置下标,如果没有则返回-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;
}
源码分析:
分查找元素为空与非空两种情况进行讨论。从后往前遍历,只返回第一次出现的元素索引,如果没有找到,则返回-1。
public ListIterator listIterator(final int index)
作用:返回从指定索引开始进行操作的迭代器。
源码如下:
public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index);//判断索引是否越界,越界抛出异常
return new ListItr(index);
}
//链表迭代器
private class ListItr implements ListIterator<E> {
//迭代器最近一次返回的节点
private Node<E> lastReturned;
//迭代器即将返回的节点
private Node<E> next;
//迭代器即将要返回的节点的索引
private int nextIndex;
//通过内部类变量expectedModCount保存外部类变量modCount的值,以此保证外部类与内部类的快速失败机制的同步,在内部类方法执行的时候,ModCount如果改变,那么必然与expectedNodCount的值不等,直接触发快速失败机制
private int expectedModCount = modCount;
//构造方法,创建一个从指定索引index开始的迭代器
ListItr(int index) {
//如果index == size,则next为null,因为索引size处没有节点。否则,next是索引值为index的节点
next = (index == size) ? null : node(index);
//初始化变量nextIndex,索引为index的节点就是即将要返回的节点
nextIndex = index;
}
public boolean hasNext() {//检查迭代器中当前节点是否还有下个一节点
return nextIndex < size;
}
//返回节点的元素,迭代的方向是从左至右
public E next() {
checkForComodification();//同ArrayList;检查是否有其他线程修改了LinkedList链表
if (!hasNext())
throw new NoSuchElementException();
//next已经在构造方法中进行了初始化,如果next()方法顺利执行完毕,next引用的节点元素就会被返回,同时断开next与节点的引用关系,此节点称为迭代器最近一次返回的节点,所以在断开前需要将节点的引用传递给lastReturned,否则节点会被垃圾回收期回收
lastReturned = next;
//断开next引用,指向下一个节点,此时lastReturned引用迭代器最后一次返回的节点
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 = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
//用包含元素e的节点代替节点
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) {
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();
}
}
源码分析:
迭代器中定义了一些列操作,拥有迭代器的集合,遍历时不需要知道集合内部实现,迭代器统一了遍历方法,不同集合各自遍历方法可能不同,而迭代器则在上层提供了一个统一的抽象,只需要掌握一种迭代器使用就能操作多种不同类型的集合了。
public ListIterator listIterator()
作用:返回从头结点开始迭代的迭代器
源码如下:
//inherited from AbstractList
public ListIterator<E> listIterator() {
return listIterator(0);//返回从指定节点(头结点)开始的迭代器,listIterator(final int index)源码详见上述:public ListIterator listIterator(final int index)
}
源码分析:
listIterator()方法底层调用listIterator(final int index)实现,返回从头结点开始进行迭代的迭代器
public Iterator descendingIterator()
作用:返回逆序的元素的迭代器
源码分析:
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());//获取ListItr对象
public boolean hasNext() {//进行逆向迭代
return itr.hasPrevious();
}
public E next() {//逆向迭代
return itr.previous();
}
public void remove() {
itr.remove();
}
}
源码分析:
从类名和上面的代码可以看出这是一个反向的Iterator,代码很简单,都是调用的ListItr类中的方法。
public Object remove(int index)
作用:移除指定位置的元素。
源码如下:
public E remove(int index) {
checkElementIndex(index);//检查索引是否合法
return unlink(node(index));//移除索引处的节点
}
源码分析:
checkElementIndex(),源码详见上述:public Object get(int index)
node(),源码详见上述:public boolean addAll(Collection c)
unlink(),源码详见上述:public boolean remove(Object o)
public boolean removeAll(Collection c)
作用:移除指定集合在此列表中的所有元素
源码如下:
//inherited from AbstractCollection
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//判断要移除的集合是否为空
return batchRemove(c, false);
}
/*
@parameter:Collection c:目标集合
boolean complement:当前集合是否需要包含目标集合
false,则删除目标集合在当前集合中所存在的元素
true,则删除目标集合在当前集合中不纯在的元素
*/
private boolean batchRemove(Collection<?> c, boolean complement)//
final Object[] elementData = this.elementData;//定义当前数组对象
int r = 0, w = 0;
// r 在外层定义for循环所用下标,目的是为了判断有没有报错,如果报错了,r就!=size,如果没有报错r==size
// w 进行操作的次数
boolean modified = false;//定义成功标识
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)//目标集合中元素存在或不存在 在当前集合中
elementData[w++] = elementData[r];//将不需要移除的元素放入elementData数组中
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {//表示报错了,与ArrayList的父类保持一致,当c.contains()这个方法抛出异常才会r != size
System.arraycopy(elementData, r,
elementData, w,
size - r);//复制数组,从报错下标开始复制到 w下标(最后被修改的下标),复制长度是未成功循环的长度
w += size - r;//因为复制后数组长度就变了,所以需要求出目前数组的长度,w+ 复制的长度
}
if (w != size) {// 表示原数组有删除了数据
// clear to let GC do its work
for (int i = w; i < size; i++)
//从w开始循环,循环到size,这些数据是要删除的数据,设为null
elementData[i] = null;
modCount += size - w;//修改操作次数计数 size-w表示删除的数量
size = w; //将长度设为新的长度
modified = true;
}
}
return modified;
}
源码分析:
1.判断要移除的集合是否为null,如果为空则抛出null异常
2.如果不为null,则调用batchRemove方法即可移除成功
2.1. 通过contains方法,将所有不需要移除的元素放入elementData数组中,然后标记最后一个放入元素的位置w
2.2. 将所有需要移除的元素设置为null
2.3. 将size的大小设置为w
2.4. 修改modCount字段(该阻断主要是为了防止多线程模式下安全问题)
2.5. 返回移除成功的标识
public boolean retainAll(Collection c)
作用:原集合和参数集合做交集,保留相同的部分。
源码如下:
//inherited from AbstractCollection
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);//判断要移除的集合是否为空
boolean modified = false;//判断链表是否被修改
Iterator<E> it = iterator();
while (it.hasNext()) {//遍历链表
//如果c中不包含原集合某元素,则在元集合中删除此元素
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
源码分析:
使用迭代器遍历LinkedList集合,比较是否含有c中的元素,将LinkedList集合中与c中元素不等的元素移除。
public Object set(int index,Object element)
作用:修改LinkedList链表指定位置的值,并返回原值
源码如下:
public E set(int index, E element) {
checkElementIndex(index);//检查索引是否合法
Node<E> x = node(index);//Node类型变量x指向index所在的节点
E oldVal = x.item;//将index位置节点的元素赋值给oldVal
x.item = element;//用element覆盖初值
return oldVal;
}
public List subList(int fromIndex,int toIndex)
作用:只返回包含从fromIndex到toIndex之间的数据元素的链表。
源码如下:
//inherited from AbstractList
public List<E> subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ?
new RandomAccessSubList<E>(this, fromIndex, toIndex) :
new SubList<E>(this, fromIndex, toIndex));
}
源码分析:
这里的this是多态的,指的是那个外部调用subList方法的那个List对象,先判断this是否实现了RandomAccess接口,实现返回RandomAccessSublist,否则返回SubList,通过构造方法将this传给SubList中属性。
一.若外部调用subList方法的那个List对象实现了RandomAccess接口
RandomAccess接口源码如下:
public interface RandomAccess {
}
源码分析:
RandomAccess定义为空接口作为标识接口,实现了RandomAccess接口的对象(? instance RandomAccess)返回1。
RandomAccessSublist源码如下所示:
//inherited from AbstractList
class RandomAccessSubList<E> extends SubList<E> implements RandomAccess {
RandomAccessSubList(AbstractList<E> list, int fromIndex, int toIndex) {
super(list, fromIndex, toIndex);
}
public List<E> subList(int fromIndex, int toIndex) {
return new RandomAccessSubList<>(this, fromIndex, toIndex);
}
}
源码分析:
RandomAccessSubList中的构造方法调用了父类SubList类的构造方法
subList构造方法源码(部分)如下:
//from AbstractList
class SubList<E> extends AbstractList<E> {
private final AbstractList<E> l;
private final int offset;
private int size;
SubList(AbstractList<E> list, int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > list.size())
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
l = list;
offset = fromIndex;
size = toIndex - fromIndex;
this.modCount = l.modCount;
}
…………………//(其他方法)
}
通过对offset与size的限定,给调用subList方法的对象返回集合中的部分视图
二.若外部调用subList方法的那个List对象未实现了RandomAccess接口,则直接返回SubList,源码同上。
public Object[] toArray()
作用:将当前集合中的所有元素转化成顶级Object类型对象数组返回
源码如下:
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;
}
源码分析:
1.创建一个与LinkedList链表等大的数组。
2.通过遍历LinkedList链表,将链表元素添加到数组中。
public Object[] toArray(Object []a)
作用:将集合中的元素依次存入参数数组中并返回。
源码如下:
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;
}
源码分析:
猜想:通过反射机制将链表的值赋值到a数组中。
实现Deque接口方法
public void addFirst(Object element)
作用:将指定元素查到此列表的开头
源码如下:
public void addFirst(E e) {
linkFirst(e);//调用linkFirst将元素e插入到链表的表头
}
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++;
}
源码分析:
linkFirst()的实现分为两种情况:1.LinkedList链表为空;2.LinkedList链表非空
1.当LinkedList链表为空调用linkFirst(),如下图所示:
2.当LinkedList链表非空调用linkFirst(),如下图所示:
public void addLast(Object element)
作用:在LinkedList尾部添加一个新元素。
源码如下:
public void addLast(E e) {
linkLast(e);
}
源码分析:
底层调用了linkLast方法,linkLast方法详见上述:public boolean add(Object o);
public Object getFirst()
作用:返回LinkedList链表头结点的元素
源码如下:
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
源码分析:
当头结点为空时,抛出异常;否则,将头结点元素返回。
public Object getLast()
作用:返回LinkedList链表尾结点的元素
源码如下:
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
源码分析:
当尾结点为空时,抛出异常;否则,将尾结点元素返回。
public Object removeFirst()
作用:移除LinkedList头结点.
源码如下:
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(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;
}
源码分析:
当头结点为空时,抛出异常。否则,调用unlinkFirst()移除头结点。removeFirst()执行过程如下图所示:
public Object removeLast()
作用:移除LinkedList链表尾结点。
源码如下:
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(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;
}
源码分析:
当尾结点为空时,抛出异常。否则,调用unlinkLast()移除头结点。removeLast()执行过程如下图所示:
其他方法
public Object clone()
作用:此方法用于创建链表的重复副本和浅表副本(浅复制)
源码如下:
//浅克隆(元素不会被克隆);return一个副本
public Object clone() {
LinkedList<E> clone = superClone();//调用父类(Object)的克隆方法
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;
}
private LinkedList<E> superClone() {
try {
return (LinkedList<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
源码解析:
首先获取从辅助方法返回的LinkedList对象,接着把该对象的所有域都设置为初始值。然后把LinkedList中所有的内容复制到返回的对象中。
public Iterator iterator()
作用:返回当前集合的双向迭代器。
源码如下:
//inherited from AbstractList
public Iterator<E> iterator() {
return new Itr();
}
返回的是一个Itr类的对象,接下来我们来看它的源码
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
源码分析:
每个实现Iterable接口的类必须提供一个iterator方法,返回一个Iterator对象,LinkedList也不例外。
cursor--;
expectedModCount = modCount;
如图所示,Cursor初始状态是指向5,即若调用ArrayList的迭代器使用next()遍历数组,在遍历途中使用ArrayList.remove(1),则会跳原本应该遍历的5,直接遍历到6.采用上述代码后,它在每一次删除之后都会将cursor(下一项)的位置设置为当前位置,也就是将cursor往前移动了一位,之后再将modCount赋值给expectedModCount使它们保持相等。
public String toString()
作用:一个arraylist转换为字符串
源码如下:
//inherited from AbstractCollection
public String toString() {
Iterator<E> it = iterator();//集合本身调用迭代器方法(this.iterator),得到集合迭代器
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
源码分析:
若集合没有元素->返回String类型"[]"
若集合有元素->先拼接'['->进入for死循环用append拼接e元素+','+' '->没有元素使用return跳出死循环并拼接']'
六.参考资料
LinkedList源码解析 基于Node结构
【Java集合】LinkedList详解前篇
JavaSE基础知识(二十一)--Java集合(容器)之类LinkedList的内部类ListItr的源码分析
LinkedList源码解析(基于JDK1.8)
LinkedList类详解的更多相关文章
- java之StringBuffer类详解
StringBuffer 线程安全的可变字符序列. StringBuffer源码分析(JDK1.6): public final class StringBuffer extends Abstract ...
- java之AbstractStringBuilder类详解
目录 AbstractStringBuilder类 字段 构造器 方法 public abstract String toString() 扩充容量 void expandCapacity(in ...
- java之StringBuilder类详解
StringBuilder 非线程安全的可变字符序列 .该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍).如果可能,建议优先采用该类,因为在 ...
- Java String类详解
Java String类详解 Java字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生. 类结构: public final ...
- QAction类详解:
先贴一段描述:Qt文档原文: Detailed Description The QAction class provides an abstract user interface action tha ...
- JAVAEE学习——struts2_01:简介、搭建、架构、配置、action类详解和练习:客户列表
一.struts2是什么 1.概念 2.struts2使用优势以及历史 二.搭建struts2框架 1.导包 (解压缩)struts2-blank.war就会看到 2.书写Action类 public ...
- Struts2-整理笔记(二)常量配置、动态方法调用、Action类详解
1.修改struts2常量配置(3种) 第一种 在str/struts.xml中添加constant标签 <struts> <!-- 如果使用使用动态方法调用和include冲突 - ...
- C# 内置 DateTime类详解
C# 内置 DateTime类详解 摘抄自微软官方文档,用来方便自己查阅:网址:https://msdn.microsoft.com/zh-cn/library/system.datetime(v=v ...
- Android游戏开发之旅 View类详解
Android游戏开发之旅 View类详解 自定义 View的常用方法: onFinishInflate() 当View中所有的子控件 均被映射成xml后触发 onMeasure(int, int) ...
随机推荐
- bash copy multi files
bash copy multi files # copy one file $ cp file1.js /var/www/html # copy multi files ??? no space $ ...
- 开放式 Web 应用程序安全性项目 OWASP
开放式 Web 应用程序安全性项目 OWASP Open Web Application Security Project (OWASP) OWASP 基金会是谁? Open Web Applicat ...
- Vue 3 In Action
Vue 3 In Action $ yarn add vue https://v3.vuejs.org demos refs https://v3.vuejs.org/guide/migration/ ...
- holy shit StackOverflow
holy shit StackOverflow refs https://stackoverflow.com/users/5934465/xgqfrms?tab=questions xgqfrms 2 ...
- React Native Apps
React Native Apps https://github.com/ReactNativeNews/React-Native-Apps github app https://github.com ...
- git hooks & pre-commit
git hooks & pre-commit
- 08_MySQL数据库的字段约束
数据库的字段约束 实战: CREATE TABLE t_teacher ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) N ...
- vue_webpack
1.生成项目工程描述文件 npm init 2.安装webpack开发依赖 (本地安装):npm install -D 3.(webpack4.0版本以上安装webpack cli) npm inst ...
- java对象克隆复制
原文链接:https://blog.csdn.net/ztchun/article/details/79110096 自己先简单描述总结一下:当想要将一个对象中已有的值直接给另外一个对象的时候,其实并 ...
- linux的0,1,2号进程
一.答案 https://blog.csdn.net/gatieme/article/details/51532804 仔细读一下作者的博客,都是操作系统底层相关. 二.补充: 1.linux代码,a ...