这篇文章来自我的博客
正文之前
之前介绍过了ArrayList的源码了,在刚学Java的时候,书籍中就经常拿ArrayList和LinkedList来举例子,学完了ArrayList最常用部分的源码后,就打算把LinkedList也学完,源码中有两种操作,一种是列表操作,一种是队列操作,分两篇文章来讲,今天先讲列表操作
今天的内容有这些:
- LinkedList 概念介绍
- 结点
- 类的基本信息
- 构造
- 增删改查
正文
1. 概念介绍
All of the operations perform as could be expected for a doubly-linked* list. Operations that index into the list will traverse the list from* the beginning or the end, whichever is closer to the specified index. * Doubly-linked list implementation of the {@code List} and {@code Deque}* interfaces. Implements all optional list operations, and permits all* elements (including {@code null}).**
按照注释给出的,LinkedList叫做“双向链表”,实现了List(列表)接口和Deque(双向队列)接口,支持队列的操作
对特定的元素的操作,可以从列表的首或者尾开始,哪边离得近就从哪边开始
Note that this implementation is not synchronized.* If multiple threads access a linked list concurrently, and at least* one of the threads modifies the list structurally, it must be* synchronized externally. (A structural modification is any operation* that adds or deletes one or more elements; merely setting the value of* an element is not a structural modification.) This is typically* accomplished by synchronizing on some object that naturally* encapsulates the list.** If no such object exists, the list should be "wrapped" using the* {@link Collections#synchronizedList Collections.synchronizedList}* method. This is best done at creation time, to prevent accidental* unsynchronized access to the list: *
* List list = Collections.synchronizedList(new LinkedList(...));
和ArrayList一样,这个容器也是非线程安全的,如果要在多线程环境下使用,也要使用synchronizedList包装起来
在源码中还有一些注释是关于迭代器的,等到以后的文章中再说明
2. 节点
- 节点的表示
需要先统一以下说法,Node叫做节点,Node.item叫做元素
//当前节点的类型是泛型private static class Node
- 根据索引寻找节点
在查找的过程要一个一个节点的迭代,所以LinkedList随机读取的开销要比ArrayList大
/*** Returns the (non-null) Node at the specified element index.*/Node
3. 类的基本信息
先来看看这个类继承了什么类,实现了什么接口
public class LinkedList
}
继承了AbstractSequentialList(AbstractList的子类),多实现了一个Deque接口,其他的和ArrayList是一样的
变量:
//大小transient int size = 0;/*** Pointer to first node.* Invariant: (first == null && last == null) ||* (first.prev == null && first.item != null)*/ //第一个节点,满足上面这两个条件之一transient Node
4. 构造
- 空链表
/*** Constructs an empty list.*/public LinkedList() {}
- 带有其他容器元素的链表
public LinkedList(Collection extends E> c) {//先构造一个空链表this();//添加指定容器的元素的方法,下文讲述addAll(c);}
5. 增删改查
增
因为链表是双向的,所以在增加元素的时候,要设置节点两边的指向,接下来说明指向的时候,会说明是向前指向还是向后指向,下文说的删除指向,就是把指向设为null
主要内容
- addFirst()
- linkFirst()
- addLast()
- linkLast()
- add()
- linkLast()
- linkBefore()
- addAll()
从内往外,先将内部方法,再讲公用方法:
增加至表头
/*** Links e as first element.*///将元素添加至链表头private void linkFirst(E e) {//先定义一个节点final Node
增加至表尾
原理是一样的,就不画图了
/*** Links e as last element.*///基本套路是一样的void linkLast(E e) {final Node
增加至指定位置
插入到指定位置,涉及三个元素,两两之间有关联,所有需要有四条指向
/*** Inserts element e before non-null Node succ.*/void linkBefore(E e, Node
刚才上面的三个方法都是为下面这些方法服务的:
//添加至头部public void addFirst(E e) {linkFirst(e);}//添加至尾部public void addLast(E e) {linkLast(e);}//和上面加至队尾是等价的,除了有返回值public boolean add(E e) {linkLast(e);return true;}//在指定位置添加节点public void add(int index, E element) {//检查元素是否越界checkPositionIndex(index);/*** private void checkPositionIndex(int index) {* if (!isPositionIndex(index))* throw new IndexOutOfBoundsException(outOfBoundsMsg(index));* }*///如果索引在最后,直接加至队尾if (index == size)linkLast(element);else//调用node()方法寻找节点linkBefore(element, node(index));}
还有直接添加另一个容器的元素至链表的方法:
- 将指定容器的元素添加至指定位置,插入的位置在index元素之前(若index = 1,表示第2个元素,则插入第1个和第2个元素之间)
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
- 将指定容器的元素添加至链表尾:
public boolean addAll(Collection extends E> c) {return addAll(size, c);}
删
删除节点的方式,就是把节点的元素和两边的指向设为null,让GC来收集
主要内容
- removeFirst()
- unlinkFirst()
- removeLast()
- unlinkLast()
- remove()
- unlink()
- clear()
还是按照相同的顺序来:
删除第一个节点
删除节点内容,删除指向
private E unlinkFirst(Node
删除最后一个节点
步骤是类似的,就画个图,代码就不解释了
/*** Unlinks non-null last node l.*/private E unlinkLast(Node
删除指定节点
/*** Unlinks non-null node x.*/E unlink(Node
这三个删除的方法写来是被以下这些方法调用的:
//移除第一个节点public E removeFirst() {final Node
清空链表
没有用到上面的方法,直接将所有的设为null
public void clear() {// Clearing all of the links between nodes is "unnecessary", but:// - helps a generational GC if the discarded nodes inhabit// more than one generation// - is sure to free memory even if there is a reachable Iterator//不断迭代,把节点的三个属性设为nullfor (Node
改
链表中改变节点的方式就一个:set()方法
public E set(int index, E element) {//检查是否越界checkElementIndex(index);//根据索引查找节点Node
查
- 查找首尾元素:
这个没什么好说的,直接上代码
public E getFirst() {final Node
- 判断是否含有某个元素
public boolean contains(Object o) {//indexOf()方法在下面讲述return indexOf(o) != -1;}
- 根据索引查找
public E get(int index) {checkElementIndex(index);//node()方法根据索引来找到节点,在用 .item 来返回节点内容return node(index).item;}
- 根据元素得出索引
得到特定元素第一次出现的位置:
public int indexOf(Object o) {int index = 0;//如果对象为null,则也可以查找出内容为null的节点的位置if (o == null) {for (Node
得到特定元素最后一次出现的位置,也就是把上面的反过来找:
public int lastIndexOf(Object o) {int index = size;if (o == null) {for (Node
LinkedList基于列表的操作到这里就介绍完了,下一篇会是基于队列的操作