目录
LinkedList
doc
- list和Deque接口的双链表实现。实现所有可选的列表操作,并允许所有元素(包括null)。
- 对于双链接列表,所有操作的执行都符合预期。索引到列表中的操作将从开始或结束遍历列表,以更接近指定索引的为准。
- 请注意,此实现不是同步的(线程不安全)。如果多个线程同时访问一个链接列表,并且至少有一个线程在结构上修改了该列表,则必须在外部同步该列表。(结构修改是添加或删除一个或多个元素的任何操作;仅仅设置元素的值不是结构修改。)这通常是通过在自然封装列表的某个对象上同步来完成的。如果不存在这样的对象,则应该使用集合.synchronizedList方法。最好在创建时执行此操作,以防止意外的未同步访问列表:
List list = Collections.synchronizedList(new LinkedList(...));
- 这个类的iterator和listIterator方法返回的迭代器是快速失败的:如果在迭代器创建之后的任何时候,以任何方式(除了通过迭代器自己的remove或add方法)对列表进行结构修改,迭代器将抛出一个ConcurrentModificationException。因此,在并发修改的情况下,迭代器会迅速而彻底地失败,而不是在将来某个不确定的时间冒着任意的、不确定的行为的风险。
- 这个类是Java集合框架的一个成员。
构造方法和属性
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* 指向第一个节点的指针。
* 不变量: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* 指向最后一个节点的指针。
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
/**
* 构造一个空列表。
*/
public LinkedList() {
}
/**
*构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回。
*
* @param c 要将其元素放入此列表中的集合
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
//将集合元素添加到linkedList末尾
addAll(c);
}
/**
按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾。
如果在操作进行过程中修改了指定的集合,则此操作的行为未定义。
(请注意,如果指定的集合是此列表,并且它不是空的,则会发生这种情况。)
*
* @param c 包含要添加到此列表的元素的集合
* @return {@code true} 如果此列表因调用而更改
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
双向链表
如图(图片来自于网络):
注意: LinkedList的末节点底层源码并未指向首节点而是为null
addAll(重要)
从指定位置开始,将指定集合中的所有元素插入此列表。当不是构造方法调用时
它会在其最后节点追加新的元素
/**
从指定位置开始,将指定集合中的所有元素插入此列表。
将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加其索引)。
新元素将按照指定集合的迭代器返回的顺序出现在列表中。
*
* @param 插入指定集合中第一个元素的索引
* @param c 包含要添加到此列表的元素的集合
* @return {@code true} 如果此列表因调用而更改,则为true
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(int index, Collection<? extends E> c) {
// 检查index是否越界 判断是否是有效下标 此时index必须为0
checkPositionIndex(index);
Object[] a = c.toArray();
//数组length
int numNew = a.length;
if (numNew == 0)
return false;
//创建两个双向链表节点 pred末游节点 succ首节点 初始值为null
Node<E> pred, succ;
if (index == size) {
//初始首节点为null
succ = null;
//存储第一个node
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);
if (pred == null)//第一个节点时 他的上一个节点时null
first = newNode; //此时最后一个节点就是它本身
else
//此时将上一个节点的下游节点链接就是当前生成的
pred.next = newNode;
//记录当前末游节点
pred = newNode;
}
//此时数据链接完
if (succ == null) {
//将最后一个节点赋值给last
last = pred;
//如果存在首届
} else {
//将最后一个的下游节点链接为第一个节点
pred.next = succ;
//将第一个的上游节点链接为末游节点
succ.prev = pred;
}
//size=length+1
size += numNew;
modCount++;
return true;
}
linkFirst linkLast linkBefore
这三个方法分别在linkedList中的前 后 中 插入元素 私有方法不被外部调用
可以通过addFirst,addLast调用他的首末添加方法
/**
*插入首元素
*/
private void linkFirst(E e) {
//获取首元素的node节点 调用者类中已经储存对应的信息
final Node<E> f = first;
//创建新的node节点 此时首节点为null 末节点为原首节点
final Node<E> newNode = new Node<>(null, e, f);
//首节点==新生成节点
first = newNode;
if (f == null)
//当首节点为null说明此时新增的就是首又是末
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
/**
*将指定的元素追加到此列表的末尾
*/
void linkLast(E e) {
//获取末元素的node节点
final Node<E> l = last;
//每次生成最后一个节点时 最后的节点.next并没有指向首节点
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
/**
* 在非空节点成功之前插入元素e。
*/
void linkBefore(E e, Node<E> succ) {
// 断言成功 succ != null;
final Node<E> pred = succ.prev;//获取参数节点的上游阶段
//构建新节点(上游节点,数据,下游节点)
final Node<E> newNode = new Node<>(pred, e, succ);
//修改参数的上游节点
succ.prev = newNode;
if (pred == null)
first = newNode;
else
//原上游的下游节点重新指向新生成的
pred.next = newNode;
size++;
modCount++;
}
add
中间插入如图:
/**
将指定的元素追加到此列表的末尾。
此方法等效于addLast。
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
在此列表的指定位置插入指定的元素。将当前位于该位置的元素(如果有)
和任何后续元素向右移动(在其索引中添加一个元素)。
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
//首
linkLast(element);
else
//中间
linkBefore(element, node(index));
}
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(int index) {
// assert isElementIndex(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 中添加一个对象时,实际上 LinkedList 内部会生成一个
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;
}
}
remove
如图
/**
从列表中删除第一个出现的指定元素(如果存在)。如果此列表不包含元素,则它将保持不变。
更正式地说,删除索引i最低的元素,这样(o==null?get(i)==null:o.equals(get(i))
(如果存在这样的元素)。
如果此列表包含指定的元素,则返回true(如果此列表因调用而更改,则返回等效值)。
*/
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
//如果值为null
if (x.item == null) {
//取消非空节点
unlink(x);
return true;
}
}
} else {
//每次遍历完x = x.next
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
/**
* 取消非空节点x的链接。
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
//此时当前节点的上游的下游节点 指向 当前节点的下游
prev.next = next;
x.prev = null;//当前节点上游指向null
}
if (next == null) {
last = prev;
} else {
//当前节点下游的上游 指向 当前节点的上游节点
next.prev = prev;
x.next = null;//当前下游节点指向null
}
x.item = null;
size--;//当前集合size-1
modCount++;
return element;
}
总结:由上可以看出当add时本质上是更换原Node节点的重新指向从而构成新的双线链表,而由于此特性所以LinkedList更利于修改和删除,且不会有扩容相关的问题。
与ArrayList的异同点
经过上面的分析,结合我们之前对ArrayList的理解,那么可以得出对比结果:
但他们都支持null值,也没有对线程安全做处理
LinkedList实现Queue (队列)
- 队列先进先出,bai栈先进后出。
- 对插入和删du除操作的"限定"不同zhi。
栈是限定只能在表的一端进dao行插入和删除操作的线性表。
队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
public class MyQueue
{
private LinkedList list = new LinkedList();
public void put(Object o)
{
list.addLast(o);
}
public Object get()
{
return list.removeFirst();//队列 先进先出
//return list.removeLast();//栈 先进后出
}
public boolean isEmpty()
{
return list.isEmpty();
}
public static void main(String[] args)
{
MyQueue myQueue = new MyQueue();
myQueue.put("one");
myQueue.put("two");
myQueue.put("three");
System.out.println(myQueue.get());
System.out.println(myQueue.get());
System.out.println(myQueue.get());
System.out.println(myQueue.isEmpty());
}