Java里面LinkedList、ArrayList的效率分析

Java数据结构之LinkedList、ArrayList的效率分析

前言:

     在我们平常开发中难免会用到List集合来存储数据,一般都会选择ArrayList和LinkedList,以前只是大致知道ArrayList查询效率高LinkedList插入删除效率高,今天来实测一下。

先了解一下List     

  List列表类,顺序存储任何对象(顺序不变),可重复。

  List是继承于Collection的接口,不能实例化。实例化可以用:

  • ArrayList(实现动态数组),查询快(随意访问或顺序访问),增删慢。整体清空快,线程不同步(非线程安全)。数组长度是可变的百分之五十延长
  • LinkedList(实现链表),查询慢,增删快。
  • Vector(实现动态数组),都慢,被ArrayList替代。长度任意延长。线程安全(同步的类,函数都是synchronized)
  • Stack(实现堆栈)继承于Vector,先进后出。

    List基本操作

  • 插入:add()
  • 查找:get()
  • 删除:remove(int index)
  • 修改:set()
  • 清空表:clear()
  • 遍历:用Iterator迭代器遍历每个元素

ArrayList、LinkedList性能对比

为了很好的对比效率,直接写个测试程序看下运行结果

代码1:模拟5w条数据指定插入最后一位,然后查询全部,循环从最后一位删除

 

public class TestList {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		testArrayList();
		testLinkedList();
	}

	private static void testLinkedList() {
		LinkedList<String> list = new LinkedList<>();
		int maxTestCount = 50000;

		// 测试添加
		long start = System.currentTimeMillis();

		for (int i = 0; i < maxTestCount; i++) {
			list.add(0, String.valueOf(i));
		}

		long end = System.currentTimeMillis();

		 System.out.println("LinkedList add cost time :" + (end - start));

		// 测试查询
		start = System.currentTimeMillis();

		for (int i = 0; i < maxTestCount; i++) {
			list.get(i);
		}

		end = System.currentTimeMillis();

		 System.out.println("LinkedList get cost time :" + (end - start));

		// 测试删除
		start = System.currentTimeMillis();

		for (int i = maxTestCount; i > 0; i--) {
			list.remove(0);
		}

		end = System.currentTimeMillis();

		 System.out.println("LinkedList remove cost time :" + (end - start));

	}
	
	
	
	private static void testArrayList(){
        ArrayList<String> list=new ArrayList<>();
        int maxTestCount=50000;

        //测试添加
        long start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.add(i,String.valueOf(i));
        }

        long end =System.currentTimeMillis();

        System.out.println("ArrayList add cost time :"+(end-start));

        //测试查询
        start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.get(i);
        }

        end =System.currentTimeMillis();

        System.out.println("ArrayList get cost time :"+(end-start));

        //测试删除
        start =System.currentTimeMillis();

        for(int i =maxTestCount;i>0;i--){
            list.remove(i-1);
        }

        end =System.currentTimeMillis();

        System.out.println("ArrayList remove cost time :"+(end-start));

    }

}

 

结果:

ArrayList add cost time :15
ArrayList get cost time :2
ArrayList remove cost time :3
LinkedList add cost time :10
LinkedList get cost time :2308
LinkedList remove cost time :2

代码2:模拟5w条数据指定插入第一位,然后查询全部,循环从第一位删除

public class TestList {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		testArrayList();
		testLinkedList();
	}

	private static void testLinkedList() {
		LinkedList<String> list = new LinkedList<>();
		int maxTestCount = 50000;

		// 测试添加
		long start = System.currentTimeMillis();

		for (int i = 0; i < maxTestCount; i++) {
			list.add(0, String.valueOf(i));
		}

		long end = System.currentTimeMillis();

		 System.out.println("LinkedList add cost time :" + (end - start));

		// 测试查询
		start = System.currentTimeMillis();

		for (int i = 0; i < maxTestCount; i++) {
			list.get(i);
		}

		end = System.currentTimeMillis();

		 System.out.println("LinkedList get cost time :" + (end - start));

		// 测试删除
		start = System.currentTimeMillis();

		for (int i = maxTestCount; i > 0; i--) {
			list.remove(0);
		}

		end = System.currentTimeMillis();

		 System.out.println("LinkedList remove cost time :" + (end - start));

	}
	
	
	
	private static void testArrayList(){
        ArrayList<String> list=new ArrayList<>();
        int maxTestCount=50000;

        //测试添加
        long start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.add(0,String.valueOf(i));
        }

        long end =System.currentTimeMillis();

        System.out.println("ArrayList add cost time :"+(end-start));

        //测试查询
        start =System.currentTimeMillis();

        for(int i =0;i<maxTestCount;i++){
            list.get(i);
        }

        end =System.currentTimeMillis();

        System.out.println("ArrayList get cost time :"+(end-start));

        //测试删除 
        start =System.currentTimeMillis();

        for(int i =maxTestCount;i>0;i--){
            list.remove(0);
        }

        end =System.currentTimeMillis();

        System.out.println("ArrayList remove cost time :"+(end-start));

    }

}

结果:

ArrayList add cost time :168
ArrayList get cost time :2
ArrayList remove cost time :138
LinkedList add cost time :8
LinkedList get cost time :2636
LinkedList remove cost time :4

 

通过上面的运行结果可以大致得出以下结论:

  • ArrayList插入、删除效率,在每次都操作第一位元素的时候,明显低于LinkedList
  • ArrayList查询效率远远高于LinkedList

通过上面的结构是不是就可以认为插入删除频繁选择LinkedList,追求查询效率就选择ArrayList呢,我们先来分析一下效率差别的原因,这个就跟数据结构有关系了,可以参考一些数据结构中链表的知识,arraylist 顺序表,用数组的方式实现。想想数组要查询那个元素只给出其下标即可,所以才说arraylist随机访问多的场景比较合适。但是如果删除某个元素比如第 i 个元素,则要将 i 之后的元素都向前移一位以保证顺序表的正确,增加也是一样,增加还涉及到扩容的问题,要移动多个元素。要多次删除增加的话是很低效的。而LinkedList是双向链表,注意是链表。要查询只能头结点开始逐步查询,虽然linkedlist也有get(i)的方法,看起来也是根据下标去获取元素,但是它的get(i)底层是这样的

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


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

还是遍历了整个链表去获取元素,所以慢。但是,如果要增加后删除一个元素的话,只需要改变其前后元素的指向即可,不需要像arraylist那样整体移动,所以才说多用于增删多的场合。

 

List扩容

我们知道List集合的底层实现是数组结构,而数组的大小是不可改变的,因此当其容器内存不足时,需要进行扩容,扩容的方法就是重新分配一个新数组,然后复制元素到新数组中,再将元素添加到数组末位 
一、ArrayList 
首先便是默认大小的问题 
这里我们可以通过ArrayList的源码来分析,首先可以看一下他的构造函数,发现有三个,分别是:无参、一个int型参数、一个集合泛型; 
在分析构造函数之前 我们先来看几个参数: 
在 
即ArrayList 在内部的表示即为一个Object类型的数组,并非真正的泛型,这里的变量就是数组的真实存储大小 
这里写图片描述 
可以理解为设置默认数组大小的常量

1、无参的构造函数 
这里写图片描述 
在这里我们可以看到,当不传参数时,数组的大小直接为默认数组大小的常量,而这个大小就为10,至于怎么得到的,这里我们可以先卖个小关子,在后面一起介绍~~ 
2、有一个int的构造函数 
这里写图片描述 
我们可以发现,当传入的值大于0时,数组的大小即为传入的值大小,为0时,则为EMPTY_ELEMENTDATA,小于0则抛出异常。这个EMPTY_ELEMENTDATA又是什么意思呢?? 
其实就是0(即亦可理解为传入的值);

3、有一个Collection泛型的构造函数 
这里写图片描述 
而集合参数的其实与Int的构造函数差不多,只是在初始化容器的同时,还将容器中的数据也初始化了(完成copy)。

虽然说完了构造函数,但实际的扩容是发生在增加元素的时候(容器内存不足或者存储达到加载因子),因此 来到了最为主要的一部分内容: 
4、ArrayList 的元素添加 
这里写图片描述 
通过上面的代码可以看到,每次添加元素的时候,都会执行一个方法,而这个方法,就是判断是否需要扩容的方法; 
这里写图片描述
进入方法之后,我们看到了一个if语句,发现了么,这个就是我们之前在无参构造函数时卖的关子,当当前的大小为默认数组大小的常量时,minCapacity(即位数组中存储的数据大小即size+1)就为DEFAULT_CAPACITY, minCapacity之间大的那个,而minCapacity的默认值为0,所以从这里就可以知道,无参构造函数产生的数组大小为10~~ 
但是真正的判断还不在这里,而是下面这个方法: 
这里写图片描述 
由此我们可以看出,其实就是判断加一个元素后,数组大小是够超出当前数组的大小,若超出,则执行下面的”扩容“代码~~ 
这里写图片描述

从文中我们可看到,新数组的大小其实就是( oldCapacity + (oldCapacity >> 1)),如果你懂得位运算的话,你就知道了,原来就是原数组的长度加上原数组的长度大小的一半(位运算,右移一位相当于整体/2),后面判断数组大小越界这里就不多阐述了,感兴趣的可以自己查看一下源码。 
上面的ArrayList是没有涉及加载因子的,但是有些集合类型如Vector等使用到了加载因子,只要把是否需要扩容d的判断条件改一下就可以了。 

总结:

   通过运行结果和查阅资料基本上验证了ArrayList和LinkedList效率问题,有助于在以后的开发中根据实际场景选择合适的技术方案,所以不能笼统的说arrayList适合查询元素,而linkedList适合增删元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值