目录
9. addAll(int index, Collection c)
基本介绍
LinkedList是实现了List和Deque接口的双向链表,实现了所有可选列表的操作,允许存放所有元素(包括null)
LinkedList是非线程安全的,因此在多线程的情况下,需要加同步锁来保证线程安全,或者使用Collections.synchronizedList
常用方法
public LinkedList()
public LinkedList(Collection<? extends E> c)
public E getFirst() 获取第一个元素
public E getLast() 获取最后一个元素
public boolean add(E e) 添加一个元素
public void add(int index, E element) 在指定位置插入元素
public void addFirst(E e) 在集合前部增加一个元素
public void addLast(E e) 在集合尾部增加一个元素
public boolean addAll(Collection<? extends E> c) 把另一个集合全部添加到当前集合
public boolean addAll(int index, Collection<? extends E> c) 在指定位置把另一个集合全部添加到当前集合
public boolean contains(Object o) 判断是否包含
public int size() 返回集合大小
public boolean remove(Object o) 根据指定对象删除元素
public E remove(int index) 根据指定索引删除元素
public E removeFirst() 删除第一个元素
public E removeLast() 删除最后一个元素
public E get(int index) 根据索引获取元素
public E set(int index, E element) 在指定索引处添加一个元素(覆盖原数据)
public int indexOf(Object o) 返回指定元素索引
public int lastIndexOf(Object o) 返回指定元素出现的最后一个索引
源码解析
1. LinkedList的底层结构,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;
}
}
2. LinkedList的几个内部变量
transient int size = 0;
// 标记链表第一个元素
transient Node<E> first;
// 标记链表最后一个元素
transient Node<E> last;
// 链表结构修改的次数
protected transient int modCount = 0;
3. getFirst()
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
这个方法比较简单,其实就是取链表的first节点,getLast()方法类似,不再赘述
4. removeFirst()
public E removeFirst() {
// 拿到first节点
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
// 把需要删除的节点的值置为空,并且把其引用也置为空
f.item = null;
f.next = null; // help GC
// 把next节点置为first节点
first = next;
// 如果next节点为空,说明整个链表为空
if (next == null)
// 则把last节点也置为空
last = null;
else
// 把next节点的前置引用置为空,因为prev节点已经删除,next节点已经为头节点
// 此时原来的first节点的引用已经全部置空,下一次GC时会把这个节点回收
next.prev = null;
// 容器的size - 1
size--;
modCount++;
return element;
}
removceLast()方法类似,只是反过来将最后一个元素置空。
5. addFirst(E e)
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) {
final Node<E> f = first;
// 实例化一个Node节点,设置前置节点为null,原来的first节点置为当前节点的next节点
final Node<E> newNode = new Node<>(null, e, f);
// 把链表的first节点置为当前节点
first = newNode;
// 如果当前链表为空
if (f == null)
// 则将last节点也置为新实例化的节点
last = newNode;
else
// 否则,将原有的first节点的前置节点置为当前节点
f.prev = newNode;
size++;
modCount++;
}
addLast() 也类似,不再详细说明了
6. contains(Object o)
public boolean contains(Object o) {
return indexOf(o) != -1;
}
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;
}
7. add(E e)
public boolean add(E e) {
// 将元素添加到链表的最后一位
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
// 实例化一个Node节点,将原来的last节点置为当前节点的前置节点,后置节点置为null
final Node<E> newNode = new Node<>(l, e, null);
// 将last节点置为当前节点
last = newNode;
// 如果原先的last节点为空,说明之前链表为空
if (l == null)
// 将first节点置为当前节点
first = newNode;
else
// 否则将之前last节点的next节点置为当前节点
l.next = newNode;
size++;
modCount++;
}
8. remove(Object o)
public boolean remove(Object o) {
// 跟其他方法很类似,先判断元素是否为null
if (o == null) {
// 循环找到对应元素,并执行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)) {
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;
// 如果前置节点为空,说明当前待删除节点是头节点
if (prev == null) {
// 把first节点置为当前节点的next节点
first = next;
} else {
// 否则就把当前节点的前置节点的next节点置为当前节点的next节点
prev.next = next;
// 把待删除节点的前置节点置为null
x.prev = null;
}
// 如果待删除节点的next节点为null,说明当前节点是最后一个节点
if (next == null) {
// 将当前链表的last节点置为待删除节点的前置节点
last = prev;
} else {
// 把当前节点的next节点的前置节点置为当前节点的前置节点
next.prev = prev;
// 当前节点的next节点置为null
// 此时待删除节点的引用关系已经全部置空
x.next = null;
}
// 把当前节点的数据置为null
x.item = null;
size--;
modCount++;
return element;
}
9. addAll(int index, Collection<? extends E> c)
/**
* 在索引位置插入新的集合
*
* @Param index 索引
* @Param c 需要添加的集合
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 校验index是否超出范围
checkPositionIndex(index);
// 将集合转成数组
Object[] a = c.toArray();
int numNew = a.length;
// 如果需要添加的集合为空,直接return false
if (numNew == 0)
return false;
Node<E> pred, succ;
// 如果索引位置等于当前集合的size,说明要将新的集合添加在链表的尾部
if (index == size) {
succ = null;
pred = last;
} else {
// 把当前索引的元素赋值给succ
succ = node(index);
// 当前索引的前置节点赋值给pred
pred = succ.prev;
}
// 循环数组
for (Object o : a) {
Node<E> newNode = new Node<>(pred, e, null);
// 如果pred为null,则将新node对象赋值给first
if (pred == null)
first = newNode;
else
pred.next = newNode;
// 把pred置为当前元素,继续添加下一个元素
pred = newNode;
}
// 如果是在队尾添加元素
if (succ == null) {
// 则将链表的last节点置为pred,因为上面在循环末尾会将pred = newNode,此时pred就是最后一个元素
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
// 如果超出范围就抛出IndexOutOfBoundsException
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
10. get(int index)
public E get(int index) {
// 校验索引是否越界,如果越界则抛异常
checkElementIndex(index);
// 返回当前节点的元素
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
Node<E> node(int index) {
// 判断索引是靠近前半部分还是后半部分
if (index < (size >> 1)) {
// 如果索引靠前,则从头开始遍历,直到当前索引
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
// 然后返回当前索引对应的node
return x;
} else {
Node<E> x = last;
// 如果索引靠后,则从末尾开始向前遍历
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
11. spliterator()
LinkedList中还有spliterator拆分器,该拆分器是后期绑定的,并且使用与链接列表相同的元素快速失效。后期绑定拆分器绑定到元素源意味着在第一次遍历、第一次拆分或第一次查询估计大小时链接列表,而不是在创建拆分器时。它可以和 Java 8 中的 Streams 一起使用。此外,它还可以单独和批量遍历元素。Spliterator 是遍历元素的更好方法,因为它对元素提供了更多的控制。
这个拆分器感觉在我平时的工作中用的也不多,就不做解析了,如果大家想看看这个方法的解析的话给我留言,我再单独写一篇这个的解析。
总结
1. LinkedList底层是由双向链表实现的
2. LinkedList插入和删除比较快,因为正常插入都是插入链表的最后一个元素,只需要改变前置节点的next指向和当前节点的前置指向即可,不需要想ArrayList一样移动数组
3. LinkedList如果按照索引查询会很慢,因为他需要从头或从尾开始逐个遍历直到对应索引位置,不支持随机访问
4. LinkedList是非线程安全的
5. LinkedList元素允许为null,允许重复元素
6. LinkedList是基于双向链表实现的,所以不存在容量不足的情况,不需要扩容