在使用二分法排序的时候,ArrayList和LinkedList的性能出现了极大的差异,由此记录,以供警醒
总所周知,在java的集合中,ArrayList底层就是数组的形式,新增数据的时候需要如果数组大小超过默认值就需要扩容,由此产生的时间比较多,
而LinkedList使用链表的形式,在向末尾位置添加数据时不存在扩容的说法,由此运行速度比较快
所以linkedlist适合插入和删除数据,ArrayList由于底层原因,查找数组小标更容易找到,这也是仅仅表现在添加或删除末尾数据上的比较
而且如果将ArrayList的容器大小确定,再与LinkedList比较,此时ArrayList的添加速度都会比LinkedList快
于是我有了下面的测试想法,导入到数据模拟真实环境均是实体对象
1 @Data //可以省略getter和setter 2 public class Article implements Comparable<Article> 3 , Comparator<Article> 4 { 5 6 private int id; 7 8 private int userId; 9 10 private double value; 11 12 private String userName; 13 14 public Article(){} 15 16 public Article(int id,int userId,double value){ 17 this(); 18 this.id = id; 19 this.userId = userId; 20 this.value = value; 21 } 22 23 public Article(int id,int userId,double value,String userName){ 24 this(); 25 this.id = id; 26 this.userId = userId; 27 this.value = value; 28 this.userName = userName; 29 } 30 31 @Override 32 public int compareTo(Article o) { 33 if (o.getId() > this.getId()) 34 return -1; 35 else if (o.getId() < this.getId()) 36 return 1; 37 else 38 return 0; 39 } 40 41 @Override 42 public int compare(Article o1, Article o2) { 43 if (o1.getId() < o2.getId()) 44 return -1; 45 else if (o1.getId() > o2.getId()) 46 return 1; 47 else 48 return 0; 49 } 50 }
1 public Map get(int number){ 2 List array = new ArrayList<Article>(100000); 3 List list = new LinkedList(); 4 int slow = 0; 5 long arrayTime = 0; 6 long linkedTime = 0; 7 for(int i = 0;i<number;i++) { 8 int nextInt = new Random().nextInt(number); 9 Article article = new Article(new Random().nextInt(number), 10 new Random().nextInt(number), 11 Math.round(new Random().nextDouble()) * 5l, 12 "user" + ('a' + nextInt)); 13 long begin = System.nanoTime(); 14 list.add(article); 15 long middle = System.nanoTime(); 16 array.add(article); 17 long end = System.nanoTime(); 18 arrayTime += end - middle; 19 linkedTime += middle - begin; 20 } 21 System.out.println("linkedlist总耗时:"+linkedTime+",arraylist总耗时:"+arrayTime); 22 return new HashMap(){{put("arraylist",array);put("linkedlist",list);}}; 23 }
最后开始写排序的的二分法,分别用不同的两种list来实现
1 public List binaryArrayListSorted(ArrayList<Article> articles){ 2 ArrayList<Article> list = new ArrayList<>(); 3 articles.forEach(new Consumer<Article>() { 4 @Override 5 public void accept(Article article) { 6 if(list.size()==0) 7 list.add(article); 8 else if(list.get(0).compareTo(article)>=0){ 9 list.add(0,article); 10 }else if(list.get(list.size()-1).compareTo(article)<=0){ 11 list.add(article); 12 }else { 13 int begin = 0; 14 int end = list.size()-1; 15 int middle = end/2; 16 while(begin<middle&&middle<end){ 17 if(article.compareTo(list.get(middle))>0){ 18 begin = middle; 19 }else if(article.compareTo(list.get(middle))<0){ 20 end = middle; 21 }else{ 22 end = middle; 23 break; 24 } 25 middle = (begin+end)/2; 26 } 27 28 long time = System.nanoTime(); 29 list.add(article); 30 if(list.size()%500==0) { 31 System.out.println(System.nanoTime() - time); 32 } 33 } 34 } 35 }); 36 return list; 37 }
1 public LinkedList<T> binaryLinkedListSort(LinkedList<T> list){ 2 Iterator<T> iterator = list.iterator(); 3 LinkedList<T> linkedList = new LinkedList<>(); 4 while(iterator.hasNext()){ 5 T next = iterator.next(); 6 int begin = 0; 7 int end = linkedList.size()-1; 8 int middle = end + begin >> 1; 9 if(linkedList.size()==0||next.compareTo(linkedList.get(begin))<=0){ 10 linkedList.add(begin,next); 11 }else if(next.compareTo(linkedList.get(end))>=0){ 12 linkedList.add(next); 13 }else { 14 while ((end > middle) && (middle > begin)){ 15 if(next.compareTo(linkedList.get(middle))>0){ 16 begin = middle; 17 }else if(next.compareTo(linkedList.get(middle))<0){ 18 end = middle; 19 }else{ 20 end = middle; 21 break; 22 } 23 middle = end + begin >> 1; 24 } 25 26 long time = System.nanoTime(); 27 linkedList.add(next); 28 if(linkedList.size()%500==0) { 29 System.out.println(System.nanoTime() - time); 30 } 31 } 32 } 33 return linkedList; 34 }
使用main方法去运行比较
1 public class CompareSorted<T extends Comparable 2 & Comparator<? super T> 3 > { 4 //因为用到了Arrays.sort(Compartor)方法,所以主类的泛型要继承Compartor 5 public static void main(String[] args) { 6 CompareSorted<Article> acs = new CompareSorted<Article>(); 7 Map map = acs.get(10000); 8 LinkedList<Article> list = (LinkedList) map.get("linkedlist"); 9 ArrayList<Article> array = (ArrayList) map.get("arraylist"); 10 Article[] arr = array.toArray(new Article[array.size()]); 11 long begin1 = System.currentTimeMillis(); 12 acs.help(array); 13 System.out.println("ArrayList二分法排序用时:"+(System.currentTimeMillis()-begin1)+"ms"); 14 long begin2 = System.currentTimeMillis(); 15 acs.binarySort(list); 16 System.out.println("LinkedList二分法排序用时:"+(System.currentTimeMillis()-begin2)+"ms"); 17 long begin = System.currentTimeMillis(); 18 //这里array排序后不能再被后面使用,因为已经改变了原来的数据顺序 19 array.sort(array.get(0)); 20 System.out.println("集合的普通排序用时:"+(System.currentTimeMillis()-begin)+"ms"); 21 long begin3 = System.currentTimeMillis(); 22 Arrays.sort(arr); 23 System.out.println("Arrays的排序用时:"+(System.currentTimeMillis()-begin3)+"ms"); 24 long begin4 = System.currentTimeMillis(); 25 Collections.sort(list); 26 System.out.println("Collections的排序用时:"+(System.currentTimeMillis()-begin4)+"ms"); 27 } 28 }
分别跑不同的数据大小获得下面的结果(在数据大于5w的时候就不在输出LinkedList了)
插入10000条数据linkedlist总耗时:952800ns,arraylist总耗时:3923500ns
ArrayList二分法排序用时:18ms
LinkedList二分法排序用时:998ms
集合的普通排序用时:10ms
Arrays的排序用时:8ms
Collections的排序用时:7ms
插入50000条数据linkedlist总耗时:5983000ns,arraylist总耗时:2946500ns
ArrayList二分法排序用时:161ms
LinkedList二分法排序用时:82140ms
集合的普通排序用时:30ms
Arrays的排序用时:28ms
Collections的排序用时:31ms
插入100000条数据linkedlist总耗时:5876600ns,arraylist总耗时:4923100ns
ArrayList二分法排序用时:601ms
集合的普通排序用时:58ms
Arrays的排序用时:109ms
Collections的排序用时:67ms
插入500000条数据linkedlist总耗时:20399900ns,arraylist总耗时:17101100ns
ArrayList二分法排序用时:15659ms
集合的普通排序用时:215ms
Arrays的排序用时:308ms
Collections的排序用时:204ms
插入1000000条数据linkedlist总耗时:36756400ns,arraylist总耗时:33962300ns
ArrayList二分法排序用时:61723ms
集合的普通排序用时:419ms
Arrays的排序用时:559ms
Collections的排序用时:489ms
总结:
1.在末尾添加数据,ArrayList正常情况都会比LinkedList快,而我们说的慢往往是因为ArrayList开辟容器大小时花费的时间更多
(ArrayList添加元素直接在数组上新增,linkedlist链表添加需要执行两步,分别是指定最后一个元素的末位置是新元素的head,链表最后一个为新元素,这里可以参考 链表和数组结构)
2.在指定位置添加数据(就是测试代码中的二分法排序,ArrayList是找到指定的位置后,使用System.arraycopy()方法复制数组前后的数据,然后让拼接成新的组合数组,而LinkedList虽然插入快速,
但是光查询指定位置就十分耗费时间,所以在排序5w条数据时,LinkedList整整花费了82s的时间,如果数据更多,上千万或上亿级数据使用二分法是非常不适用的,
于是我们后面会记录jdk1.8的排序方式:双枢快排)