链表的优点除了插入删除不需要移动其他元素之外,还在于它是一个局部化结构。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
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;
/**
* Constructs an empty list.
*/
public LinkedList() {
}
}
简单看一段源码就能够明白,LinkedList结构上最终的就是利用节点来进行操作,你拿到链表的一个
节点之后,不需要操作太多其它数据,就可以完成插入,删除的操作。而其它的数据结构不行。这是LinkedList所具有的优势。
但链表并不会节省空间,因为链表有节点。单向链表有指向下一个元素的节点;单项循环链表的最后一个元素有指向第一个元素的节点;双向循环链表有指向前一个元素的节点和指向后一个元素的节点。所以链表不会节省空间。再说时间问题:如果只是插入和删除操作,那么不会移动元素,所以会节省时间,数组的插入和删除是要移动元素的(插入和删除最后一个元素不移动);链表的查找操作是从第一个元素开始,所以相对数组要耗时间(数组直接就可以查找到)。
所以要看怎样的情况再具体应用,这样才能体会到LinkedList的好处。
LinkedList 通常在经常需要在 List 中间增删元素的场景下使用。
LinkedList提供了两个构造方法。
public LinkedList() {
header.next = header.previous = header;
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
第一个构造方法不接受参数,将header实例的previous和next全部指向header实例(注意,这个是一个双向循环链表,如果不是循环链表,空链表的情况应该是header节点的前一节点和后一节点均为null),这样整个链表其实就只有header一个节点,用于表示一个空的链表。 执行完构造函数后,header实例自身形成一个闭环。
第二个构造方法接收一个Collection参数c,调用第一个构造方法构造一个空的链表,之后通过addAll将c中的元素全部添加到链表中。
在操作系统中,链表用来分配内存,链接了一串空闲内存。分配容易,释放容易。数据库中,比如说字典,就是用链表来解决冲突的,可以参考redis数据库。
最后需要注意的是:LinkedList是线程不安全的,如果需要线程安全那么请使用synchronized加锁,或者使用vector,或者使用java.util.concurrent包。
如果需要线程遍历List的时候,避免出现ConcurrentModificationException异常,那么有3种解决方式。
1.遍历List的使用synchronized加锁;
2.使用java.util.concurrent包下面的CopyOnWriteArrayList,每次使用List时实际上都是使用的List副本。
3.使用Jdk8中foreach方法,不过该方法只接受lambda表达式
如果需要线程遍历List的时候,避免出现ConcurrentModificationException异常,那么有3种解决方式。
1.遍历List的使用synchronized加锁;
2.使用java.util.concurrent包下面的CopyOnWriteArrayList,每次使用List时实际上都是使用的List副本。
3.使用Jdk8中foreach方法,不过该方法只接受lambda表达式