今天接着浅析一下JDK中的另一个集合类LinkedList.
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
可以看到LinkedList和ArrayList一样同样实现了List接口.再看一下它的构造方法:
public LinkedList() {
header.next = header.previous = header;//初始化头结点
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);//加入Collection接口的实现类的存放的全部元素到LinkedList中
}
在默认构造方法中可以看到有个header的私有变量,看其声明:
private transient Entry<E> header = new Entry<E>(null, null, null);
header变量是Entry类型的,对于Entry看如下代码:
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
Entry是LinkedList的私有内部类,外部并不能访问,其里面声明了三个属性,分别是将要显示数据、指向下一个Entry的引用、指向前一个Entry的引用。从这里可以看到和ArrayList实现上的不同.LinkedList底层是采用链式结构,每一个Entry代表一个链上的节点,其中
包括指向上一个Entry和下一个Entry的引用,从而与相邻的Entry关联起来.
对于另一个有参构造方法,接收了一个Collection接口的类型,故可以是list,也可以是set,然后调用默认构造方法初始化一个
header节点用作头指针,然后调用addAll方法将传入参数中的集合的全部元素加入到LinkedList中.下面看一下addAll的方法源代码:
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
该源代码调用了一个addAll方法:
public boolean addAll(int index, Collection<? extends E> c) {
if (index < 0 || index > size) //如果传入的index越界,则抛出异常
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Object[] a = c.toArray();//将传入的Collection类型的参数转化为数组
int numNew = a.length;//得到数组的长度
if (numNew==0) //长度为0,返回false
return false;
modCount++;
Entry<E> successor = (index==size ? header : entry(index));//返回index位置上的Entry
Entry<E> predecessor = successor.previous;//得到上一个Entry
for (int i=0; i<numNew; i++) {//循环遍历数组
Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);//利用第i索引上的元素构造一个Entry
predecessor.next = e;//predecessor的下一个引用指向该Entry
predecessor = e;//此时的e变为上一个Entry,作为数组中第i+1位置上构造的Entry的previous
}
successor.previous = predecessor;//循环结束完以后让数组最后一个元素作为第index Entry的前一个Entry
size += numNew;//总长度增加数组长度个单位
return true;
}
上述代码中有一个entry(index)方法用于寻找第index上的entry,JDK在次方法中使用了一个技巧,下面看一下该方法的源代码:
private Entry<E> entry(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+size);
Entry<E> e = header;
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;//返回查找到的entry
}
通过该源代码可以看到jdk在查找相应entry时做了优化,避免全盘遍历链表查找,提高了效率
下面看一下其他方法:
public boolean add(E e) {
addBefore(e, header);
return true;
}
add方法调用了addBefore方法:
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);//改变了两次引用
newEntry.previous.next = newEntry;//将新元素前一个位置上的下一个引用指向该新元素
newEntry.next.previous = newEntry;//将新元素下一个位置上的上一个引用指向该新元素
size++;
modCount++;
return newEntry;
}
通过上述代码示意,可以看到添加时会修改相邻entry和带添加entry的相关引用,由于底层是有双向链表表示,所以插入一个新元素会改变4次引用
public E remove() {
return removeFirst();
}
public E removeFirst() {
return remove(header.next);
}
private E remove(Entry<E> e) {
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;
}
可以看到对于删除LinkedList中的某个entry,经过了多次调用才调到了其核心的方法remove(Entry e),在该方法中,通过
修改相邻entry的引用,再将自己设置为null,从而达到移除,自身的母的,最后随着size减少一个长度单位,完成了本次删除操作
综合来看,LinkedList底层使用了链表这一机制,这是与ArrayList最大的不同.使用链表可以对插入、删除操作频繁的环境效率更高,因为不需要想ArrayList那样频繁移动数组,然而通过entry(index)方法可以看出查找是必须从头节点开始一步一步开始往下找,效率上有点慢