Java中LinkedList真的是查找慢增删快

测试结果

废话不多说,先上测试结果。作者分别在ArrayList和LinkedList的头部、尾部和中间三个位置插入与查找100000个元素所消耗的时间来进行对比测试,下面是测试结果

(感谢@Hosalo的指正,在这里说明一下测试的环境,尾部插入是在空表的基础上测试的,头部和中间位置插入是在已存在100000个元素的表上进行测试的)

插入 查找
ArrayList尾部 26ms 4ms
ArrayList头部 2887ms 3ms
ArrayList中间 1936ms 4ms
LinkedList尾部 28ms 9ms
LinkedList头部 15ms 11ms
LinkedList中间 12310ms 11387ms

测试结论

  • ArrayList的查找性能绝对是一流的,无论查询的是哪个位置的元素
  • ArrayList除了尾部插入的性能较好外(位置越靠后性能越好),其他位置性能就不如人意了
  • LinkedList在头尾查找、插入性能都是很棒的,但是在中间位置进行操作的话,性能就差很远了,而且跟ArrayList完全不是一个量级的

源码分析

我们把Java中的ArrayList和LinkedList就是分别对顺序表和双向链表的一种实现,所以在进行源码分析之前,我们先来简单回顾一下数据结构中的顺序表与双向链表中的关键概念

  • 顺序表:需要申请连续的内存空间保存元素,可以通过内存中的物理位置直接找到元素的逻辑位置。在顺序表中间插入or删除元素需要把该元素之后的所有元素向前or向后移动。
  • 双向链表:不需要申请连续的内存空间保存元素,需要通过元素的头尾指针找到前继与后继元素(查找元素的时候需要从头or尾开始遍历整个链表,直到找到目标元素)。在双向链表中插入or删除元素不需要移动元素,只需要改变相关元素的头尾指针即可。

所以我们潜意识会认为:ArrayList查找快,增删慢。LinkedList查找慢,增删快。但实际上真的是这样的吗?我们一起来看看吧。

测试程序

测试程序代码基本没有什么营养,这里就不贴出来了,但是得把程序的运行结果贴出来,方便逐个分析。

运行结果

ArrayList尾部插入100000个元素耗时:26ms
LinkedList尾部插入100000个元素耗时:28ms
ArrayList头部插入100000个元素耗时:859ms
LinkedList头部插入100000个元素耗时:15ms
ArrayList中间插入100000个元素耗时:1848ms
LinkedList中间插入100000个元素耗时:15981ms
ArrayList头部读取100000个元素耗时:7ms
LinkedList头部读取100000个元素耗时:11ms
ArrayList尾部读取100000个元素耗时:12ms
LinkedList尾部读取100000个元素耗时:9ms
ArrayList中间读取100000个元素耗时:13ms
LinkedList中间读取100000个元素耗时:11387ms

ArrayList尾部插入

源码

add(E e)方法
  public boolean add(E e) {
    // 检查是否需要扩容
    ensureCapacityInternal(size + 1); // Increments modCount!!
    // 直接在尾部添加元素
    elementData[size++] = e;
    return true;
  }

可以看出,对ArrayList的尾部插入,直接插入即可,无须额外的操作。

LinkedList尾部插入

源码

LinkedList中定义了头尾节点
  /**
   * Pointer to first node.
   */
  transient Node<E> first;

  /**
   * Pointer to last node.
   */
  transient Node<E> last;

add(E e)方法,该方法中调用了linkLast(E e)方法

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

linkLast(E e)方法,可以看出,在尾部插入的时候,并不需要从头开始遍历整个链表,因为已经事先保存了尾结点,所以可以直接在尾结点后面插入元素

  /**
   * Links e as last element.
   */
  void linkLast(E e) {
    // 先把原来的尾结点保存下来
    final Node<E> l = last;
    // 创建一个新的结点,其头结点指向last
    final Node<E> newNode = new Node<>(l, e, null);
    // 尾结点置为newNode
    last = newNode;
    if (l == null)
      first = newNode;
    else
      // 修改原先的尾结点的尾结点,使其指向新的尾结点
      l.next = newNode;
    size++;
    modCount++;
  }

总结

对于尾部插入而言,ArrayList与LinkedList的性能几乎是一致的

ArrayList头部插入

源码

add(int index, E element)方法,可以看到通过调用系统的数组复制方法来实现了元素的移动。所以,插入的位置越靠前,需要移动的元素就会越多

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1); // Increments modCount!!
    // 把原来数组中的index位置开始的元素全部复制到index+1开始的位置(其实就是index后面的元素向后移动一位)
    System.arraycopy(elementData, index, elementData, index + 1,
             size - index);
    // 插入元素
    elementData[index] = element;
    size++;
  }

LinkedList头部插入

源码

add(int index, E element)方法,该方法先判断是否是在尾部插入,如果是调用linkLast()方法,否则调用linkBefore(),那么是否真的就是需要重头开始遍历呢?我们一起来看看

  public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
      linkLast(element);
    else
      linkBefore(element, node(index));
  }

在头尾以外的位置插入元素当然得找出这个位置在哪里,这里面的node()方法就是关键所在,这个函数的作用就是根据索引查找元素,但是它会先判断index的位置,如果index比size的一半(size >> 1,右移运算,相当于除以2)要小,就从头开始遍历。否则,从尾部开始遍历。从而可以知道,对于LinkedList来说,操作的元素的位置越往中间靠拢,效率就越低

  Node<E> node(int index) {
    // assert isElementIndex(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;
    }
  }

这个函数的工作就只是负责把元素插入到相应的位置而已,关键的工作在node()方法中已经完成了

  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)
      first = newNode;
    else
      pred.next = newNode;
    size++;
    modCount++;
  }

总结

  • 对于LinkedList来说,头部插入和尾部插入时间复杂度都是O(1)
  • 但是对于ArrayList来说,头部的每一次插入都需要移动size-1个元素,效率可想而知
  • 但是如果都是在最中间的位置插入的话,ArrayList速度比LinkedList的速度快将近10倍

ArrayList、LinkedList查找

  • 这就没啥好说的了,对于ArrayList,无论什么位置,都是直接通过索引定位到元素,时间复杂度O(1)
  • 而对于LinkedList查找,其核心方法就是上面所说的node()方法,所以头尾查找速度极快,越往中间靠拢效率越低

到此这篇关于Java中LinkedList真的是查找慢增删快 的文章就介绍到这了,更多相关Java LinkedList查找慢增删快 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JAVA LinkedList和ArrayList的使用及性能分析

    第1部分 List概括List的框架图List 是一个接口,它继承于Collection的接口.它代表着有序的队列.AbstractList 是一个抽象类,它继承于AbstractCollection.AbstractList实现List接口中除size().get(int location)之外的函数.AbstractSequentialList 是一个抽象类,它继承于AbstractList.AbstractSequentialList 实现了"链表中,根据index索引值操作链表的全部函数

  • java 中ArrayList与LinkedList性能比较

    java 中ArrayList与LinkedList性能比较 今天看一框架的代码,看到有些 可以使用ArrayList的地方 使用的是 LinkedList,用到的情景是在一个循环里面进行顺序的插入操作. 众所周知java里面List接口有两个实现ArrayList 和 LinkedList,他们的实现原理分别是c语言中介绍的数组和链表. 正如学习数据结构时的认识,对于插入操作,链表的结构更高效,原因是可以通过修改节点的指针 就可以完成插入操作, 而不像数组, 需要把插入位置之后的数组元素依次后

  • Java LinkedList的实现原理图文详解

    一.概述 先来看看源码中的这一段注释,我们先尝试从中提取一些信息: Doubly-linked list implementation of the List and Deque interfaces. Implements all optional list operations, and permits all elements (including null).All of the operations perform as could be expected for a doubly-l

  • 如何实现Java中一个简单的LinkedList

    LinkedList与ArrayList都是List接口的具体实现类.LinkedList与ArrayList在功能上也是大体一致,但是因为两者具体的实现方式不一致,所以在进行一些相同操作的时候,其效率也是有差别的. 对于抽象的数据结构--线性表而言,线性表分为两种,一种是顺序存储结构的顺序表,另一种是通过指针来描述其逻辑位置的链表. 针对于具体的Java实现: 顺序存储的顺序表是用数组来实现的,以数组为基础进行封装各种操作而形成的List为ArrayList 链表是用指针来描述其逻辑位置,在J

  • java LinkedList类详解及实例代码

    java  LinkedList类详解 LinkedList的特有功能 A:添加功能 public void addFirst(Object e); public void addLast(Object e); B:特有功能 public Object getFirst(); public Object getLast(); C:删除功能 public Object removeFirst(); public Object removeLast(); 实例代码: import java.util

  • java LinkedList的实例详解

    java LinkedList的实例详解 站在Java的角度看,玩队列不就是玩对象引用对象嘛! 实例代码: public class LinkedList<E> implements List<E>, Deque<E> { Node<E> first; Node<E> last; int size; public boolean add(E e) { final Node<E> l = last; final Node<E>

  • Java集合框架LinkedList详解及实例

    Java集合框架LinkedList详解 LinkedList定义 package java.util; public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ transient int size = 0; transient Node<E> first;

  • java LinkedList源码详解及实例

    一.LinkedList概述: LinkedList与ArrayList一样,是实现了List接口.由于LinkedList是基于链表实现的,所以它执行插入和删除操作时比ArrayList更高效,而随机访问的性能要比ArrayList低. 二.LinkedList的实现: 1.构造方法 //构造一个空的LinkedList public LinkedList() { } //接收一个Collection参数c,默认构造方法构造一个空的链表,并通过addAll将c中的元素全部添加到链表中. pub

  • Java中集合LinkedList的原理与使用方法

    前言 LinkedList和ArrayList一样是集合List的实现类,虽然较之ArrayList,其使用场景并不多,但同样有用到的时候,那么接下来,我们来认识一下它. 一. 定义一个LinkedList public static void main(String[] args) { List<String> stringList = new LinkedList<>(); List<String> tempList = new ArrayList<>(

  • Java中LinkedList真的是查找慢增删快

    测试结果 废话不多说,先上测试结果.作者分别在ArrayList和LinkedList的头部.尾部和中间三个位置插入与查找100000个元素所消耗的时间来进行对比测试,下面是测试结果 (感谢@Hosalo的指正,在这里说明一下测试的环境,尾部插入是在空表的基础上测试的,头部和中间位置插入是在已存在100000个元素的表上进行测试的) 插入 查找 ArrayList尾部 26ms 4ms ArrayList头部 2887ms 3ms ArrayList中间 1936ms 4ms LinkedLis

  • Java中LinkedList详解和使用示例_动力节点Java学院整理

    第1部分 LinkedList介绍 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 List 接口,能对它进行队列操作. LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用. LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆. LinkedList 实现java.io.Serial

  • java中LinkedList使用迭代器优化移除批量元素原理

    本文主要介绍了java中LinkedList使用迭代器优化移除批量元素原理,分享给大家,具体如下: public interface Iterator<E> { /** *是否还有下一个元素 */ boolean hasNext(); /** *下一个元素 */ E next(); /** * 从集合中删除最后一个返回的元素 */ default void remove() { throw new UnsupportedOperationException("remove"

  • Java中LinkedList原理代码解析

    本文研究的主要是Java中LinkedList原理的相关内容,具体介绍如下. 一句话概括,Java中的LinkedList其实就是使用双向链表,LinkedList的基本操作就是对双向链表的操作. 上面可以清晰的看出,链表中每个元素对应一个节点,节点里面包含三部分,一个是前一个节点的引用,一个是元素内容,一个是后一个节点的引用. 向链表中添加元素的过程就是在链表尾部追加一个节点 void linkLast(E e) { final Node<E> l = last; final Node<

  • Java中使用HashMap改进查找性能的步骤

    Java中,HashMap,其实就是键值对.一个Key,对应一个值:写数据时,指定Key写对应值:读取时凭Key找到相应值.感觉就跟Redis差不多. // 创建 HashMap 对象 Sites HashMap<Integer, String> Sites = new HashMap<Integer, String>(); // 添加键值对 Sites.put(1, "Google"); Sites.put(2, "Runoob"); Si

  • Java中你真的会用Constructor构造器吗之看完本篇你就真的会了

    引言 相信大家对于java里的构造器应该都是有了解的,这次我们来了解一些构造器的不同使用方式,了解构造器的调用顺序,最后可以灵活的在各种情况下定义使用构造器,进一步优化我们的代码: 构造器简介 还是简单介绍一下构造器到底是什么吧, 构造器是类中一种特殊的方法,通过调用构造器来完成对象的创建,以及对象属性的初始化操作. 构造器定义方式: [修饰符列表] 构造器名(形式参数列表){ 构造方法体; } 构造器有以下几个特点: 构造器名和类名一致: 构造器用来创建对象,以及完成属性初始化操作: 构造器返

  • Java中LinkedList的模拟实现

    目录 关于LinkedList 模拟实现LinkedList 准备工作 头插 尾插 在任意位置插入 删除remove 删除removeAll 找元素下标indexOf 找元素下标lastIndexOf 判断链表是否包含某个元素 获得链表长度 链表判空 清空链表 关于LinkedList LinkedList的底层是用一个双向链表实现的,即一个结点中除了有一个引用指向下一个结点的地址,还有一个引用指向前一个结点的地址 LinkedList还有三个成员变量: size,表示该链表中结点的个数 fir

  • java中List集合及其实现类的方法详解

    List集合_List接口介绍 特点 1).有序的: 2).可以存储重复元素: 3).可以通过索引访问: List<String> list = new ArrayList<>(); list.add("张无忌"); list.add("张三丰"); list.add("章子怡"); list.add("章子怡");//OK的,可以添加 for(String s : list){ System.out.

  • Java中ArrayList和LinkedList的遍历与性能分析

    前言 通过本文你可以了解List的五种遍历方式及各自性能和foreach及Iterator的实现,加深对ArrayList和LinkedList实现的了解.下面来一起看看吧. 一.List的五种遍历方式 1.for each循环 List<Integer> list = new ArrayList<Integer>(); for (Integer j : list) { // use j } 2.显示调用集合迭代器 List<Integer> list = new Ar

随机推荐