List集合(二)之LinkedList的实现原理

概述

这篇文章我们来讲讲LinkedList,现在我对LinkedList一无所知,那么我怎么去学习它呢,现在我们在IDE中打开LinkedList来看一下。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList的结构关系图

从该类的上下继承关系我们可以推测出LinkedList可以进行序列化,实现了克隆接口,有队列的属性,可以使用迭代器进行遍历,和ArrayList一样也是List集合的一种。

通过类的定义,我们对LinkedList有一个大致的印象,但更具体的确说不上来,这样我找来了官方对LinkedList的定义

  Doubly-linked list implementation of the {@code List} and {@code Deque} interfaces. Implements all optional list operations, and permits all elements (including {@code null}).
  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.
  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(…));
  The iterators returned by this class’s iterator} and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the Iterator’s own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
  Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw {@code ConcurrentModificationException} on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
  his class is a member of the Java Collections Framework.

翻译过来就是:

  list和deque接口的双链表实现,实现所有可选的列表操作,并允许操作所有元素,包括null。遍历列表的时候,取离指定索引最近的那个。
  注意,LinkedList是非线程安全的,如果多个线程同时方式一个链表,并且至少有一个线程修改了链表的结构(添加或删除,仅仅设置一个元素的值不算修改结构),必须在外部进行同步控制。如果要进行同步控制,最好在创建的时候进行,以防止意外的非同步访问列表,可以调用List list =Collections.synchronizedList(new LinkedList(…));得到一个线程安全的集合。
  这个类的{@code迭代器}和{@code listIterator}方法返回的迭代器是快速失败的:如果在迭代器创建之后的任何时间对列表进行了结构修改,除了通过迭代器自己的remove或add以外,在其它的任何地方进行修改,迭代器将抛出ConcurrentModificationException。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在未来不确定的时间冒任意的、不确定的行为的风险。
  请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬保证。快速失败迭代器尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常来保证正确性的程序是错误的:迭代器的快速故障行为应该只用于检测错误。
  Java中Collections框架的一个成员。

通过阅读官方文档对LinkedList的描述,我们可以总结出以下几点:

  1. LinkedList的数据结构是一个双链表,可以添加所有类型的元素,包括null。
  2. 遍历LinkedList的时候,取离指定索引最近的那个。
  3. LinkedList是非线程安全的,使用迭代器进行遍历时,如果其它线程修改了LinkedList的结构会抛出ConcurrentModificationException异常。
  4. Collections类可以对LinkedList进行包装,例如包装成线程安全的集合。

数据结构

LinkedList是用双向链表实现的,心里面有这个结构再看源码理解起来就会很容易,因此我画了一份双向链表的结构图
双向链表结构图
根据双向链表来看LinkedList的源码,首先得有节点,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;
        }
    }
    // 头节点
    transient Node<E> first;
    // 尾节点
	transient Node<E> last;

构造方法

	public LinkedList() {
	}

添加元素

	public boolean add(E e) {
        linkLast(e);
        return true;
    }
	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++;
    }

添加新节点时是在尾节点后面插入,那么新的节点就变成了尾节点,如果链表中只有一个节点,那么头节点和尾节点就是同一个前指针和后指针都为null的节点。

修改元素

	public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

修改元素改变的不是链表的结构,只修改节点的元素值,返回该节点的旧值。
不过这里需要注意的是,查找需要修改的节点的时候调用了node方法,这里做了什么处理呢。

	Node<E> node(int 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;
        }
    }

如果元素是高位索引,则从尾节点开始遍历,如果元素是低位索引,则从头节点开始遍历查找到指定节点。

删除

	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;
    }

从列表中优先删除索引更低的指定值的节点,如果该列表不包含该元素,则保持不变。

遍历

  • for循环
  • 迭代器

扩展

LinkedList不仅实现了List接口,也实现了Deque(double ended queue)接口,这样LinkedList还可以作为队列、双向队列、栈来使用。

	// 实例化队列
    Deque<String> queue = new LinkedList<>();
    // 进入队列
    queue.offer("q_note1");
    queue.offerFirst("q_node_first");
    queue.offerLast("q_node_last");
    System.out.println(queue.toString());
    System.out.println(queue.peek());
    // 出队列
    System.out.println(queue.poll());
    System.out.println(queue.toString());

运行结果:
[q_node_first, q_note1, q_node_last]
q_node_first
q_node_first
[q_note1, q_node_last]


	// 实例化栈
    Deque<String> stack = new LinkedList<>();
    // 压栈
    stack.push("s_node1");
    stack.push("s_node2");
    stack.push("s_node3");
    // 取栈顶
    System.out.println(stack.peek());
    // 弹出栈
    System.out.println(stack.pop());
    System.out.println(stack.toString());

运行结果:
s_node3
s_node3
[s_node2, s_node1]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值