LinkedList
LinkedList与ArrayList一样都是线程不安全的。
ArrayList是数组形式的,而LinkedList是链表形式的,并且是不带头双向链表。
成员
// 链表存储元素的个数
transient int size = 0;
// 链表的头节点
transient Node<E> first;
// 链表的尾节点
transient Node<E> last;
Node
这个类是LinkedList类的内部类
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;
}
}
添加一个元素
add方法
添加一个新的节点,从尾部开始添加。判断尾节点是否为null,如果为null说明此时链表为空,所以这个新的节点既是头结点也是尾节点。
如果尾节点不为null,那么就将新节点加在链表后面,并且新节点成为尾节点。
public boolean add(E e) {
linkLast(e);
return true;
}
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 boolean offer(E e) {
return add(e);
}
public void push(E e) {
addFirst(e);
}
删除一个元素
remove(Object o)
public boolean remove(Object o) {
// 如果要删除的元素是null,那么就遍历整个链表,判断哪个元素是null,然后将它删掉。
// 如果不是null,那么也是遍历整个链表,将要删除的元素与链表中的每一个
// 元素比较,相同的话,删除。
// 如果遍历完链表发现并没有这个元素那么返回false。
if (o == null) {
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)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {
// 具体的删除操作,取出当前节点的前驱节点和后继节点,和他的元素
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 如果当前节点的前驱节点是null,说明,当前节点是头结点,那么要删
// 除当前节点,就要将当前节点的后继节点变为头结点
// 如果前驱节点不为null,那么前驱节点的后继节点就是当前节点的后继节点
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
// 前驱节点处理完再看后继节点,后继节点若是null,说明当前节点是尾节点
// 那么删除当前节点,将前驱节点变为尾节点
// 后继节点不为null,那么后继节点的前驱节点就不再是当前节点,而是
// 当前节点的前驱节点。
// 也就是说,删除一个节点就是略过这个节点,将他的前后连个节点连接
// 起来,并将自己本身的数据赋值为null;
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
get(int index)
得到某个下标的元素,链表的查找不如数组来的简单省时。
需要遍历整个链表,耗时。
本次的查找,将链表一分为二,判断当前下标是在链表的前半段还是在链表的后半段,然后再去遍历链表的一半。
public E get(int index) {
// 检查当前下标是否合法
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int 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;
}
}
LinkedList的优点就是插入删除一个元素比较方便,时间复杂度比ArrayList低。因为链表只用将前后指针改变,然而数组,可能要移动很多数据。
但相对来说,数组查找起来非常方便。链表查找就不如数组。数组给一个下标立马可以取得数据,链表的话,知道下标还得遍历到下标处。
两种容器有好有坏,重要的是不同的场合使用不同的工具。物尽其用最好。