JAVA基础篇-深入比较ArrayList和LinkedList

引言

List是我们能开发中最场用到的一种集合容器,也是第三方框架中最常用到的集合容器,用到的频率屈居老二,除了老大String。
其API简单上手,使用起来也很方便,我们平时很多时候都是不假思索就用起来了,我们有没有想过是不是所有场景都该用List,是不是所有场景都该用ArrayList,是不是有的场景该选择使用LinkedList呢,是否可以选择Set?

预备知识

1.两者都是Collection的子类,都实现了List接口。
2.两者都实现了Coloneable和Serializable接口,支持克隆和序列化操作。
3.两者都是AbstractList的子类。
4.使用时我们一般都是用List<TypeReference>泛型形式来构造一个集合对象。
5.两者容器大小都是静态固定的,如果下表越界的话都会抛出IndexOutOfBoundsException异常。

注意:LinkedList的首个元素没有前驱节点而末元素是没有后继节点,首尾并不相连。我们不要误以为它是循环链表。

\color{red}{上面的阐述我们可以通过他们俩的类的结构图能看出来 }

ArrayList类图.png
LinkedList类图.png
  • 不同点
    \color{red}{LinkedList}本质上是一个既是一个双端队列,也是一个双向链表,\color{red}{ArrayList}本质上是一个数组(链表),只不过其可以动态扩容。

    \color{red}{LinkedList}\color{red}{ArrayList}更耗内存。理由是\color{red}{LinkedList}除了数据块之外,还有前驱Node和后继Node,因为需要对象存储所以更耗内存。

    \color{red}{LinkedList}更适合随机插入和添加数据,只需改变前驱后继节点指向即可。

    \color{red}{ArrayList}在随机访问效率比\color{red}{LinkedList}效率更快,直接数组通过index索引定位到元素(RandomAccess有详细说明 for each runs faster than iterator),而\color{red}{LinkedList}随机取值时,需要循环指针迭代。

  • 我们来说明一下\color{red}{ArrayList}的扩容机制,扩容对象其实就是\color{red}{ArrayList}中的\color{red}{ElementData}数组的容量动态大小。

扩容算法描述:当没有初始化的情况下,第一次扩容为默认10个大小,往后当size超过这个容量的时候就进行n+n/2的方式进行扩容,当有足够容量的时候,每次添加的时候就不会扩容,这样大大增加了往尾部添加元素的效率。初始化大小为10的扩容数列是:{10,15,22,33,49.........},依次类推即可。动态数组默认的最大容量是MAX_VALUE = 0x7fffffff=2^31-1,如果在添加数据的情况下出现内存溢出也会抛出内存溢出异常。

优缺点说明

\color{red}{ArrayList}是个可动态扩容的数组,是带有下表索引数组的一个容器,适合用来检索数据.

\color{red}{LinkedList}是一个双端链表,并且每个数据都包含了Node节点,每个Node都包含了Prev(前驱结点)与Next(后继节点)以及item(数据节点).适合随机添加与插入和删除操作,LinkedList不需要动态扩容。

性能对比:

  1. \color{red}{ArrayList}在尾部添加大量元素时的效率反而要高于 \color{red}{LinkedList}。原因是:前者在容量足够的情况下时不需要扩容的,而LinkedList需要构造一个Node节点出来。
//ArrayList向尾部添加元素
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

//LinkedList向尾部添加元素
 public boolean add(E e) {
        linkLast(e);
        return true;
    }

    /**
     * Links e as last element。
    *空时将first的后继节点指向新添入的节点,非空时将 
    * Last的next指向新的节点
     */
    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++;
    }
  1. \color{red}{ArrayList}指定位置设置元素的时间上的性能明显高于 \color{red}{LinkedList}.
    \color{red}{ArrayList}的时间复杂度时O(1),而\color{red}{LinkedList}的时间复杂度时O(n).
//这是ArrayList的指定位置修改函数
 public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
··································································
//这是 $\color{red}{LinkedList}$的修改指定位置的元素
public E set(int index, E element) {
        checkElementIndex(index);
        //这里折中法查找指定位置上的Node节点,双向循环队列必须这么做啊!
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

 Node<E> node(int index) {
        // assert isElementIndex(index);
      //
        if (index < (size >> 1)) {
           //如果是索引是前半段则从折中以first节点后继节点寻找
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
           //如果是索引是前半段则从折中以last前驱节点寻找
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }


3.\color{red}{ArrayList}For 循环取值和迭代器取值要远远高于LinkedList的取值\color{red}{LinkedList}

只要对比他们的源码就可以直观地分析出来了。

//List通过索引取值时直接数组下标取值,相当于hash取值一样的效率,效率级别为O(1)
 public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

//而LinkedList 是没有数组下标的概念的,只有Node节点的概念,所以通过下表去找对象的时候只能通过节点去next或prev查找
    /**
     * Returns the (non-null) Node at the specified element index.
     */
    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;
        }
    }

4.\color{red}{ArrayList}根据索引移除元素在效率上要低于\color{red}{LinkedList},通过源代码分析可以确认这个结论。

 public E ArrayList.remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }


    /**
     * Unlinks non-null node x.
     */
    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;
    }

5.\color{red}{ArrayList}在迭代器中移除元素的效率同样也低于于\color{red}{LinkedList},亲测代码。

        Iterator arrayListIterator = arrayList.iterator();

        while (arrayListIterator.hasNext()) {
            arrayListIterator.next();
            arrayListIterator.remove();
        }
        long end = System.currentTimeMillis();

        System.out.println(String.format("ArrayList 迭代器 中删除元素耗时%s毫秒", end - start));

        start = System.currentTimeMillis();
        Iterator linkedIterator = linkedList.iterator();

        while (linkedIterator.hasNext()) {
            linkedIterator.next();
            linkedIterator.remove();
        }

        end = System.currentTimeMillis();
        System.out.println(String.format("LinkedList 迭代器 中删除元素耗时%s毫秒", end - start));

作用场景描述

1.我们在平时做查询接口的时候通常都选择ArrayList集合,相比LinkedList和其他Set集合的话,ArrayList的迭代效率更快更适合做协议上的流化输出,并且ArrayList在不需要频繁扩容的情况下往尾部添加数据的效率并不低。如果需要保证线程安全的话,用Collections.SynchronizedList(List)来设置一个有同步器的集合。

2.尽量减少ArrayList的扩容次数,提高性能,知道固定大小的情况下可以设置数组的初始大小,知道大概区间就设置上限初始的Capacity大小。

3.做根据下表去删除和在中间位插入元素时尽量选择LinkedList,原因是ArrayList需要做数组移位操作。而LinkedList只需要改变节点的前驱和后继节点的指向即可。

4.因为LinkedList有双端队列的特性,所以天生适合移除首尾元素,还有更适合添加头节点元素。

ArrayList删除动作.png

补充:ArrayList的数组移位压缩并不慢,做了哪些优化呢?因为用到了System.arrayCopy)(),这个方法是JNI接口。

//Linux copy的方法
 void _Copy_conjoint_jints_atomic(jint* from, jint* to, size_t count) {
    if (from > to) {
      jint *end = from + count;
      while (from < end)
        *(to++) = *(from++);
    }
    else if (from < to) {
      jint *end = from;
      from += count - 1;
      to   += count - 1;
      while (from >= end)
        *(to--) = *(from--);
    }
  }

//Windows

static void pd_conjoint_jints_atomic(jint* from, jint* to, size_t count) {
  if (from > to) {
    while (count-- > 0) {
      // Copy forwards
      *to++ = *from++;
    }
  } else {
    from += count - 1;
    to   += count - 1;
    while (count-- > 0) {
      // Copy backwards
      *to-- = *from--;
    }
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353