关于集合的一点儿小见解

大家在日常的开发中用的最多的容器要属集合了吧,关于集合大家又真正的了解多少呢?比如面试最喜欢问的一个Arraylist和Linkedlist的区别?大家都知道arraylist查找快,linkedlist增删快这一结论;但是关于他内部的原因大家又了解多少呢?今天,我们结合部分源码来深度解析一下他们存在这种区别的原因?

添加方法比较:不管是arraylist还是linkedlist都是调用的add方法,大家都知道,arraylist的本质是一个数组,下面是arraylist的部分源码分析:

关于集合的一点儿小见解

可以看到他的实际添加值都是通过下标递增来进行赋值的;既然是数组,那就会涉及到一个问题,数组都是定长的,而我们使用的集合通常没有定义他的长度,而且还是源源不断的添加数据,这个是如何实现的呢?再附上一张源码图

关于集合的一点儿小见解

可以看到源码在这里进行了处理,arraylist存在一个初始容量,当超过这个容量的时候会进行扩容,调用grow方法:

关于集合的一点儿小见解

可以看到当超过初始容量的时候,扩容之后的新容量是之前老容量的1.5倍,然后再调用copyOf方法,将新容量和老数组中的数据进行复制;我们这里描述的比较快,因为对于扩容来说,内容也是比较多的,我们以后有时间专门补一篇扩容的文章;这里主要介绍区别。

大家了解了arraylist的结构之后,再去看下linkedlist的结构,linkedlist本身是一个双向链表:

关于集合的一点儿小见解

上面就是他add方法的核心代码,根据传入的对象创建一个node节点,然后拼接在链表最后,如此看来,linkedlist添加速度是比arraylist要快的,不需要每次添加都去重新copyOf原数组的值;

查询方法比较:arraylist本质是一个数组,数组本身就存在下标,arraylist的查询就好比是一群人拿着序号在排队,你要找出某一个序号的人会特别简单,直接到那个位置去找他就可以了;这里不过多分析;

Linkdlist是链表,他是如何对元素进行查找的呢?核心代码如下:

关于集合的一点儿小见解

Linkdlist将传入的索引和当前链表的长度的一半做对比,看属于前半部分还是后半部分,属于前半部分的话,从0开始到,查询下一个,一直到索引位置,使用for循环,逐个遍历;属于后半段的话,就从最后开始,查询前一个,一直到索引位置,所以,linkedlist的情况比较特殊,我们通过源码分析得出的结论是:他并不是所有的查询都比较慢,查询第一个和最后一个的时候还是比较快的,有可能比arraylist都快,因为他们分别是前、后部分循环的起始位置,但是中间部分的就比较麻烦了,要循环多次,比起arraylist要慢的多。

删除方法:

Arraylist是一个数组,删除数组中的某一个元素,大家可能都写过这种小例子,源码这里呢到了native关键字修饰的方法,就进不去了,可能是c++的底层,我们看不到,但是我这里自己写了一个小例子用来实现删除数组的某一个对象:

关于集合的一点儿小见解

运行结果如下:

关于集合的一点儿小见解

在这里我们要删除的第三个元素,然后要将第三个后边的元素前移一位,才能得到最终的效果,我们可以大胆进行猜测一下,当数组中的数据有几十万条时,我们如果要移除第一条数据,arraylist要把所有的数据前移一位,这一步做起来是非常消耗时间的;而linkedlist的移除请看源码:

关于集合的一点儿小见解

看起来好像很复杂,他内部的逻辑其实是比较简单的,因为他是双向链表,每一个元素只跟前一个prev和后一个next有关系,我们要移除某一个元素的时候,只要把他的前后束缚断掉进行重新指向即可,不需要最整体链表进行操作,然后断掉的节点会被gc回收,因此,linkedlist的删除效率是远远要比arraylist要高的;

总结:对于不经常修改的数据,我们常常采用arraylist进行存储,因为他查询较快,反之呢,就用linkedlist进行存储,大家在使用的时候注意取舍。


分享到:


相關文章: