Java集合类源代码之LinkedList
- Java集合类源代码之LinkedList
- linked list简介:
- LinkedList必备知识点总结:
- (一)ArrayList与LinkedList的区别有哪些?
- 对于 ArrayList 来说:
- 1)get(int index)
- 2)add(E e)
- 3)add(int index, E element)
- 4)remove(int index)
- 对于对于 LinkedList 来说:
- 1)get(int index)
- 2)add(E e)
- 3)add(int index, E element)
- 4)remove(int index)
- (二)LinkedList作为队列使用
- 队列的基本方法
- 演示:
- .add()//添加元素
- .poll()//删除队列头元素
- .peek()//获取队列头元素,不删除
- (三)LinkedList作为栈使用
- 栈的基本方法
- 演示:
- .push() //将元素插入到栈顶
- .pop() //取出栈顶的元素并删除栈顶的元素
- .peek() //获取栈顶元素,不删除
- (四)LinkedList作为双端队列使用
- 双端队列的基本方法
- 演示:
- .addFirst() ; //在队列头部添加
- .pollFirst() ; //删除头部第一个元素(等价于poll())
- .peekFirst() ; //获取头部第一个元素(等价于peek())
- .addLast() ; //在队列尾部添加(等价于add())
- .pollLast() ; // 删除并返回最后一个节点
- .peekLast() ; //获取尾部第一个元素
- 迭代器:
- 迭代器的构造方法
- hasNext() 判断是否有下一个数据
- next()读取下一个数据
- hasPrevious() 判断当前位置前面是否还有数据
- previous()获取上一个数据
- 源码:
- 总结
Java集合类源代码之LinkedList
Java集合工具包位于Java.util包下,包含了很多常用的数据结构,如数组、链表、栈、队列、集合、哈希表等。学习Java集合框架下大致可以分为如下五个部分:List列表、Set集合、Map映射、迭代器(Iterator、Enumeration)、工具类(Arrays、Collections)。
Java集合框架图:
从上图中可以看出,集合类主要分为两大类:Collection和Map。
Collection是List、Set等集合高度抽象出来的接口,它包含了这些集合的基本操作,它主要又分为两大部分:List和Set。
今天我们说的Java集合类之 LinkedList
linked list简介:
LinkedList是基于 双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈、队列和双端队列来使用。
LinkedList同样是非线程安全的,只在单线程下适合使用。
LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。
LinkedList必备知识点总结:
(一)ArrayList与LinkedList的区别有哪些?
看这个之前大家可以先去看看ArrayList的源码总结
点这里可以到上到Arraylist的文章去区别已经说过一次了 以防还有人不知道 那么贴一次。
家人们 来自阿里面试官的问题:好好听,虽然我讲得不好
ArrayList 内部使用的动态数组来存储元素,LinkedList 内部使用的双向链表来存储元素,这也是 ArrayList 和 LinkedList 最本质的区别。
对于 ArrayList 来说:
1)get(int index)
get(int index) 方法的时间复杂度为 O ( 1 ) ,因为是直接从底层数组根据下标获取的,和数组长度无关。这也是 ArrayList 的最大优点。
// 获取index位置的元素值
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
}
2)add(E e)
add(E e) 方法会默认将元素添加到数组末尾,但需要考虑到数组扩容的情况,如果不需要扩容,时间复杂度为 O ( 1 )。
// 将e 添加到ArrayList中
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 将e添加到ArrayList的指定位置
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
但是 这个的话 需要考虑 是否超出容器的大小 看是否需要扩容 如果需要扩容的话,并且不是第一次(oldCapacity > 0)扩容的时候,内部执行的 Arrays.copyOf() 方法是耗时的关键,需要把原有数组中的元素复制到扩容后的新数组当中。
*这个点 我不太明白 因为我没看到我的源码,不过看一个大佬写的是如下源代码 *
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
3)add(int index, E element)
add(int index, E element) 方法将新的元素插入到指定的位置,需要先通过遍历查找这个元素,然后再进行插入,所以时间复杂度为 O ( n )。
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
4)remove(int index)
remove(int index) 方法将指定位置上的元素删除,考虑到需要复制底层数组,所以时间复杂度为 O ( n )
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
对于对于 LinkedList 来说:
1)get(int index)
get(int index) 方法的时间复杂度为 O ( n ) ,因为需要循环遍历整个链表。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
LinkedList.Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
LinkedList.Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
LinkedList.Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
下标小于链表长度的一半时,从前往后遍历;否则从后往前遍历,这样从理论上说,就节省了一半的时间。
如果下标为 0 或者 list.size() - 1 的话,时间复杂度为 O ( 1 ) O(1)O(1)。这种情况下,可以使用 getFirst() 和 getLast() 方法。
public E getFirst() {
final LinkedList.Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
public E getLast() {
final LinkedList.Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
first 和 last 在链表中是直接存储的,所以时间复杂度为 O ( 1 ) 。
2)add(E e)
add(E e) 方法默认将元素添加到链表末尾,所以时间复杂度为 O ( 1 ) O(1)O(1)。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final LinkedList.Node<E> l = last;
final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
3)add(int index, E element)
add(int index, E element) 方法将新的元素插入到指定的位置,需要先通过遍历查找这个元素,然后再进行插入,所以时间复杂度为 O ( n )
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
如果下标为 0 或者 list.size() - 1 的话,时间复杂度为 O ( 1 ) 。这种情况下,可以使用 addFirst() 和 addLast() 方法。
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final LinkedList.Node<E> f = first;
final LinkedList.Node<E> newNode = new LinkedList.Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
linkFirst() 只需要对 first 进行更新即可。
public void addLast(E e) {
linkLast(e);
}
void linkLast(E e) {
final LinkedList.Node<E> l = last;
final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
linkLast() 只需要对 last 进行更新即可。
需要注意的是,有些文章里面说,LinkedList 插入元素的时间复杂度近似 O ( 1 ),其实是有问题的,因为 add(int index, E element) 方法在插入元素的时候会调用 node(index) 查找元素,该方法之前我们之间已经确认过了,时间复杂度为 O ( n ),即便随后调用 linkBefore() 方法进行插入的时间复杂度为 O ( 1 ),总体上的时间复杂度仍然为 O ( n )才对。
void linkBefore(E e, LinkedList.Node<E> succ) {
// assert succ != null;
final LinkedList.Node<E> pred = succ.prev;
final LinkedList.Node<E> newNode = new LinkedList.Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
4)remove(int index)
remove(int index) 方法将指定位置上的元素删除,考虑到需要调用 node(index) 方法查找元素,所以时间复杂度为 O ( n )。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(LinkedList.Node<E> x) {
// assert x != null;
final E element = x.item;
final LinkedList.Node<E> next = x.next;
final LinkedList.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;
}
需要注意
如果列表很大很大,ArrayList 和 LinkedList 在内存的使用上也有所不同。LinkedList的每个元素都有更多开销,因为要存储上一个和下一个元素的地址。ArrayList 没有这样的开销。
但是,ArrayList 占用的内存在声明的时候就已经确定了(默认大小为 10),不管实际上是否添加了元素,因为复杂对象的数组会通过null 来填充。LinkedList 在声明的时候不需要指定大小,元素增加或者删除时大小随之改变。另外,ArrayList 只能用作列表;LinkedList 可以用作列表或者队列,因为它还实现了 Deque 接口。
(二)LinkedList作为队列使用
开头就说了 因为LinkedList是双向循环链表 所以它也可以当成队列,栈和双端队列来使用
首先我们先来说一下LinkedList关于队列操作的源码。
队列的基本方法
//定义
LinkedList<Integer> queue = new LinkedList<Integer>();
//添加元素
queue.add(1);
//删除队列头元素
queue.poll();
//获取队列头元素,不删除
queue.peek();
演示:
首先定义就不说了 直接看源码:
.add()//添加元素
public boolean add(E e) {
linkLast(e); //添加在队尾
return true;
}
.poll()//删除队列头元素
// 删除并返回第一个节点
// 若LinkedList的大小为0,则返回null
public E poll() {
if (size == 0)
return null;
return removeFirst();
}
.peek()//获取队列头元素,不删除
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peek() {
if (size == 0)
return null;
return getFirst();
}
(三)LinkedList作为栈使用
栈的基本方法
//定义栈
LinkedList<Integer> stack = new LinkedList<Integer>();
//将元素插入到栈顶
stack.push(1)
//取出栈顶的元素并删除栈顶的元素
stack.pop()
//获取栈顶元素,不删除
stack.peek()
演示:
源码分析如下:
.push() //将元素插入到栈顶
// 将e插入到双向链表开头
public void push(E e) {
addFirst(e);
}
.pop() //取出栈顶的元素并删除栈顶的元素
// 删除并返回第一个节点
public E pop() {
return removeFirst();
}
.peek() //获取栈顶元素,不删除
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peekFirst() {
if (size == 0)
return null;
return getFirst();
}
(四)LinkedList作为双端队列使用
双端队列的基本方法
//定义
LinkedList<Integer> deque = new LinkedList<Integer>();
deque.addFirst(); //在队列头部添加
deque.pollFirst(); //删除头部第一个元素(等价于poll())
deque.peekFirst(); //获取头部第一个元素(等价于peek())
deque.addLast(1); //在队列尾部添加(等价于add())
deque.pollLast(); //删除尾部第一个元素
deque.peekLast(); //获取尾部第一个元素
演示:
.addFirst() ; //在队列头部添加
// 将元素添加到LinkedList的起始位置
public void addFirst(E e) {
addBefore(e, header.next);
}
.pollFirst() ; //删除头部第一个元素(等价于poll())
// 删除并返回第一个节点
// 若LinkedList的大小为0,则返回null
public E pollFirst() {
if (size == 0)
return null;
return removeFirst();
}
.peekFirst() ; //获取头部第一个元素(等价于peek())
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peekFirst() {
if (size == 0)
return null;
return getFirst();
}
.addLast() ; //在队列尾部添加(等价于add())
// 将元素添加到LinkedList的结束位置
public void addLast(E e) {
addBefore(e, header);
}
.pollLast() ; // 删除并返回最后一个节点
// 删除并返回最后一个节点
// 若LinkedList的大小为0,则返回null
public E pollLast() {
if (size == 0)
return null;
return removeLast();
}
.peekLast() ; //获取尾部第一个元素
// 返回最后一个节点
// 若LinkedList的大小为0,则返回null
public E peekLast() {
if (size == 0)
return null;
return getLast();
}
迭代器:
这里就不再分析 添加元素,删除元素,查找数组的源码了 上面多多少少都提到了 然后 最下面的源码 也有介绍这些源码
这里主要分析一下迭代
Java 中存在一个双向迭代器的接口:ListIterator,这个接口提供了向前和向后的迭代方法,如下所示:
迭代器----是一个对象,他的工作是遍历并选择序列中的对象,而客户端的程序员不必知道和关心该序列底层的结构。迭代器通常被称为‘轻量级对象’,创建他的代价小。
其实说白了 就是正序遍历,反向迭代无非就说逆序遍历数组
演示:
public static void fun6() {
List<Integer> arraylist = new ArrayList<Integer>();
arraylist.add(1);
arraylist.add(2);
arraylist.add(3);
arraylist.add(5);
arraylist.add(0, 0);//在index=0的位置上插入10
arraylist.add(4, 4);
System.out.println("arraylist里的整数列表为:" + "\n" + arraylist);
//1,LinkedList支持在两端插入和删除操作。
LinkedList<Object> linkedlist = new LinkedList<Object>(arraylist);//使用数组列表创建链表
linkedlist.add(1, "CCW");//在index=1的位子上插入blue
linkedlist.removeLast();
linkedlist.addFirst("JSU");
System.out.println("linkedlist的列表为:" + "\n" + linkedlist);
//2,链表支持在中间任意位置增加和删除
linkedlist.remove(new Integer(1));//移除元素1
linkedlist.remove(2);//移除索引2的值
System.out.println("linkedlist移除后的新列表为:" + "\n" + linkedlist);
//3,链表支持双向遍历
ListIterator<Object> listiterator = linkedlist.listIterator();//获得列表迭代器,扩展了Iterator接口
System.out.println("linkedlist向后遍历的结果:");
while (listiterator.hasNext()) {//向后遍历
System.out.print(listiterator.next() + ", ");
}
System.out.println();
System.out.println("linkedlist向前遍历的结果:");
while (listiterator.hasPrevious()) {//向前遍历,即倒序遍历
System.out.print(listiterator.previous() + ", ");
}
简单说一下 迭代的方法:
迭代器的构造方法
public ListIterator<E> listIterator() {
return listIterator(0);
}
hasNext() 判断是否有下一个数据
// 是否存在下一个元素
public boolean hasNext() {
// 通过元素索引是否等于“双向链表大小”来判断是否达到最后。
return nextIndex != size;
}
next()读取下一个数据
// 获取下一个元素
public E next() {
checkForComodification();
if (nextIndex == size)
throw new NoSuchElementException();
lastReturned = next;
// next指向链表的下一个元素
next = next.next;
nextIndex++;
return lastReturned.element;
}
hasPrevious() 判断当前位置前面是否还有数据
// 是否存在上一个元素
public boolean hasPrevious() {
// 通过元素索引是否等于0,来判断是否达到开头。
return nextIndex != 0;
}
previous()获取上一个数据
// 获取上一个元素
public E previous() {
if (nextIndex == 0)
throw new NoSuchElementException();
// next指向链表的上一个元素
lastReturned = next = next.previous;
nextIndex--;
checkForComodification();
return lastReturned.element;
}
源码:
package java.util;
public class LinkedList<E> extends AbstractSequentialList<E> implements
List<E>, Deque<E>, Cloneable, java.io.Serializable {
// 链表的表头,表头不包含任何数据。Entry是个链表类数据结构。
private transient Entryheader = new Entry(null, null, null);
// LinkedList中元素个数
private transient int size = 0;
// 默认构造函数:创建一个空的链表
public LinkedList() {
header.next = header.previous = header;
}
// 包含“集合”的构造函数:创建一个包含“集合”的LinkedList
public LinkedList(Collection c) {
this();
addAll(c);
}
// 获取LinkedList的第一个元素
public E getFirst() {
if (size == 0)
throw new NoSuchElementException();
// 链表的表头header中不包含数据。
// 这里返回header所指下一个节点所包含的数据。
return header.next.element;
}
// 获取LinkedList的最后一个元素
public E getLast() {
if (size == 0)
throw new NoSuchElementException();
// 由于LinkedList是双向链表;而表头header不包含数据。
// 因而,这里返回表头header的前一个节点所包含的数据。
return header.previous.element;
}
// 删除LinkedList的第一个元素
public E removeFirst() {
return remove(header.next);
}
// 删除LinkedList的最后一个元素
public E removeLast() {
return remove(header.previous);
}
// 将元素添加到LinkedList的起始位置
public void addFirst(E e) {
addBefore(e, header.next);
}
// 将元素添加到LinkedList的结束位置
public void addLast(E e) {
addBefore(e, header);
}
// 判断LinkedList是否包含元素(o)
public boolean contains(Object o) {
return indexOf(o) != -1;
}
// 返回LinkedList的大小
public int size() {
return size;
}
// 将元素(E)添加到LinkedList中
public boolean add(E e) {
// 将节点(节点数据是e)添加到表头(header)之前。
// 即,将节点添加到双向链表的末端。
addBefore(e, header);
return true;
}
// 从LinkedList中删除元素(o)
// 从链表开始查找,如存在元素(o)则删除该元素并返回true;
// 否则,返回false。
public boolean remove(Object o) {
if (o == null) {
// 若o为null的删除情况
for (Entrye = header.next; e != header; e = e.next) {
if (e.element == null) {
remove(e);
return true;
}
}
} else {
// 若o不为null的删除情况
for (Entrye = header.next; e != header; e = e.next) {
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
// 将“集合(c)”添加到LinkedList中。
// 实际上,是从双向链表的末尾开始,将“集合(c)”添加到双向链表中。
public boolean addAll(Collection c) {
return addAll(size, c);
}
// 从双向链表的index开始,将“集合(c)”添加到双向链表中。
public boolean addAll(int index, Collection c) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
Object[] a = c.toArray();
// 获取集合的长度
int numNew = a.length;
if (numNew == 0)
return false;
modCount++;
// 设置“当前要插入节点的后一个节点”
Entrysuccessor = (index == size ? header : entry(index));
// 设置“当前要插入节点的前一个节点”
Entrypredecessor = successor.previous;
// 将集合(c)全部插入双向链表中
for (int i = 0; i < numNew; i++) {
Entrye = new Entry((E) a[i], successor, predecessor);
predecessor.next = e;
predecessor = e;
}
successor.previous = predecessor;
// 调整LinkedList的实际大小
size += numNew;
return true;
}
// 清空双向链表
public void clear() {
Entrye = header.next;
// 从表头开始,逐个向后遍历;对遍历到的节点执行一下操作:
// (01) 设置前一个节点为null
// (02) 设置当前节点的内容为null
// (03) 设置后一个节点为“新的当前节点”
while (e != header) {
Entrynext = e.next;
e.next = e.previous = null;
e.element = null;
e = next;
}
header.next = header.previous = header;
// 设置大小为0
size = 0;
modCount++;
}
// 返回LinkedList指定位置的元素
public E get(int index) {
return entry(index).element;
}
// 设置index位置对应的节点的值为element
public E set(int index, E element) {
Entrye = entry(index);
E oldVal = e.element;
e.element = element;
return oldVal;
}
// 在index前添加节点,且节点的值为element
public void add(int index, E element) {
addBefore(element, (index == size ? header : entry(index)));
}
// 删除index位置的节点
public E remove(int index) {
return remove(entry(index));
}
// 获取双向链表中指定位置的节点
private Entryentry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "
+ size);
Entrye = header;
// 获取index处的节点。
// 若index < 双向链表长度的1/2,则从前先后查找;
// 否则,从后向前查找。
if (index < (size >> 1)) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
// 从前向后查找,返回“值为对象(o)的节点对应的索引”
// 不存在就返回-1
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Entry e = header.next; e != header; e = e.next) {
if (e.element == null)
return index;
index++;
}
} else {
for (Entry e = header.next; e != header; e = e.next) {
if (o.equals(e.element))
return index;
index++;
}
}
return -1;
}
// 从后向前查找,返回“值为对象(o)的节点对应的索引”
// 不存在就返回-1
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
for (Entry e = header.previous; e != header; e = e.previous) {
index--;
if (e.element == null)
return index;
}
} else {
for (Entry e = header.previous; e != header; e = e.previous) {
index--;
if (o.equals(e.element))
return index;
}
}
return -1;
}
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peek() {
if (size == 0)
return null;
return getFirst();
}
// 返回第一个节点
// 若LinkedList的大小为0,则抛出异常
public E element() {
return getFirst();
}
// 删除并返回第一个节点
// 若LinkedList的大小为0,则返回null
public E poll() {
if (size == 0)
return null;
return removeFirst();
}
// 将e添加双向链表末尾
public boolean offer(E e) {
return add(e);
}
// 将e添加双向链表开头
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
// 将e添加双向链表末尾
public boolean offerLast(E e) {
addLast(e);
return true;
}
// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peekFirst() {
if (size == 0)
return null;
return getFirst();
}
// 返回最后一个节点
// 若LinkedList的大小为0,则返回null
public E peekLast() {
if (size == 0)
return null;
return getLast();
}
// 删除并返回第一个节点
// 若LinkedList的大小为0,则返回null
public E pollFirst() {
if (size == 0)
return null;
return removeFirst();
}
// 删除并返回最后一个节点
// 若LinkedList的大小为0,则返回null
public E pollLast() {
if (size == 0)
return null;
return removeLast();
}
// 将e插入到双向链表开头
public void push(E e) {
addFirst(e);
}
// 删除并返回第一个节点
public E pop() {
return removeFirst();
}
// 从LinkedList开始向后查找,删除第一个值为元素(o)的节点
// 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
// 从LinkedList末尾向前查找,删除第一个值为元素(o)的节点
// 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
public boolean removeLastOccurrence(Object o) {
if (o == null) {
for (Entrye = header.previous; e != header; e = e.previous) {
if (e.element == null) {
remove(e);
return true;
}
}
} else {
for (Entrye = header.previous; e != header; e = e.previous) {
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
// 返回“index到末尾的全部节点”对应的ListIterator对象(List迭代器)
public ListIteratorlistIterator(int index) {
return new ListItr(index);
}
// List迭代器
private class ListItr implements ListIterator<E> {
// 上一次返回的节点
private EntrylastReturned = header;
// 下一个节点
private Entrynext;
// 下一个节点对应的索引值
private int nextIndex;
// 期望的改变计数。用来实现fail-fast机制。
private int expectedModCount = modCount;
// 构造函数。
// 从index位置开始进行迭代
ListItr(int index) {
// index的有效性处理
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index
+ ", Size: " + size);
// 若 “index 小于 ‘双向链表长度的一半’”,则从第一个元素开始往后查找;
// 否则,从最后一个元素往前查找。
if (index < (size >> 1)) {
next = header.next;
for (nextIndex = 0; nextIndex < index; nextIndex++)
next = next.next;
} else {
next = header;
for (nextIndex = size; nextIndex > index; nextIndex--)
next = next.previous;
}
}
// 是否存在下一个元素
public boolean hasNext() {
// 通过元素索引是否等于“双向链表大小”来判断是否达到最后。
return nextIndex != size;
}
// 获取下一个元素
public E next() {
checkForComodification();
if (nextIndex == size)
throw new NoSuchElementException();
lastReturned = next;
// next指向链表的下一个元素
next = next.next;
nextIndex++;
return lastReturned.element;
}
// 是否存在上一个元素
public boolean hasPrevious() {
// 通过元素索引是否等于0,来判断是否达到开头。
return nextIndex != 0;
}
// 获取上一个元素
public E previous() {
if (nextIndex == 0)
throw new NoSuchElementException();
// next指向链表的上一个元素
lastReturned = next = next.previous;
nextIndex--;
checkForComodification();
return lastReturned.element;
}
// 获取下一个元素的索引
public int nextIndex() {
return nextIndex;
}
// 获取上一个元素的索引
public int previousIndex() {
return nextIndex - 1;
}
// 删除当前元素。
// 删除双向链表中的当前节点
public void remove() {
checkForComodification();
EntrylastNext = lastReturned.next;
try {
LinkedList.this.remove(lastReturned);
} catch (NoSuchElementException e) {
throw new IllegalStateException();
}
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = header;
expectedModCount++;
}
// 设置当前节点为e
public void set(E e) {
if (lastReturned == header)
throw new IllegalStateException();
checkForComodification();
lastReturned.element = e;
}
// 将e添加到当前节点的前面
public void add(E e) {
checkForComodification();
lastReturned = header;
addBefore(e, next);
nextIndex++;
expectedModCount++;
}
// 判断 “modCount和expectedModCount是否相等”,依次来实现fail-fast机制。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
// 双向链表的节点所对应的数据结构。
// 包含3部分:上一节点,下一节点,当前节点值。
private static class Entry<E> {
// 当前节点所包含的值
E element;
// 下一个节点
Entrynext;
// 上一个节点
Entryprevious;
/**
* 链表节点的构造函数。 参数说明: element —— 节点所包含的数据 next —— 下一个节点 previous —— 上一个节点
*/
Entry(E element, Entrynext, Entryprevious) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
// 将节点(节点数据是e)添加到entry节点之前。
private EntryaddBefore(E e, Entryentry) {
// 新建节点newEntry,将newEntry插入到节点e之前;并且设置newEntry的数据是e
EntrynewEntry = new Entry(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
// 修改LinkedList大小
size++;
// 修改LinkedList的修改统计数:用来实现fail-fast机制。
modCount++;
return newEntry;
}
// 将节点从链表中删除
private E remove(Entrye) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
// 反向迭代器
public IteratordescendingIterator() {
return new DescendingIterator();
}
// 反向迭代器实现类。
private class DescendingIterator implements Iterator {
final ListItr itr = new ListItr(size());
// 反向迭代器是否下一个元素。
// 实际上是判断双向链表的当前节点是否达到开头
public boolean hasNext() {
return itr.hasPrevious();
}
// 反向迭代器获取下一个元素。
// 实际上是获取双向链表的前一个节点
public E next() {
return itr.previous();
}
// 删除当前节点
public void remove() {
itr.remove();
}
}
// 返回LinkedList的Object[]数组
public Object[] toArray() {
// 新建Object[]数组
Object[] result = new Object[size];
int i = 0;
// 将链表中所有节点的数据都添加到Object[]数组中
for (Entrye = header.next; e != header; e = e.next)
result[i++] = e.element;
return result;
}
// 返回LinkedList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
public T[] toArray(T[] a) {
// 若数组a的大小 < LinkedList的元素个数(意味着数组a不能容纳LinkedList中全部元素)
// 则新建一个T[]数组,T[]的大小为LinkedList大小,并将该T[]赋值给a。
if (a.length < size)
a = (T[]) java.lang.reflect.Array.newInstance(a.getClass()
.getComponentType(), size);
// 将链表中所有节点的数据都添加到数组a中
int i = 0;
Object[] result = a;
for (Entrye = header.next; e != header; e = e.next)
result[i++] = e.element;
if (a.length > size)
a[size] = null;
return a;
}
// 克隆函数。返回LinkedList的克隆对象。
public Object clone() {
LinkedListclone = null;
// 克隆一个LinkedList克隆对象
try {
clone = (LinkedList) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
// 新建LinkedList表头节点
clone.header = new Entry(null, null, null);
clone.header.next = clone.header.previous = clone.header;
clone.size = 0;
clone.modCount = 0;
// 将链表中所有节点的数据都添加到克隆对象中
for (Entrye = header.next; e != header; e = e.next)
clone.add(e.element);
return clone;
}
// java.io.Serializable的写入函数
// 将LinkedList的“容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// 写入“容量”
s.writeInt(size);
// 将链表中所有节点的数据都写入到输出流中
for (Entry e = header.next; e != header; e = e.next)
s.writeObject(e.element);
}
// java.io.Serializable的读取函数:根据写入方式反向读出
// 先将LinkedList的“容量”读出,然后将“所有的元素值”读出
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// 从输入流中读取“容量”
int size = s.readInt();
// 新建链表表头节点
header = new Entry(null, null, null);
header.next = header.previous = header;
// 从输入流中将“所有的元素值”并逐个添加到链表中
for (int i = 0; i < size; i++)
addBefore((E) s.readObject(), header);
}
}
总结
1、注意源码中的Entryentry(int index)方法。该方法返回双向链表中指定位置处的节点,而链表中是没有下标索引的,要指定位置出的元素,就要遍历该链表。
从源码的实现中,我们看到这里有一个加速动作。源码中先将index与长度size的一半比较,如果indexsize/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。
2、注意链表类对应的数据结构Entry。如下;
3、LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
4、在查找和删除某元素时,源码中都划分为该元素为null和不为null两种情况来处理,LinkedList中允许元素为null。