数据结构(二)LinkedList

引言

由上一篇我们知道,ArrayList的优势是查询速度快,但是插入、删除相对较慢,那对于需要大量增、删操作的数据,该用什么样的结构呢?

链表

单向链表
如图所示,定义一种链表结构,每一个节点都持有下一个节点的引用,当增加元素时,只需要修改当前位置的指向即可,速度就会比ArrayList复制快。
单向链表新增元素

显然,它有一个弊端,如果我们想获取最后一个元素,必须从第一个元素一个一个查下去,浪费时间。

为了提高查询效率,定义如下结构:
双向链表

每个元素都持有它前、后两个元素的引用,在遇到上述问题,会大大缩短查询时间。

LinkedList源码

链表首、尾节点的引用:

/**
 * 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;

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

添加元素 add -> linkLast

public boolean add(E e) {
    linkLast(e);
    return true;
}

/**
 * Links e as last element.
 */
void linkLast(E e) {
    // 取尾部元素
    final Node<E> l = last;
    // 生成新的Node节点
    final Node<E> newNode = new Node<>(l, e, null);
    // 将尾引用指向新的节点
    last = newNode;
    if (l == null)// 如果列表中没有元素,将头指向新节点
        first = newNode;
    else// 如果有元素,将新节点链接到队尾
        l.next = newNode;
    size++;
    modCount++;
}

删除元素 remove -> unlink

public boolean remove(Object o) {
    if (o == null) {
        // 遍历删除null
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        // 遍历找到equals的对象
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

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

查询数据 get -> node

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

Node<E> node(int index) {
    // assert isElementIndex(index);
    // 判断index是否<总个数/2
    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;
    }
}

其他

LinkedList插入一定比ArrayList快吗?

大家不妨跑跑下面的代码:

public class MyTest {
    private static ArrayList<Integer> testArray = new ArrayList<>();
    private static LinkedList<Integer> testLinked = new LinkedList<>();

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            testArray.add(i);
        }
        System.out.println("AAAAA test array time:" + (System.currentTimeMillis() - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            testLinked.add(i);
        }
        System.out.println("AAAAA test linked time:" + (System.currentTimeMillis() - start));
    }

}

这是我跑了几次的结果

AAAAA test array time:1233
AAAAA test linked time:2396

AAAAA test array time:833
AAAAA test linked time:2312

AAAAA test array time:824
AAAAA test linked time:2242

于是得出以下结论,在涉及大量数据的增加(直接增加,插入末尾)且很少涉及删除的操作时,ArrayList速度反而更快。

为什么会如此呢?可能与以下代码有关:
LinkedList添加元素时,创建新的Node
LinkedList每次add时,便创建一个Node对象,增大了时间开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值