Java 集合深入理解 :LinkedList链表源码研究,及双向队列如何实现
Posted 踩踩踩从踩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合深入理解 :LinkedList链表源码研究,及双向队列如何实现相关的知识,希望对你有一定的参考价值。
文章目录
简介:
LinkedList是java集合框架很常用数据结构;在开发中运用也很多,经常对比也是和arrayList进行对比。
ArrayList 是以数组实现的,遍历时很快,但是插入、删除时都需要移动后面的元素;
LinkedList 则是以链表实现的,插入、删除时只需要改变前后两个节点指针指向即可,省事不少。
关键方法时间复杂度
- get() 获取第几个元素,依次遍历,复杂度O(n)
- add(E) 添加到末尾,复杂度O(1)
- add(index, E) 添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n)
- remove()删除元素,直接指针指向操作,复杂度O(1)
代码示例
public static void main(String[] args) {
System.out.println("--------------LinkedList--------------");
LinkedList<Integer> v=new LinkedList<Integer>();
v.add(1);
v.add(2);
v.add(3);
v.add(7);
v.add(5);
v.add(6);
v.stream().forEach(m->{
System.out.println(m);
});
}
--------------LinkedList--------------
1
2
3
7
5
6
链表实现的集合,能保证数据插入顺序和取出顺序,不保证多线程情况下数据的安全性
全篇注释
/**
*是基于{@code list}和{@code Deque}的双链表实现
*接口。实现所有可选的列表操作,并允许
*元素(包括{@code null})。
*<p>所有操作的性能都与双链接列表相同。索引到列表中的操作将从
*开始或结束,以接近指定索引的为准。
*<p><strong>请注意,此linkedlist实现不同步。</strong>
*如果多个线程同时访问一个链表其中一个线程从结构上修改列表,它必须
*外部同步(结构修改是指任何操作添加或删除一个或多个元素;只是设定元素不是结构修改。)这通常是通过在某个自然封装列表。如果不存在这样的对象,则应该使用
*{@link Collections#synchronizedList集合.synchronizedList}方法。这最好在创建时完成,以防止意外对集合列表的非同步访问:<pre>
*List=Collections.synchronizedList(新建LinkedList(…))</预处理>
*<p>这个类的{@code iterator}返回的迭代器和{@codelistiterator}方法是<i>快速失败的</i>:如果列表是在迭代器创建之后的任何时候,在除了通过迭代器自己的{@code remove}或{@code add}方法,迭代器将抛出一个{@链接ConcurrentModificationException}。因此,面对
*修改后,迭代器会快速而干净地失败,而不是冒着任意的,不确定的行为未来的时间。
*<p>请注意,不能保证迭代器的快速失败行为一般说来,不可能在未来作出任何硬性保证存在未同步的并发修改。失败快速迭代器
*尽最大努力抛出{@code ConcurrentModificationException}。因此,编写依赖于此的程序是错误的
*其正确性例外:<i>迭代器的快速失败行为
*应仅用于检测错误。</i>
*<p>这个class是
*<a href=“{@docRoot}/./technotes/guides/collections/index.html”>
*Java集合框架</a>。
@author Josh Bloch
@see List
@see ArrayList
@since 1.2
@param <E> the type of elements held in this collection
*/
注释关键点
- 允许空数据
- 所有操作的性能都与双链接列表相同。
- 通过双向链表实现的,并且不是线程安全的
- 需要线程安全的类,请使用Collections#synchronizedList使之变成安全的
- 该类不能进行遍历同时remove和add,否则会抛出ConcurrentModificationException异常,直接使用迭代器提供的remove方法则可以使用删除
接口实现
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
实现list 和 deque接口 AbstractSequentialList 实现抽象的类
- 实现双链表操作
- 可序列化
- 实现list的操作
成员变量分析双向链表
transient int size = 0;
/**
* Pointer to first node. 开始节点
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node. 结束节点
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
成员属性分析
相比于arraylist的成员属性会少很多
- 相同点
采用transient 关键字,来提高序列化的性能;都是实现了list接口的操作 - 不同点
通过first节点 和last节点来达找到节点,添加删除操作 没有动态扩容操作 - size 包含的元素大小 并不是整个length的长度
transient 非私有以简化嵌套类访问
被transient修饰的变量不参与序列化和反序列化;
LinkedList是实现了Serializable说明他能够被序列化和反序列化传输的;
什么是writeObject 和readObject?可定制的序列化过程
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out size
s.writeInt(size);
// Write out all elements in the proper order.
for (Node<E> x = first; x != null; x = x.next)
s.writeObject(x.item);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
linkLast((E)s.readObject());
}
构造方法
addAll 方法
- 通过checkPositionIndex校验index 是否达到数据满值
- 传入 Collection 中toarray方法转换为object数组
- 获取插入节点的对象,以及前一个节点
- 将所有object数组遍历,生成节点,连接起来
- 将连接组装到链表中
- size 添加numNew modCount 加1
/**
*构造一个包含指定元素的列表
*集合,按集合的迭代器。
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
/**
*将指定集合中的所有元素插入此列表,从指定位置开始。移动元素目前在该位置(如有)以及右边(增加他们的指数)。新元素将出现
*在列表中按指定集合的迭代器。
* @param index index at which to insert the first element
* from the specified collection
* @param c collection containing elements to be added to this list
* @return {@code true} if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
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;
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);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
size += numNew;
modCount++;
return true;
}
toArray方法
-
判断传进来的数组是否小于内部数据的大小
-
小于则通过反射 去创建一个新数组
-
根据首节点通过遍历添加元素到新的数组中,未满的情况则设置为null
/**
*返回一个数组,数组中包含此列表中的所有元素
*正确的顺序(从第一个元素到最后一个元素);的运行时类型
*返回的数组是指定数组的数组。如果名单合适的话
*在指定的数组中,它在其中返回。否则,一个新的
*使用指定数组的运行时类型和此列表的大小。
*<p>如果列表适合指定的数组,并且有空闲空间(即。,
*数组中的元素比列表中的元素多
*紧跟在列表末尾的是{@code null}。
*(这在确定列表长度时非常有用,如果
*调用者知道列表不包含任何空元素。)
*<p>与{@link#toArray()}方法类似,此方法充当
*基于数组和基于集合的API。此外,该方法允许
*对输出数组的运行时类型的精确控制,
*在某些情况下,可以用来节省配置成本。
*<p>假设{@codex}是一个只包含字符串的列表。
*以下代码可用于将列表转储到新的
*已分配的{@code String}数组:
* <pre>
* String[] y = x.toArray(new String[0]);</pre>
*
* Note that {@code toArray(new Object[0])} is identical in function to
* {@code toArray()}.
*@param a要将列表元素放入的数组
*如果足够大的话,就要储存起来;否则,一个新的
*为此目的分配了相同的运行时类型。
*@返回包含列表元素的数组
*如果指定数组的运行时类型
*不是中每个元素的运行时类型的超类型
*此列表
*如果指定的数组为null,@throws NullPointerException
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
node节点
node节点 包含内容对象 下个节点对象 上个节点对象
所有操作基于node节点 或者 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;
}
}
关键方法头部添加删除,尾部添加删除,获取指定节点,指定节点的添加删除
linkFirst方法
添加首节点
- 得到链表中first节点
- 创建新节点 ,第一个节点等于新的节点,并将原first节点的前节点设置为添加节点
- size+1 modCount加1
/**
* 添加第一个节点数据
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
linkLast方法
添加末尾添加节点
- 得到链表中last节点
- 创建新节点 ,末尾节点等于新的节点,并将原last节点的后节点设置为添加节点
- size+1 modCount加1
/**
* 添加末尾添加节点
*/
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++;
}
linkBefore方法
在非空节点之前插入元素e
- 拿到非空节点的前置节点 pred
- 创建新节点 ,前置节点 pred等于新的节点,并将前置节点 pred的后节点设置为添加节点
- size+1 modCount加1
/**
* 在非空节点之前插入元素e。
*/
void linkBefore(E e, Node<E> succ) {
// assert 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++;
}
unlinkFirst方法包括该节点
清除一个非空节点之前的节点并设置为头节点
- 拿到非空节点的数据,拿到节点的后置节点
- 设置非空节点的数据为空,节点的后置节点为空
- 设置首节点为非空节点的后置节点
- 设置首节点的前置为空
- size+1 modCount加1
- 返回节点数据数据
/**
* 清除一个非空节点之前的节点并设置为头节点
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
unlinkLast方法
清除非空节点的后面节点 并设置为空 返回数据
- 拿到非空节点的数据,拿到节点的前置节点
- 设置非空节点的数据为空,节点的前置节点为空
- 设置末节点为非空节点的后置节点
- 设置末节点的后置节点为空
- size+1 modCount加1
- 返回节点数据数据
/**
* 清除非空节点的后面节点 并设置为空
*/
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null; // help GC
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
unlink方法
删除某个节点 并返回数据
- 获得删除节点数据
- 获取删除节点得前置节点 和后置节点
- 设置前置节点得后置等于 后置节点 并设置删除节点得前置为null
- 设置后置节点得前置置等于 前置节点 并设置删除节点得后置为null
- 设置节点数据为空
- size+1 modCount加1
- 返回节点数据数据
/**
* 删除某个节点
*/
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;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
总结
这些内部方法实现了对 链表节点的 基本修改操作,每次操作都只要修改前后节点的指针节点,时间复杂度为 O(1) ,
很多需要中间进行操作的公开方法都是基于这些方法实现。
add方法
添加数据到此列表的末尾
- 调用linkLast方法 插入链表末尾,并返回数据
在此列表中的指定位置插入指定的元素
- checkPositionIndex 校验 index是否操作数据长度
- index 等于size ,调用linkLast 方法直接添加末尾
- index 不等于size,调用linkBefore 在非空节点之前插入元素e
/**
*将指定的元素追加到此列表的末尾。
*<p>此方法等价于{@link#addLast}。
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
/**
*在此列表中的指定位置插入指定的元素。
*移动当前位于该位置的元素(如果有)和
*右边的后续元素(在其索引中添加一个)
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
实现自双端队列(Deque)的添加方法 push、pop、offer 方法等等
- push 方法 将元素推送到由该列表表示的堆栈上。其他单词,在列表的前面插入元素
直接调用addFirst 方法
/**
*将元素推送到由该列表表示的堆栈上。其他单词,在列表的前面插入元素。
* <p>This method is equivalent to {@link #addFirst}.
*
* @param e the element to push
* @since 1.6
*/
public void push(E e) {
addFirst(e);
}
- pop 从该列表表示的堆栈中弹出一个元素
调用removeFirst 方法
/**
*从该列表表示的堆栈中弹出一个元素。其他
*单词,删除并返回此列表的第一个元素。
*
* <p>This method is equivalent to {@link #removeFirst()}.
*
* @return the element at the front of this list (which is the top
* of the stack represented by this list)
* @throws NoSuchElementException if this list is empty
* @since 1.6
*/
public E pop() {
return removeFirst();
}
- Deque 操作
在此列表前面插入指定的元素
// Deque operations
/**
* 在此列表前面插入指定的元素。
*
* @param e the element to insert
* @return {@code true} (as specified by {@link Deque#offerFirst})
* @since 1.6
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
总结
这个双向队列的操作 理论上来讲,不会有动态扩容 堆内存有多大 数据就有多大
删除方法
这里注意
- 从此列表中删除指定元素的第一个匹配项
- 从首节点开始遍历,并没有使用二分查找法,我想应该清除数据应该不太考虑效率把
/**
*从此列表中删除指定元素的第一个匹配项,
*如果有。如果此列表不包含元素,则为
*不变。更正式地说,删除索引最低的元素
*{@code i}这样
*<tt>(o==null  ;get(i)==null: ;o、 等于(get(i)))</tt>
*(如果存在这样的元素)。如果此列表
*包含指定的元素(如果此列表
*因呼叫而更改)。
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
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;
}
/**
*检索并删除此列表的头(第一个元素)。
*
* @return the head of this list
* @throws NoSuchElementException if this list is empty
* @since 1.5
*/
public E remove() {
return removeFirst();
}
get(int index)
返回此列表中指定位置的元素
- 校验index是否大于size
- 调用node方法 返回指定元素索引处的(非空)节点
- 这里就使用了二分查找法 index < (size >> 1) 判断 当前index在数据大小左部分还是右部分
- 根据元素进行遍历
/**
* 返回此列表中指定位置的元素。
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* 返回指定元素索引处的(非空)节点。
*/
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;
}
}
DescendingIterator 倒序迭代器
这个方法返回一个迭代器在此双端队列逆向顺序的元素
- next 这个方法也是 从指定指针节点往后遍历
- previous 方法利用链表得属性从last节点往前遍历 lastReturned = next = (next == null) ? last : next.prev;
/**
* @since 1.6
*/
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
/**
* 适配器通过ListItr.previous提供降序迭代器
*/
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
list实现结构图
以上是关于Java 集合深入理解 :LinkedList链表源码研究,及双向队列如何实现的主要内容,如果未能解决你的问题,请参考以下文章