首先放出类图
然后是成员变量和构造方法
private transient Entry<E> header = new Entry(null, null, null);
private transient int size = 0;
private static final long serialVersionUID = 876323262645176354L;
public LinkedList()
{
this.header.next = (this.header.previous = this.header);
}
public LinkedList(Collection<? extends E> paramCollection)
{
this();
addAll(paramCollection);
}
在无参构造方法里面用到了一个对象 Entry(节点类)
private static class Entry<E>
{
E element;
Entry<E> next;
Entry<E> previous;
Entry(E paramE, Entry<E> paramEntry1, Entry<E> paramEntry2)
{
this.element = paramE;
this.next = paramEntry1;
this.previous = paramEntry2;
}
}
header是双向链表的头节点,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。 size是双向链表中节点实例的个数
注意这一段
this.header.next = (this.header.previous = this.header);
第一个构造方法不接受参数,将header实例的previous和next全部指向header实例(注意,这个是一个双向循环链表,如果不是循环链表,空链表的情况应该是header节点的前一节点和后一节点均为null),这样整个链表其实就只有header一个节点,用于表示一个空的链表。
其实
LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据,如下:
既然是双向链表,那么必定存在一种数据结构——我们可以称之为节点,节点实例保存业务数据,前一个节点的位置信息和后一个节点位置信息,如下图所示:
看一下add方法
public boolean add(E paramE)
{
addBefore(paramE, this.header);
return true;
}
addBefore
private Entry<E> addBefore(E paramE, Entry<E> paramEntry)
{
Entry localEntry = new Entry(paramE, paramEntry, paramEntry.previous);
localEntry.previous.next = localEntry;
localEntry.next.previous = localEntry;
this.size += 1;
this.modCount += 1;
return localEntry;
}
下面分解“添加第一个数据”的步骤:
第一步:初始化后LinkedList实例的情况:
第二步:初始化一个预添加的Entry实例(newEntry)。
Entry newEntry = newEntry(e, entry, entry.previous);
第三步:调整新加入节点和头结点(header)的前后指针。
newEntry.previous.next = newEntry;
newEntry.previous即header,newEntry.previous.next即header的next指向newEntry实例。在上图中应该是“4号线”指向newEntry。
newEntry.next.previous = newEntry;
newEntry.next即header,newEntry.next.previous即header的previous指向newEntry实例。在上图中应该是“3号线”指向newEntry。
调整后如下图所示:
图——加入第一个节点后LinkedList示意图
下面分解“添加第二个数据”的步骤:
第一步:新建节点。
图——添加第二个节点
第二步:调整新节点和头结点的前后指针信息。
图——调整前后指针信息
添加后续数据情况和上述一致,LinkedList实例是没有容量限制的。
总结,addBefore(E e,Entry<E> entry)实现在entry之前插入由e构造的新节点。而add(E e)实现在header节点之前插入由e构造的新节点。为了便于理解,下面给出插入节点的示意图。
public E get(int paramInt)
{
return entry(paramInt).element;
}
private Entry<E> entry(int paramInt)
{
if ((paramInt < 0) || (paramInt >= this.size)) {
throw new IndexOutOfBoundsException("Index: " + paramInt + ", Size: " + this.size);
}
Entry localEntry = this.header;
int i;
if (paramInt < this.size >> 1) {
for (i = 0; i <= paramInt; i++) {
localEntry = localEntry.next;
}
} else {
for (i = this.size; i > paramInt; i--) {
localEntry = localEntry.previous;
}
}
return localEntry;
}
至此,我们可以得出LinkedList的特点
1. 底层是双向循环链表
2.有序,可重复
3.查找慢,增删快(和ArrayList相比)