java8 LinkedList源码阅读

来源:互联网 发布:棋牌辅助作弊软件 编辑:程序博客网 时间:2024/06/07 20:03

基于java8

LinkedList,同时List和Queue的子类。

底层存储

LinkedList,底层是使用双链表的数据结构,存储元素的。

transient int size = 0;  // 元素数量transient Node<E> first; // 头元素, transient Node<E> last;  // 尾元素private static class Node<E> {    E item;  //数据    Node<E> next;  //下一个节点,尾元素的next指向为null    Node<E> prev;  //上一个节点,头元素的prev的指向为null    Node(Node<E> prev, E element, Node<E> next) {        this.item = element;        this.next = next;        this.prev = prev;    }}

add操作

add主要有两类:追加和插入

添加的方法有:add(),addFirst(),addLast(),addAll()

核心方法:linkFirst(),linkLast(),linkBefore()

头部添加,调用linkFirst(),时间复杂度为O(1)

追加或者插入位置在末尾,调用linkLast(),时间复杂度为O(1)

插入,但位置不在末尾,调用linkBefore(),时间复杂度为O(n)

public void add(int index, E element) {    checkPositionIndex(index);    if (index == size)        // 如果插入位置在末尾,则调用        linkLast(element);    else        // 找到index对应的元素,调用linkBfore        linkBefore(element, node(index));}

linkFirst,linkLast,linkBefore的操作其实是差不多的,只不过细节有所不同

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)  // 代表succ节点为头节点,也就说将新元素插入到头部        first = newNode;    else        pred.next = newNode;    size++;    modCount++;}

步骤1:执行linkBefore时,ArrayList的数据结构

pic1

步骤2:执行final Node<E> newNode = new Node<>(pred, e, succ);

pic2

步骤3:执行pred.next = newNode;

pic3

remove操作

删除操作,主要有两类:删除指定位置和删除指定元素

删除方法有:remove(),removeFirst(),removeLast()

核心方法:unlink(),unlinkFirst(),unlinkLast()

不管是删除指定位置或者指定元素的对象,LinkedList都需要遍历寻找,因此remove的时间复杂度为O(n)。

但是由于LinkedList内部持有头和尾两个元素的引用,所以删除头尾,是不需要查找寻找,时间复杂度为O(1).

E unlink(Node<E> x) {    // assert x != null;    // 临时保存移除对象的所有数据(prev和next指针以及存储数据)    final E element = x.item;    final Node<E> next = x.next;    final Node<E> prev = x.prev;    if (prev == null) { //前指针为null,则说明该对象为头节点        first = next;    } else {        prev.next = next;  //         x.prev = null;    }    if (next == null) {  // 后指针为null,则说明该对象为尾节点        last = prev;    } else {        next.prev = prev;        x.next = null;    }    x.item = null;    size--;    modCount++;    return element;}

步骤1:执行remove操作前的数据结构
这里写图片描述
步骤2:执行

if (prev == null) {    first = next;} else {    prev.next = next;  //     x.prev = null;}

这里写图片描述
步骤3:执行

if (next == null) {    last = prev;} else {    next.prev = prev;    x.next = null;}

这里写图片描述
步骤4:执行

x.item = null;size--;

这里写图片描述

unlink操作中将,prev,next,item都置为null,是为了帮助jvm进行GC,避免造成内存泄露

get和set操作(时间复杂度为O(n/2))

get和set操作,查找对应的元素都是通过调用node的方法,而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;    }}

contains操作

时间复杂度O(n),最差时间复杂度O(n),最优时间复杂度O(1)

和ArrayList的contains操作差不多,contains操作调用indexOf方法,

通过遍历的方式,对null元素,使用==判断,对非null元素,使用equals方法判断,查找对应的元素

indexOf和lastIndexOf方法唯一不同点,就是一个从first节点开始,另一个从last节点开始

public int indexOf(Object o) {    int index = 0;    if (o == null) {        // lastIndexOf方法中x = last        for (Node<E> x = first; x != null; x = x.next) {            if (x.item == null)                return index;            index++;        }    } else {        for (Node<E> x = first; x != null; x = x.next) {            if (o.equals(x.item))                return index;            index++;        }    }    return -1;}

使用场景

优点:

LinkedList没有大小限制

没有浪费存储空间(Node节点的创建需要额外消耗少量空间)

add,remove等操作的空间消耗是固定的,不会造成对元素进行额外的移动拷贝

缺点:

除了对首尾元素外,对其他节点,进行add,remove,set,get等操作,都需要进行遍历查找的,时间复杂度为O(n)

查询操作少,存储大量数据,可以考虑使用LinkedList

多线程下:

LinkedList和ArrayList一样,都不是线程安全的。

在考虑线程安全的情况下,可以使用 ConcurrentLinkedQueue代替LinkedList,直接同步LinkedList对象,或者使用

List list = Collections.synchronizedList(new LinkedList(...));
0 0
原创粉丝点击