一、LinkedList的结构
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
我们知道LinkedList是使用链表实现的线性表,其与ArrayList一样也继承了AbstractList,但其没有实现RandomAccess接口。
二、成员变量
1、size
transient int size = 0;
这个就是用来表明有多少个元素的。
2、first
transient Node<E> first;
这个是表明第一个元素。
3、last
transient Node<E> last;
表明最后一个元素。
整体来说其的成员变量较少。
三、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;
}
}
可以看到其是一个静态内部类,其有三个变量。item:表明该节点本身的数据值、next:表示该节点的下一个节点、prev:表明该节点的前一个节点。通过这些信息就像一个链条一样建立了元素之间的关系,同时我们也可以看到LinkedList是一个双向链表。
四、AbstractSequentialList
下面我们来看下AbstractSequentialList这个抽象类中的方法,这个类是用来定义一些按顺序存储的方法。
1、get(int index)
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
这个方法就是获取这个列表的第index个元素,可以看到其是通过迭代器去遍历获取元素的,ArrayList是直接获取数组对应的index位置的值。
2、set(int index, E element)
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);
}
}
这里也是通过迭代器先获取对应位置的值,再将原值返回,其他的add(int index, E element)、remove(int index)都是使用迭代器去操作的。
五、构造函数
1、LinkedList()
public LinkedList() {
}
这个构造方法可以看到其没有进行一些数据的初始化。
2、LinkedList(Collection<? extends E> c)
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
这个其的入参是一个集合类,再通过addAll方法将这些数据添加到LinkedList中。
六、方法
1、linkFirst(E 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++;
}
将参数e链接为第一个元素(添加到最前面)。可以看到这里是先通过元素值e去创建一个Node节点,节点的parent为null,下一个节点是原来的first元素,如果还没有first元素,则在设置这个newNode为first的时候也将其设置为last元素,如果first元素,则将原来的first节点的parent(prev)设置为newNode,再size++&modCount++。
2、linkLast(E e)
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,与前面的操作类似。
3、linkBefore(E e, Node<E> 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++;
}
这个是将元素添加在另一个节点succ的前面。其的操作时,先获取succ的prev前一个节点pred,再将pred设置为新节点parent节点,而succ设置为newNode的next节。可以看到Linked在进行first、last、明确前置节点或后值节点等插入操作的时候是很方便的,因为不想ArrayList那样,要将目前的这个后面(前面)的内容进行copy移动。
4、unlinkFirst(Node<E> f) & unlinkLast(Node<E> l)
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节点(删除现在的first节点)。其是获取现在这个first的next节点,将该节点设置为新的first,再将现在节点的对应值置为null。unlinkLast与其类似
5、unlink(Node<E> 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;
}
这个就是不链接(删除)一个具体的节点,可以看到这个也是相对ArrayList要好的(看具体数据的操作情况,老人如是查询,遍历多,还是节点的插入设置多)。其是将目前节点前置&后置节点删除,再将前置节点&后置节点通过next&prev将这两个节点关联。不过可以看到该方法的作用域是default。
6、getFirst() | getLast()
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
获取first( | last)节点。
7、removeFirst() | removeLast()
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
删除目前的first节点,可以看到其是调用的unlinkFirst方法去操作。removeLast方法是remove last节点。
8、addFirst(E e) addLast(E e)
public void addFirst(E e) {
linkFirst(e);
}
添加到first,也是包装的linkFirst(e)方法。addLast添加到last。
9、indexOf(Object o) | lastIndexOf(Object o)
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;
}
这里就是查找对应节点元素所在的位置。这个就是通过next一直往后获取再通过equals方法判断,但这里需要注意的是,可以将节点的item元素数据设为null,不过这里是查询出第一个。而lastIndexOf方法就是获取最后一个,其是通过prev往前移动。
10、peek() (Deque 队列接口方法)
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
这里是获取first元素的值,但其不会删除第一个元素。
11、poll() (Deque 队列接口方法)
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
这个方法也是获取first元素值,但其会调用unlinkFirst方法去删除first节点。
12、element() (Deque 队列接口方法)
public E element() {
return getFirst();
}
获取第一个元素,但整个方法与peek() 方法不同点是这个方法由于调用的是getFirst方法,所以如果first为null其会抛出NoSuchElementException异常,而不是像peek方法直接返回的null。
13、remove() (Deque 队列接口方法)
public E remove() {
return removeFirst();
}
移除first节点,调用removeFirst方法,如果为null抛出NoSuchElementException。
14、offer(E e) (Deque 队列接口方法)
public boolean offer(E e) {
return add(e);
}
将元素添加在last。
15、offerFirst(E e) (Deque 队列接口方法)
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
将元素添加在first。
....还有一些其他的Deque接口的方法实现就不再一一表述了,主要是最link相关的方法在包装丰富其功能。
16、toArray()
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;
}
这个是将List变为数组。根据size创建对应数组,然后在next遍历。
17、add(int index, E element)
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
这个方法就是将元素添加在指定位置,可以看到这里判断,如果是添加在last,则直接调用linkLast方法,如果不是,则调用前面梳理的linkBefore方法,这里的关键是获取对应index位置的Node,可以看到其是通过node(index)方法获取。
18、node(int 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的node去主要操作也是next遍历,但这里有个点,就是通过右移,如果大于则从后面往前推。如果小于则前面往后推。
六、LinkedList关于迭代器的实现
下面我们来看下在AbstractList中关于Iterator接口的实现(能直接访问到AbstractList的成员变量的值)。
1、ListItr
1)、成员变量
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
该内部类有4个成员变量。lastReturned:表上次获取时返回的节点、next:表示下次获取的时候返回的节点、nextIndex:表示下个元素的index位置、expectedModCount:一起修改次数,上篇有提过。
2)、一些方法
public boolean hasNext() {
return nextIndex < size;
}
是否还有下一个元素。
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
获取下一个元素,通过这里对这些成员变量的修改操作也明白上面对其的解释。
然后关于这个ListItr的一些其他方法也不再展开了,关于LinkedList的方法就是围着其的Node节点的三个变量的各种操作。