ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
读写效率
HashSet读写是最慢的,因为HashSet每次add要判断hashcode,HashSet两种循环中iterator 方式不稳定,不过总是比foreach要快一点。ArrayList读写效率其次,在ArrayList中间插入或删除一个元素,要改变整个集合中该元素后面所有元素的下标位置。LinkedList读写速度最快,LinkedList的中间插入或删除一个元素的开销是固定的,只需要对插入位置前后的元素指针进行修改。
去除集合内部重复元素
如果要用HashSet对集合中元素实现去重,不推荐这种做法,因为hash算法的速度比遍历一个ArrayList要慢得多。即使在ArrayList中进行两次循环,所消耗的时间仍然快于直接使用HashSet的构造方法。
String stocks="001,002,003,002";
LinkedHashSet stocksSet=new LinkedHashSet<(Arrays.asList(stocks.split(",")));
return String.join(",",stocksSet);
ArrayList中的remove方法会将元素前移一个位置,这里使用倒序删除避免漏掉元素或者报并发错误。
String stocks="001,002,003,002";
ArrayList list=new ArrayList<>(Arrays.asList(stocks.split(",")));
for ( int i = 0 ; i < list.size() - 1 ; i ++ ) {
for ( int j = list.size() - 1 ; j > i; j -- ) {
if (list.get(j).equals(list.get(i))) {
list.remove(j);
}
}
}
return String.join(",",list);
访问元素
ArrayList的内部实现是基于基础的对象数组的,因此,它使用get方法访问列表中的任意一个元素时(random-access),它的速度要比LinkedList快。当然也包括使用 for(int i<0;i
空间复杂度
ArrayList和LinkedList在空间复杂度上都具有一定的空间上的冗余。ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,在LinkedList中有一个私有的内部类,定义如下:
private static class Entry {
Object element;
Entry next;
Entry previous;
}
每个Entry对象reference列表中的一个元素,同时还有在LinkedList中它的上一个元素和下一个元素。一个有1000个元素的LinkedList对象将有1000个链接在一起的Entry对象,每个对象都对应于列表中的一个元素。这样的话,在一个LinkedList结构中将有一个很大的空间开销,因为它要存储这1000个Entity对象的相关信息。
ArrayList使用一个内置的数组来存储元素,这个数组的起始容量是10.当数组需要增长时,新的容量按如下公式获得:新容量=(旧容量*3)/2+1,也就是说每一次容量大概会增长50%。这就意味着,如果你有一个包含大量元素的ArrayList对象,那么最终将有很大的空间会被浪费掉,这个浪费是由ArrayList的工作方式本身造成的。如果没有足够的空间来存放新的元素,数组将不得不被重新进行分配以便能够增加新的元素。对数组进行重新分配,将会导致性能急剧下降。如果我们知道一个ArrayList将会有多少个元素,我们可以通过构造方法来指定容量。我们还可以通过trimToSize方法在ArrayList分配完毕之后去掉浪费掉的空间。
ArrayList和LinkedList循环遍历方式的性能分析
1、for-each
List testList = new ArrayList();
for (String tmp : testList)
{
//use tmp;
}
2、迭代器方式这种遍历方式是最常用的遍历方式,因为书写比较方便,而且不需要考虑数组越界的问题,Effective-Java中推荐使用此种写法遍历。
List testList = new ArrayList();
for (Iterator iterator = testList.iterator(); iterator.hasNext();)
{
//String tmp = iterator.next();
}
3、下标递增或递减循环
List testList = new ArrayList();
for (int i = 0; i < testList.size(); i++;)
{
//String tmp = testList.get(i);
}
以上三种遍历方式是在使用list时最常用的方式,那么这三种方式在遍历的速度已经性能上又有什么区别呢?我们从数据的底层实现上来进行分析。下标递增或者递减循环是最早接触到的遍历方式,会经常出现数组越界的问题。
List底层储存都是使用数组来进行存储的,ArrayList是直接通过数组来进行存储,而LinkedList则是使用数组模拟指针,来实现链表的方式,因此从这里就可以总结出,ArrayList在使用下标的方式循环遍历的时候性能最好,通过下标可以直接取数据,速度最快。而LinkedList因为有一层指针,无法直接取到对应的下标,因此在使用下标遍历时就需要计算对应的下面是哪个元素,从指针的头一步一步的走,所以效率就很低。想到指针就会联想到迭代器,迭代器可以指向下一个元素,而迭代器就是使用指针来实现的,因此LinkedList在使用迭代器遍历时会效率最高,迭代器直接通过LinkedList的指针进行遍历,ArrayList在使用迭代器时,因为要通过ArrayList先生成指针,因此效率就会低于下标方式,而for-each又是在迭代器基础上又进行了封装,因此效率会更低一点,但是会很接近迭代器。
在进行list遍历时,如果是对ArrayList进行遍历,推荐使用下标方式,如果是LinkedList则推荐使用迭代器方式。