想聊几个数据结构相关的内容,从List相关的集合入手吧,重点谈一下ArrayList 和 LinkedList。
首先,我们从以下几个方面进行探究:
1、数据结构上,顺序表和链表的差异、优缺点、适用范围;
2、结合源码对两种数据结构进行分析;
3、日常使用如何选择
我们看下解答:
1、二者存储结构的区别
顺序表有以下特征:
- 长度固定,必须在分配内存之前确定数组的长度,可以使用倍增-复制的办法来支持动态扩容;
- 存储空间连续,即允许元素的随机访问。
- 存储密度大,空间利用率高,内存中存储的全部是数据元素。
- 要访问特定元素,可以使用索引访问,时间复杂度为 O(1)。
- 要想在顺序表中插入或删除一个元素,都涉及到之后所有元素的移动,因此时间复杂度为 O(n)。
所以,如果是查询比较多的场景,一般建议使用顺序表存储结构。
链表有以下特征:
- 长度不固定,可以任意增删。
- 存储空间不连续,数据元素之间使用指针相连,每个数据元素只能访问周围的一个元素(根据单链表还是双链表有所不同)。
- 存储密度小,空间利用率不高,因为每个数据元素,都需要额外存储一个指向下一元素的指针(双链表则需要两个指针)。
- 要访问特定元素,只能从链表头开始,遍历到该元素,时间复杂度为O(n) 。
- 在特定的数据元素之后插入或删除元素,不涉及到其他元素的移动,因此时间复杂度为O(1) 。双链表还允许在特定的数据元素之前插入或删除元素。
如果是增删操作比较多的场景,就建议使用链表存储结构。
2、从源码分析
我们先从ArrayList入手吧,首先看下这个类:
这边实现了三个接口,但是点进去看会发现,这三个接口其实都是空接口,就是说它啥方法也没定义,那么实现这些接口是干嘛用的呢,这个要从虚拟机那边入手了,简单来说呢,这三个空接口主要的作用就是三个标记,比如我们加载遍历这个ArrayList,JVM可能并不知道怎么加载,可以通过RandomAccess判断加载方式,这个RandomAccess接口说明实现它的类是支持随机访问的,而且大部分都是通过数组实现的。然后这是个集合类,该类的遍历方式也是以此来判断,RandomAccess表示该类是以for循环进行遍历的(当然还有一种方式是迭代器的循环方式)。
Cloneable接口表示该类支持拷贝,其实我们万物鼻祖Object里面就有定义一个clone(),但是我们在类中重写这个方法,执行的时候也是会报错的,这是因为还需要实现Cloneable接口,也就是贴上这个标签之后才能被识别。方法拷贝的最大好处是当你需要将某个集合的数据复制一份的话,我们平时会定义一个新的集合,然后循环将数据赋值给新的集合,但是这个拷贝直接通过 clone方法复制一份,效率会高很多。讲到拷贝的话,需要补充一些内容,可以看我另一篇文章:Cloneable -- Java中的拷贝。
Serializable接口是表示该类支持序列化,我们平时数据在应用执行结束