java集合类,ArrayList,HashSet ,LinkedLlist

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<list.size();i++) 这种循环方式。LinkedList中的get方法是按照顺序从列表的一端开始检查,直到另外一端。对LinkedList而言,利用Collections.reverse方法对列表进行顺序反转时,LinkedList性能要好些。 

空间复杂度

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

1

2

3

4

5

List<String> testList = new ArrayList<String>();

for (String tmp : testList)

{  

  //use tmp;

}

这种遍历方式是最常用的遍历方式,因为书写比较方便,而且不需要考虑数组越界的问题,Effective-Java中推荐使用此种写法遍历。

2、迭代器方式

1

2

3

4

5

List<String> testList = new ArrayList<String>();

for (Iterator<String> iterator = testList.iterator(); iterator.hasNext();)

{

    //String tmp = iterator.next();

}

3、下标递增或递减循环

1

2

3

4

5

List<String> testList = new ArrayList<String>();

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则推荐使用迭代器方式。


 

猜你喜欢

转载自blog.csdn.net/calvin4cn/article/details/84450978