Java集合 - ArrayList,LinkedList,Vector的相同点与区别是什么?

要想回答这个问题,可以先把各种都讲特性,然后再从底层存储结构,线程安全,默认大小,扩容机制,迭代器,增删改查效率这几个方向入手。

特性列举

  • ArrayList:动态数组,使用的时候,只需要操作即可,内部已经实现扩容机制。线程不安全有顺序,会按照添加进去的顺序排好基于数组实现,随机访问速度快,插入和删除较慢一点可以插入null元素,且可以重复
  • Vector和前面说的ArrayList很是类似,这里说的也是1.8版本,它是一个队列,但是本质上底层也是数组实现的。同样继承AbstractList,实现了List,RandomAcess,Cloneable, java.io.Serializable接口。具有以下特点:提供随机访问的功能:实现RandomAcess接口,这个接口主要是为List提供快速访问的功能,也就是通过元素的索引,可以快速访问到。可克隆:实现了Cloneable接口是一个支持新增,删除,修改,查询,遍历等功能。可序列化和反序列化容量不够,可以触发自动扩容*最大的特点是:线程安全的,相当于线程安全的ArrayList。
  • LinkedList:链表结构,继承了AbstractSequentialList,实现了List,Queue,Cloneable,Serializable,既可以当成列表使用,也可以当成队列,堆栈使用。主要特点有:线程不安全,不同步,如果需要同步需要使用List list = Collections.synchronizedList(new LinkedList());实现List接口,可以对它进行队列操作实现Queue接口,可以当成堆栈或者双向队列使用实现Cloneable接口,可以被克隆,浅拷贝实现Serializable,可以被序列化和反序列化

底层存储结构不同

ArrayList和Vector底层都是数组结构,而LinkedList在底层是双向链表结构。

线程安全性不同

ArrayList和LinkedList都不是线程安全的,但是Vector是线程安全的,其底层是用了大量的synchronized关键字,效率不是很高。

如果需要ArrayList和LinkedList是线程安全的,可以使用Collections类中的静态方法synchronizedList(),获取线程安全的容器。

默认的大小不同

ArrayList如果我们创建的时候不指定大小,那么就会初始化一个默认大小为10,DEFAULT_CAPACITY就是默认大小。

private static final int DEFAULT_CAPACITY = 10;

Vector也一样,如果我们初始化,不传递容量大小,什么都不指定,默认给的容量是10:

    public Vector() {
        this(10);
    }

而LinkedList底层是链表结构,是不连续的存储空间,没有默认的大小的说法。

扩容机制

ArrayList和Vector底层都是使用数组Object[]来存储,当向集合中添加元素的时候,容量不够了,会触发扩容机制,ArrayList扩容后的容量是按照1.5倍扩容,而Vector默认是扩容2倍。两种扩容都是申请新的数组空间,然后调用数组复制的native函数,将数组复制过去。

Vector可以设置每次扩容的增加容量,但是ArrayList不可以。Vector有一个参数capacityIncrement,如果capacityIncrement大于0,那么扩容后的容量,是以前的容量加上扩展系数,如果扩展系数小于等于0,那么,就是以前的容量的两倍。

迭代器

LinkedList源码中一共定义了三个迭代器:

  • Itr:实现了Iterator接口,是AbstractList.Itr的优化版本。
  • ListItr:继承了Itr,实现了ListIterator,是AbstractList.ListItr优化版本。
  • ArrayListSpliterator:继承于Spliterator,Java 8 新增的迭代器,基于索引,二分的,懒加载器。

Vector和ArrayList基本差不多,都是定义了三个迭代器:

  • Itr:实现接口Iterator,有简单的功能:判断是否有下一个元素,获取下一个元素,删除,遍历剩下的元素
  • ListItr:继承Itr,实现ListIterator,在Itr的基础上有了更加丰富的功能。
  • VectorSpliterator:可以分割的迭代器,主要是为了分割以适应并行处理。和ArrayList里面的ArrayListSpliterator类似。

LinkedList里面定义了三种迭代器,都是以内部类的方式实现,分别是:

  • ListItr:列表的经典迭代器
  • DescendingIterator:倒序迭代器
  • LLSpliterator:可分割迭代器

增删改查的效率

理论上,ArrayList和Vector检索元素,由于是数组,时间复杂度是O(1),在集合的尾部插入或者删除是O(1),但是其他的地方增加,删除,都是O(n),因为涉及到了数组元素的移动。但是LinkedList不一样,LinkedList不管在任何位置,插入,删除都是O(1)的时间复杂度,但是LinkedList在查找的时候,是O(n)的复杂度,即使底层做了优化,可以从头部/尾部开始索引(根据下标在前一半还是后面一半)。

如果插入删除比较多,那么建议使用LinkedList,但是它并不是线程安全的,如果查找比较多,那么建议使用ArrayList,如果需要线程安全,先考虑使用Collections的api获取线程安全的容器,再考虑使用Vector。

测试三种结构在头部不断添加元素的结果:


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class Test {
    public static void main(String[] args) {
        addArrayList();
        addLinkedList();
        addVector();
    }
    public static void addArrayList(){
        List list = new ArrayList();
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }

    public static void addLinkedList(){
        List list = new LinkedList();
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }
    public static void addVector(){
        List list = new Vector();
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }
}

测出来的结果,LinkedList最小,Vector费时最多,基本验证了结果:

ArrayList:7715
LinkedList:111
Vector:8106

测试get的时间性能,往每一个里面初始化10w个数据,然后每次get出来:


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class Test {
    public static void main(String[] args) {
        getArrayList();
        getLinkedList();
        getVector();
    }
    public static void getArrayList(){
        List list = new ArrayList();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.get(i);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }

    public static void getLinkedList(){
        List list = new LinkedList();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.get(i);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }
    public static void getVector(){
        List list = new Vector();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.get(i);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }
}

测出来的时间如下,LinkedList 执行get操作确实耗时巨大,Vector和ArrayList在单线程环境其实差不多,多线程环境会比较明显,这里就不测试了:

ArrayList : 18
LinkedList : 61480
Vector : 21

测试删除操作的代码如下,删除的时候我们是不断删除第0个元素:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class Test {
    public static void main(String[] args) {
        removeArrayList();
        removeLinkedList();
        removeVector();
    }
    public static void removeArrayList(){
        List list = new ArrayList();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.remove(0);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }

    public static void removeLinkedList(){
        List list = new LinkedList();
        for(int i=0;i<100000;i++){
            list.add(0,i);
        }
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.remove(0);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }
    public static void removeVector(){
        List list = new Vector();
        for(int i=0;i<100000;i++){
            list.add(i);
        }
        long startTime = System.nanoTime();
        for(int i=0;i<100000;i++){
            list.remove(0);
        }
        long endTime = System.nanoTime();
        System.out.println((endTime-startTime)/1000/60);
    }
}

测试结果,LinkedList确实效率最高,但是Vector比ArrayList效率还要高。因为是单线程的环境,没有触发竞争的关系。

ArrayList: 7177
LinkedList: 34
Vector: 6713

下面来测试一下,vector多线程的环境,首先两个线程,每个删除5w元素:

package com.aphysia.offer;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class Test {
    public static void main(String[] args) {
        removeVector();
    }

    public static void removeVector() {
        List list = new Vector();
        for (int i = 0; i < 100000; i++) {
            list.add(i);
        }
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    list.remove(0);
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    list.remove(0);
                }
            }
        });
        long startTime = System.nanoTime();
        thread1.start();
        thread2.start();
        while (!list.isEmpty()) {

        }
        long endTime = System.nanoTime();
        System.out.println((endTime - startTime) / 1000 / 60);
    }
}

测试时间为:12668

如果只使用一个线程,测试的时间是:8216,这也从结果说明了确实Vector在多线程的环境下,会竞争锁,导致执行时间变长。

总结一下

  • ArrayList

  • 底层是数组,扩容就是申请新的数组空间,复制

  • 线程不安全

  • 默认初始化容量是10,扩容是变成之前的1.5倍

  • 查询比较快

  • LinkedList

  • 底层是双向链表,可以往前或者往后遍历

  • 没有扩容的说法,可以当成双向队列使用

  • 增删比较快

  • 查找做了优化,index如果在前面一半,从前面开始遍历,index在后面一半,从后往前遍历。

  • Vector

  • 底层是数组,几乎所有方法都加了Synchronize

  • 线程安全

  • 有个扩容增长系数,如果不设置,默认是增加原来长度的一倍,设置则增长的大小为增长系数的大小。

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

推荐阅读更多精彩内容