Java集合---Arrays类源码剖析

一、Arrays.sort()数组排序

Java Arrays中提供了对所有类型的排序。其中主要分为Primitive(8种基本类型)Object两大类。

  基本类型:采用调优的快速排序;

  对象类型:采用改进的归并排序。

 

1、对于基本类型源码分析如下(以int[]为例):

JavaPrimitiveintfloat等原型数据)数组采用快速排序,对Object对象数组采用归并排序。对这一区别,sun<<The Java Tutorial>>中做出的解释如下:

The sort operation uses a slightly optimized merge sort algorithm that is fast and stable:

* Fast: It is guaranteed to run in n log(n) time and runs substantially faster on nearly sorted lists. Empirical tests showed it to be as fast as a highly optimized quicksort. A quicksort is generally considered to be faster than a merge sort but isn't stable and doesn't guarantee n log(n) performance.

* Stable: It doesn't reorder equal elements. This is important if you sort the same list repeatedly on different attributes. If a user of a mail program sorts the inbox by mailing date and then sorts it by sender, the user naturally expects that the now-contiguous list of messages from a given sender will (still) be sorted by mailing date. This is guaranteed only if the second sort was stable.

  也就是说,优化的归并排序既快速(nlog(n))又稳定。

  对于对象的排序,稳定性很重要。比如成绩单,一开始可能是按人员的学号顺序排好了的,现在让我们用成绩排,那么你应该保证,本来张三在李四前面,即使他们成绩相同,张三不能跑到李四的后面去。

  而快速排序是不稳定的,而且最坏情况下的时间复杂度是O(n^2)

  另外,对象数组中保存的只是对象的引用,这样多次移位并不会造成额外的开销,但是,对象数组对比较次数一般比较敏感,有可能对象的比较比单纯数的比较开销大很多。归并排序在这方面比快速排序做得更好,这也是选择它作为对象排序的一个重要原因之一。

  排序优化:实现中快排和归并都采用递归方式,而在递归的底层,也就是待排序的数组长度小于7时,直接使用冒泡排序,而不再递归下去。

  分析:长度为6的数组冒泡排序总比较次数最多也就1+2+3+4+5+6=21次,最好情况下只有6次比较。而快排或归并涉及到递归调用等的开销,其时间效率在n较小时劣势就凸显了,因此这里采用了冒泡排序,这也是对快速排序极重要的优化。

 

  源码中的快速排序,主要做了以下几个方面的优化:

1)当待排序的数组中的元素个数较少时,源码中的阀值为7,采用的是插入排序。尽管插入排序的时间复杂度为0(n^2),但是当数组元素较少时,插入排序优于快速排序,因为这时快速排序的递归操作影响性能。

2)较好的选择了划分元(基准元素)。能够将数组分成大致两个相等的部分,避免出现最坏的情况。例如当数组有序的的情况下,选择第一个元素作为划分元,将使得算法的时间复杂度达到O(n^2).

  源码中选择划分元的方法:

    当数组大小为 size=7  ,取数组中间元素作为划分元。int n=m>>1;(此方法值得借鉴)

    当数组大小 7<size<=40时,取首、中、末三个元素中间大小的元素作为划分元。

    当数组大小 size>40  ,从待排数组中较均匀的选择9个元素,选出一个伪中数做为划分元。

3)根据划分元 v ,形成不变式 v* (<v)* (>v)* v*

  普通的快速排序算法,经过一次划分后,将划分元排到素组较中间的位置,左边的元素小于划分元,右边的元素大于划分元,而没有将与划分元相等的元素放在其附近,这一点,在Arrays.sort()中得到了较大的优化。

  举例:159315416152271520

  因  7<size<=40,所以在156、和20 中选择v = 15 作为划分元。

  经过一次换分后: 151576412022931515. 与划分元相等的元素都移到了素组的两边。

  接下来将与划分元相等的元素移到数组中间来,形成:761515151541202293.

  最后递归对两个区间进行排序[76][41202293].

 

  部分源代码(一)如下:

1 package com.util;

  2

  3publicclass ArraysPrimitive {

  4     private ArraysPrimitive(){}

  5

  6     /**

  7     * 对指定的 int 型数组按数字升序进行排序。

  8      */

  9     publicstaticvoid sort(int[] a) {

 10         sort1(a, 0, a.length);

 11    }

 12    

 13     /**

 14     * 对指定 int 型数组的指定范围按数字升序进行排序。

 15      */

 16     publicstaticvoid sort(int[] a, int fromIndex, int toIndex) {

 17        rangeCheck(a.length, fromIndex,toIndex);

 18         sort1(a, fromIndex, toIndex -fromIndex);

 19    }

 20

 21     privatestaticvoid sort1(int x[], int off, int len) {

 22         /*

 23         * 当待排序的数组中的元素个数小于 7 时,采用插入排序

 24         *

 25         * 尽管插入排序的时间复杂度为O(n^2),但是当数组元素较少时,插入排序优于快速排序,因为这时快速排序的递归操作影响性能。

 26          */

 27         if (len < 7) {

 28             for (int i = off; i <len + off; i++)

 29                 for (int j = i; j > off&& x[j - 1] > x[j]; j--)

 30                     swap(x, j, j - 1);

 31             return;

 32        }

 33         /*

 34         * 当待排序的数组中的元素个数大于或等于7 时,采用快速排序

 35         *

 36         * Choose a partition element, v

 37         * 选取一个划分元,V

 38         *

 39         * 较好的选择了划分元(基准元素)。能够将数组分成大致两个相等的部分,避免出现最坏的情况。例如当数组有序的的情况下,

 40         * 选择第一个元素作为划分元,将使得算法的时间复杂度达到O(n^2).

 41          */

 42         // 当数组大小为size=7,取数组中间元素作为划分元。

 43         int m = off + (len>> 1);

 44         // 当数组大小 7<size<=40时,取首、中、末三个元素中间大小的元素作为划分元。

 45         if (len > 7) {

 46             int l = off;

 47             int n = off + len - 1;

 48             /*

 49             * 当数组大小  size>40 ,从待排数组中较均匀的选择9个元素,

 50             * 选出一个伪中数做为划分元。

 51              */

 52             if (len > 40) {

 53                 int s = len / 8;

 54                 l = med3(x, l, l + s, l + 2 * s);

 55                 m = med3(x, m - s, m, m + s);

 56                 n = med3(x, n - 2 * s, n - s, n);

 57            }

 58             // 取出中间大小的元素的位置。

 59             m = med3(x, l, m, n); // Mid-size, medof 3

 60        }

 61        

 62         //得到划分元V

 63         int v = x[m];

 64        

 65         // EstablishInvariant: v* (<v)* (>v)* v*

 66         int a = off, b = a, c= off + len - 1, d = c;

 67         while (true) {

 68             while (b <= c&& x[b] <= v) {

 69                 if (x[b] == v)

 70                     swap(x, a++, b);

 71                 b++;

 72            }

 73             while (c >= b&& x[c] >= v) {

 74                 if (x[c] == v)

 75                     swap(x, c, d--);

 76                 c--;

 77            }

 78             if (b > c)

 79                 break;

 80             swap(x, b++, c--);

 81        }

 82         // Swap partitionelements back to middle

 83         int s, n = off + len;

 84         s = Math.min(a - off, b - a);

 85         vecswap(x, off, b - s, s);

 86         s = Math.min(d - c, n - d - 1);

 87         vecswap(x, b, n - s, s);

 88         // Recursivelysort non-partition-elements

 89         if ((s = b - a) >1)

 90            sort1(x, off, s);

 91         if ((s = d - c) >1)

 92             sort1(x, n - s, s);

 93    }

 94    

 95     /**

 96     * Swaps x[a] with x[b].

 97      */

 98     privatestaticvoid swap(int x[], int a, int b) {

 99         int t = x[a];

100        x[a] = x[b];

101        x[b] = t;

102   }

103    

104    /**

105     * Swaps x[a.. (a+n-1)] with x[b .. (b+n-1)].

106      */

107    privatestaticvoid vecswap(int x[], int a, int b, int n) {

108    for (int i=0; i<n; i++, a++, b++)

109       swap(x, a, b);

110   }

111    

112    /**

113     * Returnsthe index of the median of the three indexed integers.

114      */

115    privatestaticint med3(int x[], int a, int b, int c) {

116        return (x[a] < x[b] ? (x[b] < x[c] ? b : x[a] < x[c] ? c : a)

117                 : (x[b] > x[c] ? b : x[a]> x[c] ? c : a));

118   }

119

120    /**

121     * Checkthat fromIndex and toIndex are in range, and throw an

122     *appropriate exception if they aren't.

123      */

124    privatestaticvoid rangeCheck(int arrayLen, int fromIndex, int toIndex) {

125        if (fromIndex > toIndex)

126             thrownewIllegalArgumentException("fromIndex(" + fromIndex

127                     + ") >toIndex(" + toIndex + ")");

128        if (fromIndex < 0)

129             thrownewArrayIndexOutOfBoundsException(fromIndex);

130        if (toIndex > arrayLen)

131             thrownewArrayIndexOutOfBoundsException(toIndex);

132   }

133 }

测试代码如下:

1 package com.test;

 2

 3 importcom.util.ArraysPrimitive;

 4

 5publicclass ArraysTest {

 6     publicstaticvoid main(String[]args) {

 7         int [] a={15,93,15,41,6,15,22,7,15,20};

 8        ArraysPrimitive.sort(a);

 9         for(int i=0;i<a.length;i++){

10             System.out.print(a[i]+",");

11       }

12        //结果:6,7,15,15,15,15,20,22,41,93,

13   }

14 }

2、对于Object类型源码分析如下:

  部分源代码(二)如下:

1 package com.util;

  2

  3 importjava.lang.reflect.Array;

  4

  5publicclass ArraysObject {

  6     privatestatic final intINSERTIONSORT_THRESHOLD = 7;

  7

  8     private ArraysObject() {}

  9

 10     publicstaticvoid sort(Object[] a){

 11         //java.lang.Object.clone(),理解深表复制和浅表复制

 12         Object[] aux = (Object[]) a.clone();

 13         mergeSort(aux, a, 0, a.length, 0);

 14    }

 15

 16     publicstaticvoid sort(Object[] a, int fromIndex, int toIndex) {

 17        rangeCheck(a.length, fromIndex,toIndex);

 18         Object[] aux = copyOfRange(a,fromIndex, toIndex);

 19         mergeSort(aux, a, fromIndex, toIndex,-fromIndex);

 20    }

 21

 22     /**

 23     * Src is the source array that starts atindex 0

 24     * Dest is the (possibly larger) arraydestination with a possible offset

 25     * low is the index in dest to startsorting

 26     * high is the end index in dest to endsorting

 27     * off is the offset to generatecorresponding low, high in src

 28      */

 29     privatestaticvoidmergeSort(Object[] src, Object[] dest, int low,

 30             int high, int off) {

 31         int length = high -low;

 32

 33         // Insertion sorton smallest arrays

 34         if (length <INSERTIONSORT_THRESHOLD) {

 35             for (int i = low; i <high; i++)

 36                 for (int j = i; j > low&&

 37                         ((Comparable) dest[j -1]).compareTo(dest[j]) > 0; j--)

 38                     swap(dest, j, j - 1);

 39             return;

 40        }

 41

 42         // Recursivelysort halves of dest into src

 43         int destLow = low;

 44         int destHigh = high;

 45         low += off;

 46         high += off;

 47         /*

 48         * >>>:无符号右移运算符

 49         * expression1 >>> expresion2expression1的各个位向右移expression2

 50         *  指定的位数。右移后左边空出的位数用0来填充。移出右边的位被丢弃。

 51         * 例如:-14>>>2  结果为:1073741820

 52          */

 53         int mid = (low +high) >>> 1;

 54         mergeSort(dest, src, low, mid, -off);

 55         mergeSort(dest, src, mid, high, -off);

 56

 57         // If list isalready sorted, just copy from src to dest. This is an

 58         // optimizationthat results in faster sorts for nearly ordered lists.

 59         if (((Comparable)src[mid - 1]).compareTo(src[mid]) <= 0) {

 60            System.arraycopy(src, low, dest,destLow, length);

 61             return;

 62        }

 63

 64         // Merge sortedhalves (now in src) into dest

 65         for (int i = destLow, p =low, q = mid; i < destHigh; i++) {

 66             if (q >= high ||p < mid

 67                     && ((Comparable)src[p]).compareTo(src[q]) <= 0)

 68                 dest[i] = src[p++];

 69             else

 70                 dest[i] = src[q++];

 71        }

 72    }

 73

 74     /**

 75     * Check that fromIndex and toIndex are inrange, and throw an appropriate

 76     * exception if they aren't.

 77      */

 78     privatestaticvoid rangeCheck(int arrayLen, int fromIndex, int toIndex) {

 79         if (fromIndex >toIndex)

 80             thrownewIllegalArgumentException("fromIndex(" + fromIndex

 81                     + ") >toIndex(" + toIndex + ")");

 82         if (fromIndex < 0)

 83             thrownewArrayIndexOutOfBoundsException(fromIndex);

 84         if (toIndex >arrayLen)

 85             thrownewArrayIndexOutOfBoundsException(toIndex);

 86    }

 87

 88     publicstatic <T> T[]copyOfRange(T[] original, intfrom, int to) {

 89         returncopyOfRange(original, from, to,(Class<T[]>) original.getClass());

 90    }

 91

 92     publicstatic <T, U> T[]copyOfRange(U[] original, intfrom, int to,

 93             Class<? extends T[]>newType) {

 94         int newLength = to - from;

 95         if (newLength < 0)

 96             thrownewIllegalArgumentException(from + " > " + to);

 97         T[] copy = ((Object) newType ==(Object) Object[].class)

 98                 ? (T[]) new Object[newLength]

 99                : (T[])Array.newInstance(newType.getComponentType(), newLength);

100        System.arraycopy(original, from, copy, 0,

101                 Math.min(original.length - from, newLength));

102        return copy;

103   }

104

105    /**

106     * Swapsx[a] with x[b].

107      */

108    privatestaticvoid swap(Object[] x, int a, int b) {

109        Object t = x[a];

110        x[a] = x[b];

111        x[b] = t;

112   }

113 }

测试代码如下:

1 package com.test;

 2

 3 importcom.util.ArraysObject;

 4

 5publicclassArraysObjectSortTest {

 6     publicstaticvoid main(String[]args) {

 7         Student stu1=new Student(1001,100.0F);

 8         Student stu2=new Student(1002,90.0F);

 9         Student stu3=new Student(1003,90.0F);

10        Student stu4=new Student(1004,95.0F);

11        Student[] stus={stu1,stu2,stu3,stu4};

12        //Arrays.sort(stus);

13       ArraysObject.sort(stus);

14        for(int i=0;i<stus.length;i++){

15             System.out.println(stus[i].getId()+" : "+stus[i].getScore());

16       }

17        /* 1002 : 90.0

18         * 1003 :90.0

19         * 1004 :95.0

20         * 1001 :100.0

21          */

22   }

23 }

24class Studentimplements Comparable<Student>{

25    privateint id;  //学号

26    privatefloat score;  //成绩

27    public Student(){}

28    public Student(int id,float score){

29        this.id=id;

30        this.score=score;

31   }

32   @Override

33    publicint compareTo(Student s) {

34        return (int)(this.score-s.getScore());

35   }

36    publicint getId() {

37        return id;

38   }

39    publicvoid setId(int id) {

40        this.id = id;

41   }

42    publicfloat getScore() {

43        return score;

44   }

45    publicvoid setScore(float score) {

46        this.score = score;

47   }

48 }

辅助理解代码:

1 package com.lang;

 2

 3public final class System {

 4     //System 类不能被实例化。

 5     private System() {}

 6     // System 类提供的设施中,有标准输入、标准输出和错误输出流;对外部定义的属性

 7     //和环境变量的访问;加载文件和库的方法;还有快速复制数组的一部分的实用方法。

 8     /**

 9     * src and dest都必须是同类型或者可以进行转换类型的数组.

10     *@param      src      the source array.

11     * @param     srcPos   starting position in thesource array.

12     *@param      dest     the destination array.

13     *@param      destPos  starting position in the destination data.

14     *@param      length   the number of array elements to be copied.

15      */

16    publicstatic native void arraycopy(Object src, int srcPos, Objectdest,

17             int destPos, int length);

18 }

 

 

1 package com.lang.reflect;

 2

 3public final class Array {

 4     private Array() {}

 5    

 6     //创建一个具有指定的组件类型和维度的新数组。

 7     publicstatic ObjectnewInstance(Class<?> componentType, int length)

 8             throws NegativeArraySizeException{

 9         returnnewArray(componentType, length);

10   }

11

12    privatestatic native Object newArray(Class componentType, int length)

13             throws NegativeArraySizeException;

14 }

二、Arrays.asList

慎用ArrayListcontains方法,使用HashSetcontains方法代替

在启动一个应用的时候,发现其中有一处数据加载要数分钟,刚开始以为是需要load的数据比较多的缘故,查了一下数据库有6条左右,但是单独写了一个数据读取的方法,将这6万多条全部读过来,却只需要不到10秒钟,就觉得这里面肯定有问题,于是仔细看其中的逻辑,其中有一段数据去重的逻辑,就是记录中存在某几个字段相同的,就认为是重复数据,就需要将重复数据给过滤掉。这里就用到了一个List来存放这几个字段所组成的主键,如果发现相同的就不处理,代码无非就是下面这样:

 

1 List<string> uniqueKeyList= new ArrayList<string>(); 

2 //...... 

3 if (uniqueKeyList.contains(uniqueKey)){ 

4                    continue

  }

 

根据键去查找是不是已经存在了,来判断是否重复数据。经过分析,这一块耗费了非常多的时候,于是就去查看ArrayListcontains方法的源码,发现其最终会调用他本身的indexOf方法:

 

7publicint indexOf(Objectelem) { 

8    if (elem == null) { 

9       for (int i = 0; i < size; i++) 

10       if (elementData[i]==null

11           return i; 

12   } else

13       for (int i = 0; i < size; i++) 

14       if (elem.equals(elementData[i])) 

15           return i; 

16   } 

17    return -1

18   } 

 

原来在这里他做的是遍历整个list进行查找,最多可能对一个键的查找会达到6万多次,也就是会扫描整个List,验怪会这么慢了。

于是将原来的List替换为Set

 

Set<string> uniqueKeySet= new HashSet<string>(); 

//...... 

if (uniqueKeySet.contains(uniqueKey)) { 

                   continue

}

 

速度一下就上去了,在去重这一块最多花费了一秒钟,为什么HashSet的速度一下就上去了,那是因为其内部使用的是Hashtable,这是HashSetcontains的源码:

 

public boolean contains(Object o) { 

   returnmap.containsKey(o); 

}

 

关于UnsupportedOperationException异常

     在使用Arrays.asList()后调用addremove这些method时出现java.lang.UnsupportedOperationException异常。这是由于Arrays.asList() 返回java.util.Arrays$ArrayList 而不是ArrayListArrays$ArrayListArrayList都是继承AbstractListremoveaddmethodAbstractList中是默认throw UnsupportedOperationException而且不作任何操作。ArrayList override这些method来对list进行操作,但是Arrays$ArrayList没有override remove()add()等,所以throw UnsupportedOperationException

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值