1. LinkedList特点
LinkedList是一个实现了List和Deque接口的双向链表,学过数据结构的话应该就很容易理解它的实现原理,它的内部维护链表的头元素和尾元素,所以一些像get,set
操作都需要从链表头或者尾部开始,因此它的随机访问性能不高
1. 允许所有类型的元素,包括null(元素可重复)
2. 插入以及删除操作效率高,只需要改变元素之间的关联关系
3. 随机访问性能差,虽然提供get(i)
方法,但仍需要从一端开始遍历
4. 非线程安全,多线程进行访问时,很可能会发生fail-fast事件
2. 链表节点Node
LinkedList没什么属性,主要有两个:
transient Node<E> first; //首元素
transient Node<E> last; //尾元素
例如,有一个LinkedList实例,它有三个元素,分别是item1,item2,item3,那么first指向item1,last指向item3
来看一下Node类的代码,很简单就3属性,item表示元素值,next指向下一个节点,prev指向前一个元素,这样就构成了上图的双向链表
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;
}
}
3. 添加元素
public boolean add(E e)
添加一个元素,默认会添加到链表尾部,该方法调用linkLast()
方法
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++;
}
public void add(int index, E element);
在指定的位置index插入元素,原index以及后面的元素将会在新元素的右边。如果index==size
,则表示在最后的位置插入元素,调用上述的linkLast
方法,否则调用以下的linkBefore
方法:
void linkBefore(E e, Node<E> succ) { //succ是原本第index个元素
// assert succ != null;
final Node<E> pred = succ.prev; //新元素的前一个元素是原本succ的前一个元素
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;//succ的前一个元素变成新插入的元素
if (pred == null) //表示是链表第一次插入元素
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
主要是调整pre,newNode,succ三个元素之间的指向
public boolean addAll(int index, Collection
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
Node<E> pred, succ; //succ指向index元素,pred指向succ的前一个元素
if (index == size) { //如果插入的位置是最后
succ = null;
pred = last;
} else {
succ = node(index);
pred = succ.prev;
}
for (Object o : a) { //循环集合所有元素,一个个添加到链表中
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null); //构造新节点,注意,构造函数next先指定为null,要等下一个元素插入再连接上
if (pred == null) //表示是链表中的第一个元素
first = newNode;
else //前一个元素的next指向新插入的元素
pred.next = newNode;
pred = newNode;
}
if (succ == null) {//表示在最后一个元素的后面插入
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
public void addFirst(E e)
在链表的头部添加元素E,调用linkFirst
方法
public void addLast(E e)
在链表的尾部添加元素E,调用linkedLast
方法
4. 删除元素
public E remove()
默认删除头部的元素
public E remove(int index)
删除第index个元素,如果index超出范围,则抛出IndexOutOfBoundsException
异常,以index为参数,通过node(index)
方法获取到元素e,再调用unlink(e)
把e从链表中删除
public boolean remove(Object o)
删除第一个中在LindedList出现的指定的元素o,o指的是Node中的elem属性,它可以为null
public boolean remove(Object o) {
if (o == null) { //删除的元素为null,则从list中找出第一个elem为null的Node,并unlink
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) { //对于非null的元素,通过equals进行判断是否相等,所以如果是自定义的对象类型则应该实现该方法
unlink(x);
return true;
}
}
}
return false;
}
public E removeFirst()
删除头部元素
public E removeLast()
删除尾部元素
5. 修改元素
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index); //获取第index个元素
E oldVal = x.item;
x.item = element;
return oldVal;
}
修改第index元素的值,可以看到首先要通过node(index)
方法找到要修改的元素,而该方法是通过遍历链表的方式实现的(++下文说明++),所以性能不高。
6. 获取元素
public E get(int index)
与set
一样,也是通过node(index)
方法获取到目标元素,所以说LinkedList的随机访问效率不高。
public E getFirst()
获取头部元素,如果list为空,抛出NoSuchElementException
public E peek();public E peekFirst()
返回头部元素,如果list为空,则返回null
public E getLast()
获取尾部元素,如果list为空,抛出NoSuchElementException
public E peekLast()
返回尾部元素,如果list为空,则返回null
7. 其它操作
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) { // 如果index小于size的一半,则从头部开始遍历
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的节点,如果index小于size的一半,则从头部开始遍历,否则,从尾部开始遍历链表,并返回第index个Node
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;
}
获取元素o在list中的索引,如果o==null,则返回第一个值为null的元素的索引;否则从头部开始遍历list,通过equals方法判断每个元素是否与o相等,返回首个相等的元素索引位置。如果list中不存在o元素,则返回-1.
8. 迭代器
LinkedList
也实现两种迭代器:DescendingIterator
和ListItr
,前者的实现是基于后者的。
DescendingIterator
是一个返回逆序元素的的迭代器,当调用descendingIterator()
方法时返回该迭代器,它支持next,remove
操作。
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.add("1");
list.add(2);
list.add("3");
list.add("4");
System.out.println("顺序:" + list);
Iterator x = list.descendingIterator();
while (x.hasNext()) {
System.out.println(x.next());
}
}
}
以上代码最后输出的结果为:
顺序:[1, 2, 3, 4]
4
3
2
1
ListItr
实现ListIterator
接口,与DescendingIterator
相比,它不仅支持next,remove
,还实现了add,previous,set
操作,功能上与ArrayList
的ListItr
一致.