1、list集合主要实现类对比
List集合继承于Collection,是一个有序的的集合,可以包含重复的元素,提供了按索引的方式查询元素。
List是一个接口,重要的实现类
- ArrayList
底层数据结构是数组,根据指定下标随机查询快,增删慢(增删后涉及数组重排序问题,数据量大会效率会降低)
线程不安全,效率高
数据保存占用的内存空间是连续的,每次扩容需要查看当前空间够不够扩容后的长度,如果不够会重新分配足够的长度空间 - LinkedList
底层数据结构是链表,查询慢(链表查找会将原链表从中间分为两部分,查询根据index的大小来觉得从头部开始遍历还是从尾部开始遍历查找,相对于ArrayList有序集合大数据量查询会慢些),增删快(链表增删不涉及编号重排序问题,但并不是所有的增删的效率都比较高,有时删除也是要遍历链表的,所以效率依然低)
线程不安全,效率高
数据保存不是占用连续的空间,可间隔保存 - Vector
底层数据结构是数组,查询快,增删慢
线程安全,效率低
2、部分源码分析
源码解析 - ArrayList
ArrayList有如下成员变量:
private static final int DEFAULT_CAPACITY = 10;//默认初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};//空的元素
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空集合数据
transient Object[] elementData; // 集合元素数据
private int size;//初始长度
- -add操作
ArrayList在初始化时如果未指定容量,则在第一次添加元素时会先做扩容操作,如下,先判断集合是否为空,如果为空则指定默认的容量长度,默认长度为10
这里会判断集合是否需要扩容(只有在集合在第一次添加和集合容量用完时才会进行扩容),如果添加minCapacity(size+1后的结果,也可认为是添加当前元素后集合的长度),elementData是集合数据,因为默认集合创建后集合未添加的元素全部用null代替,所以这里elementData的长度即可认为是集合的目前最大容量,如果添加后的长度大于当前集合的最大长度则表示集合需要扩容,调用grow方法进行扩容操作。
集合扩容操作,如下 >>1 是移位运算符(将当前值转换为二进制,然后左移或右移,移动后去除移出的部分,高位或低位补0),表示当前集合的容量右移一位,可认为是将当前集合扩容为原来的1.5倍向下取整。
- -select操作
根据下标获取对应的元素,进来先判断参数是否下标越界,如果越界会返回异常;如果未越界则返回数组的对应的值。
- ** indexOf **
ArrayList的查询某个元素是否存在方法,也是做的从前向后的遍历比较查询,返回第一次查询到的下标值,不存在则返回-1。
注:如果在集合初始化时指定集合的长度比未指定长度调用此方法的效率会略高些。
源码分析 - LinkedList
LinkedList的添加可以看出与ArrayList完全不同,LinkedList的成员变量有:
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
- -add操作
有内部类Node,LinkedList是双向链表结构,而且初始化时不能指定长度,每次每次都是添加的一个node对象,第一次添加即是集合的第一个对象也是最后的一个对象,下次再添加是即是向当前节点的next节点添加数据。
- -select操作
linkedList根据下标查询值和ArrayList略有区别,会判断下标属于集合的上半部分还是下半部分,然后遍历返回对应的元素值;所以与其他两种集合比较性能相对慢一些。
- indexOf 方法
同样是做的从前向后的遍历,返回第一次查询到的下标值,不存在则返回-1。
源码分析 - Vector
- -add操作
如下,Vector的构造器,有参构造器和无参构造器,表示可自定义集合的长度也可不指定,如果不指定长度,则默认给定初始长度为10,之后每次扩容都是扩容为原来容量的二倍。
- -select操作
和ArrayList差不多,只是在get方法上上了锁。
- indexOf 方法
和ArrayList差不多,不同的是参数传了一个初始下标值0,其实和ArrayList一样,也是做的从前向后遍历,返回第一个查询到的下标值,不存在则返回-1。
3、性能测试
add性能测试
测试结果:注:这里测试全是数字类型,没有图片或过长的内容,所以与实际的数据测试会略有出入。
一次插入1万条数据,性能相差并不明显
一次插入一千万条数据,此时用时如下,注:vector如果数据量比较小且确定容量大小时可以指定默认容量,但如果数据量多的话就不要指定默认容量了,这样反而性能会更差。
给一千万数据量的集合的某下标处添加值,用时如下,此时发现linkedList性能会更好些
public class ListTest {
private static int max = 1000000;
private static List<Integer> arryList = new ArrayList<Integer>();
private static List<Integer> arryList1 = new ArrayList<Integer>(max);
private static List<Integer> linkList = new LinkedList<Integer>();
private static Vector<Integer> vector = new Vector<Integer>(max);
public static void addList(int sign){
long start = System.currentTimeMillis();
for (int i = 0; i < max; i++) {
if (sign == 0) {
arryList.add(i);
}else if (sign == 1) {
arryList1.add(i);
}else if (sign == 2) {
linkList.add(i);
}else {
vector.add(i);
}
}
// long start = System.currentTimeMillis();
// if (sign == 0) {
// arryList.add(555555,6666);
// }else if (sign == 1) {
// arryList1.add(555555,6666);
// }else if (sign == 2) {
// linkList.add(555555,6666);
// }else {
// vector.add(555555,6666);
// }
long end = System.currentTimeMillis();
System.out.println("add time:" + (end - start));
}
public static void main(String[] args) {
addList(0);
addList(1);
addList(2);
addList(3);
}
}
select 性能测试
在一千万数据量的集合中get 获取数据,用时如下,会发现ArrayList性能比linkedList好些,注意:这里测试用的都是数字,没有比较长的数据(图片、长字符),所以查询可能与正常的数据略有出入。
public class ListTest {
public static Integer select(int sign){
if (sign == 0) {
long start = System.currentTimeMillis();
Integer a = arryList.get(500000);
// Integer a = arryList.indexOf(500000);
long end = System.currentTimeMillis();
System.out.println("select time:" + (end - start));
return a;
}else if (sign == 1) {
long start = System.currentTimeMillis();
Integer a = arryList1.get(500000);
// Integer a = arryList1.indexOf(500000);
long end = System.currentTimeMillis();
System.out.println("select time:" + (end - start));
return a;
}else if (sign == 2) {
long start = System.currentTimeMillis();
Integer a = linkList.get(500000);
// Integer a = linkList.indexOf(500000);
long end = System.currentTimeMillis();
System.out.println("select time:" + (end - start));
return a;
}else {
long start = System.currentTimeMillis();
Integer a = vector.get(500000);
// Integer a = vector.indexOf(500000);
long end = System.currentTimeMillis();
System.out.println("select time:" + (end - start));
return a;
}
}
public static void main(String[] args) {
System.out.println(select(0));
System.out.println(select(1));
System.out.println(select(2));
System.out.println(select(3));
}
}
所以在使用集合的时候可以根据业务中的使用情况确定使用哪种集合,比如写操作比较多或者查询比较多的情况可以选择不同的集合。