`
shukuiyan
  • 浏览: 414005 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

转-Java Arrays.sort源代码解析

    博客分类:
  • java
阅读更多

转自 http://www.cnblogs.com/gw811/archive/2012/10/04/2711746.html 

 

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

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

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

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

  Java对Primitive(int,float等原型数据)数组采用快速排序,对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()中得到了较大的优化。

  举例:15、93、15、41、6、15、22、7、15、20

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

  经过一次换分后: 15、15、7、6、41、20、22、93、15、15. 与划分元相等的元素都移到了素组的两边。

  接下来将与划分元相等的元素移到数组中间来,形成:7、6、15、15、15、15、41、20、22、93.

 

  最后递归对两个区间进行排序[7、6]和[41、20、22、93].

 

部分源代码(一)如下:

package com.util;

public class ArraysPrimitive {
    private ArraysPrimitive() {}

    /**
     * 对指定的 int 型数组按数字升序进行排序。
     */
    public static void sort(int[] a) {
        sort1(a, 0, a.length);
    }
    
    /**
     * 对指定 int 型数组的指定范围按数字升序进行排序。
     */
    public static void sort(int[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        sort1(a, fromIndex, toIndex - fromIndex);
    }

    private static void sort1(int x[], int off, int len) {
        /*
         * 当待排序的数组中的元素个数小于 7 时,采用插入排序 。
         * 
         * 尽管插入排序的时间复杂度为O(n^2),但是当数组元素较少时, 插入排序优于快速排序,因为这时快速排序的递归操作影响性能。
         */
        if (len < 7) {
            for (int i = off; i < len + off; i++)
                for (int j = i; j > off && x[j - 1] > x[j]; j--)
                    swap(x, j, j - 1);
            return;
        }
        /*
         * 当待排序的数组中的元素个数大于 或等于7 时,采用快速排序 。
         * 
         * Choose a partition element, v
         * 选取一个划分元,V
         * 
         * 较好的选择了划分元(基准元素)。能够将数组分成大致两个相等的部分,避免出现最坏的情况。例如当数组有序的的情况下,
         * 选择第一个元素作为划分元,将使得算法的时间复杂度达到O(n^2).
         */
        // 当数组大小为size=7时 ,取数组中间元素作为划分元。
        int m = off + (len >> 1);
        // 当数组大小 7<size<=40时,取首、中、末 三个元素中间大小的元素作为划分元。
        if (len > 7) {
            int l = off;
            int n = off + len - 1;
            /*
             * 当数组大小  size>40 时 ,从待排数组中较均匀的选择9个元素,
             * 选出一个伪中数做为划分元。
             */
            if (len > 40) {
                int s = len / 8;
                l = med3(x, l, l + s, l + 2 * s);
                m = med3(x, m - s, m, m + s);
                n = med3(x, n - 2 * s, n - s, n);
            }
            // 取出中间大小的元素的位置。
            m = med3(x, l, m, n); // Mid-size, med of 3
        }
        
        //得到划分元V
        int v = x[m];
        
        // Establish Invariant: v* (<v)* (>v)* v*
        int a = off, b = a, c = off + len - 1, d = c;
        while (true) {
            while (b <= c && x[b] <= v) {
                if (x[b] == v)
                    swap(x, a++, b);
                b++;
            }
            while (c >= b && x[c] >= v) {
                if (x[c] == v)
                    swap(x, c, d--);
                c--;
            }
            if (b > c)
                break;
            swap(x, b++, c--);
        }
        // Swap partition elements back to middle
        int s, n = off + len;
        s = Math.min(a - off, b - a);
        vecswap(x, off, b - s, s);
        s = Math.min(d - c, n - d - 1);
        vecswap(x, b, n - s, s);
        // Recursively sort non-partition-elements
        if ((s = b - a) > 1)
            sort1(x, off, s);
        if ((s = d - c) > 1)
            sort1(x, n - s, s);
    }
    
    /**
     * Swaps x[a] with x[b].
     */
    private static void swap(int x[], int a, int b) {
        int t = x[a];
        x[a] = x[b];
        x[b] = t;
    }
    
    /**
     * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)].
     */
    private static void vecswap(int x[], int a, int b, int n) {
    for (int i=0; i<n; i++, a++, b++)
        swap(x, a, b);
    }
    
    /**
     * Returns the index of the median of the three indexed integers.
     */
    private static int med3(int x[], int a, int b, int c) {
        return (x[a] < x[b] ? (x[b] < x[c] ? b : x[a] < x[c] ? c : a)
                : (x[b] > x[c] ? b : x[a] > x[c] ? c : a));
    }

    /**
     * Check that fromIndex and toIndex are in range, and throw an
     * appropriate exception if they aren't.
     */
    private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) {
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex
                    + ") > toIndex(" + toIndex + ")");
        if (fromIndex < 0)
            throw new ArrayIndexOutOfBoundsException(fromIndex);
        if (toIndex > arrayLen)
            throw new ArrayIndexOutOfBoundsException(toIndex);
    }
}

 

测试代码如下

package com.test;

import com.util.ArraysPrimitive;

public class ArraysTest {
    public static void main(String[] args) {
        int [] a={15,93,15,41,6,15,22,7,15,20};
        ArraysPrimitive.sort(a);
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+",");
        }
        //结果:6,7,15,15,15,15,20,22,41,93,
    }
}

 


 

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

 

  部分源代码(二)如下:

 

package com.util;

import java.lang.reflect.Array;

public class ArraysObject {
    private static final int INSERTIONSORT_THRESHOLD = 7;

    private ArraysObject() {}

    public static void sort(Object[] a) {
        //java.lang.Object.clone(),理解深表复制和浅表复制
        Object[] aux = (Object[]) a.clone();
        mergeSort(aux, a, 0, a.length, 0);
    }

    public static void sort(Object[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        Object[] aux = copyOfRange(a, fromIndex, toIndex);
        mergeSort(aux, a, fromIndex, toIndex, -fromIndex);
    }

    /**
     * Src is the source array that starts at index 0 
     * Dest is the (possibly larger) array destination with a possible offset 
     * low is the index in dest to start sorting 
     * high is the end index in dest to end sorting 
     * off is the offset to generate corresponding low, high in src
     */
    private static void mergeSort(Object[] src, Object[] dest, int low,
            int high, int off) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i = low; i < high; i++)
                for (int j = i; j > low && 
                        ((Comparable) dest[j - 1]).compareTo(dest[j]) > 0; j--)
                    swap(dest, j, j - 1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow = low;
        int destHigh = high;
        low += off;
        high += off;
        /*
         *  >>>:无符号右移运算符
         *  expression1 >>> expresion2:expression1的各个位向右移expression2
         *  指定的位数。右移后左边空出的位数用0来填充。移出右边的位被丢弃。
         *  例如:-14>>>2;  结果为:1073741820
         */
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);

        // If list is already sorted, just copy from src to dest. This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable) src[mid - 1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for (int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid
                    && ((Comparable) src[p]).compareTo(src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

    /**
     * Check that fromIndex and toIndex are in range, and throw an appropriate
     * exception if they aren't.
     */
    private static void rangeCheck(int arrayLen, int fromIndex, int toIndex) {
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex
                    + ") > toIndex(" + toIndex + ")");
        if (fromIndex < 0)
            throw new ArrayIndexOutOfBoundsException(fromIndex);
        if (toIndex > arrayLen)
            throw new ArrayIndexOutOfBoundsException(toIndex);
    }

    public static <T> T[] copyOfRange(T[] original, int from, int to) {
        return copyOfRange(original, from, to, (Class<T[]>) original.getClass());
    }

    public static <T, U> T[] copyOfRange(U[] original, int from, int to,
            Class<? extends T[]> newType) {
        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
        T[] copy = ((Object) newType == (Object) Object[].class)
                ? (T[]) new Object[newLength]
                : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
        return copy;
    }

    /**
     * Swaps x[a] with x[b].
     */
    private static void swap(Object[] x, int a, int b) {
        Object t = x[a];
        x[a] = x[b];
        x[b] = t;
    }
}

 测试代码如下:

package com.test;

import com.util.ArraysObject;

public class ArraysObjectSortTest {
    public static void main(String[] args) {
        Student stu1=new Student(1001,100.0F);
        Student stu2=new Student(1002,90.0F);
        Student stu3=new Student(1003,90.0F);
        Student stu4=new Student(1004,95.0F);
        Student[] stus={stu1,stu2,stu3,stu4};
        //Arrays.sort(stus);
        ArraysObject.sort(stus);
        for(int i=0;i<stus.length;i++){
            System.out.println(stus[i].getId()+" : "+stus[i].getScore());
        }
        /* 1002 : 90.0
         * 1003 : 90.0
         * 1004 : 95.0
         * 1001 : 100.0
         */
    }
}
class Student implements Comparable<Student>{
    private int id;  //学号
    private float score;  //成绩
    public Student(){}
    public Student(int id,float score){
        this.id=id;
        this.score=score;
    }
    @Override
    public int compareTo(Student s) {
        return (int)(this.score-s.getScore());
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public float getScore() {
        return score;
    }
    public void setScore(float score) {
        this.score = score;
    }
}

 

 辅助理解代码:

 

package com.lang;

public final class System {
    //System 类不能被实例化。 
    private System() {}
    //在 System 类提供的设施中,有标准输入、标准输出和错误输出流;对外部定义的属性
    //和环境变量的访问;加载文件和库的方法;还有快速复制数组的一部分的实用方法。
    /**
     * src and dest都必须是同类型或者可以进行转换类型的数组.
     * @param      src      the source array.
     * @param      srcPos   starting position in the source array.
     * @param      dest     the destination array.
     * @param      destPos  starting position in the destination data.
     * @param      length   the number of array elements to be copied.
     */
    public static native void arraycopy(Object src, int srcPos, Object dest,
            int destPos, int length);
}

package com.lang.reflect;

public final class Array {
    private Array() {}
    
    //创建一个具有指定的组件类型和维度的新数组。
    public static Object newInstance(Class<?> componentType, int length)
            throws NegativeArraySizeException {
        return newArray(componentType, length);
    }

    private static native Object newArray(Class componentType, int length)
            throws NegativeArraySizeException;
}

 

 

分享到:
评论

相关推荐

    面试知识点总结--JAVA测试题.pdf

    Java 源代码被编译成字节码(.class 文件),这种字节码是平台无关的,可以在任何安装了JVM的系统上运行。JVM会加载字节码文件,进行校验、解析,然后在目标平台上解释执行,实现了“一次编写,到处运行”的特性。 ...

    javajdk源码-java8-source-code-learning:Java8JDK源代码分析

    在本文中,我们将探讨Java 8中的主要新特性,并通过源代码解析来理解这些特性的实现原理。 1. **Lambda表达式**: Java 8引入了Lambda表达式,它提供了一种简洁的编写匿名函数的方式。Lambda允许我们用一种更简洁...

    28个java常用的工具类

    1. **`java.util.Arrays`**: 这个类提供了对数组进行排序、查找、复制等操作的方法,如`Arrays.sort()`用于排序数组,`Arrays.asList()`将数组转换为列表。 2. **`java.util.Collections`**: 类似于`Arrays`,但...

    jdk5.0源代码下载

    5. **launcher**: 这部分可能包含了Java应用启动器的源代码,它是启动Java应用程序的关键组件,负责解析命令行参数,加载JVM并执行主类。 6. **sunw**: 这个目录可能与Sun Microsystems(现在已被Oracle收购)的...

    2021-2022计算机二级等级考试试题及答案No.18173.docx

    1. **问题描述**:给出一段Java代码,请分析最终变量`y`的值是什么? ```java int x = 1; int y = 2; if (x % 2 == 0) { y++; } else { y--; } System.out.println("y=" + y); ``` - A. 1 - B. 2 - C. ...

    java数组排序源代码

    ### Java数组排序源代码分析与理解 #### 一、概览 本文将深入解析一个包含多种排序算法(冒泡排序、选择排序、插入排序、希尔排序)的Java程序,并通过具体的代码实现来帮助读者理解这些排序算法的工作原理及其实现...

    JAVA5-6新特性列表.doc

    6. **Compiler API**:JSR 199提供了Java编译API,允许程序在运行时动态编译Java源代码。 7. **通用的Annotations支持**:增强了注解的元注解,如@Retention和@Target,可以定义注解的生命周期和应用范围。 总的来...

    Java习题

    - 字节码是由Java编译器从Java源代码文件(`.java`)编译得到的。编译过程产生的字节码文件扩展名为`.class`。 6. **包含Java字节码的文件类型** - Java字节码文件以`.class`为扩展名。例如:`HelloWorld.class`...

    java-抽取数组中的数字元素并排序-代码

    最后使用`Arrays.sort()`方法对`int`数组进行排序。 #### 四、输出结果 最后一步是输出排序后的数字数组: ```java System.out.println(Arrays.toString(result)); ``` 这将打印出排序后的数字数组。 #### 五、...

    各语言排序算法源代码

    本文将详细解析标题“各语言排序算法源代码”所涵盖的多种编程语言实现的排序算法,包括Java、PHP和Python等。 首先,让我们了解排序算法的基本类型。常见的排序算法有: 1. 冒泡排序(Bubble Sort):通过重复...

    JAVA阶段模拟试题

    分析下面的Java源程序,编译后的运行结果是()。(选一项) ```java import java,util.*; public class Test{ public static void main(String[] args){ int[] numbers = new int[]{2,1,3}; Arrays.sort...

    java工具类

    1. **`java.util.Arrays`**:这个类提供了处理数组的各种方法,如排序(`sort()`)、查找(`binarySearch()`)和复制(`copyOf()`)等。 2. **`java.util.Collections`**:它是针对集合框架的工具类,提供了对List...

    java基本概念试卷

    使用文本编辑器编写Java源代码。 2. 保存文件,文件名与类名保持一致。 3. 在命令行编译源代码:`javac FileName.java`。 4. 运行编译后的程序:`java FileName`。 - **Java代码书写规范**: - 遵循统一的命名...

    Java 实现拓扑排序算法(源代码)

    ### Java 实现拓扑排序算法(源代码) #### 拓扑排序算法介绍 拓扑排序(Topological Sorting)是一种针对有向无环图(DAG,Directed Acyclic Graph)的特殊排序方法。其基本思想是对于有向图中的每条边 (u, v),...

    2021-2022计算机二级等级考试试题及答案No.3891.docx

    **知识点**: Java 中的 `Arrays.sort()` 方法可以用来对数组进行排序,默认情况下为升序排列。 - **选项分析**: - A. `2 7 8 12 35`: 排序后数字按照从小到大的顺序排列,因此这是正确答案。 - B. `12 35 8 7 2`:...

    30个java工具类

    Java工具类是程序员在开发过程中经常使用的类库,它们提供了各种通用的功能,使得代码更加简洁、高效。在Java中,工具类通常是静态方法的集合,用于处理常见的编程任务,如字符串操作、数学计算、日期时间处理等。...

    Java常用工具类

    Java常用工具类是Java开发中不可或缺的一部分,它们提供了一系列便捷的方法,帮助开发者高效地处理各种常见任务。在Java中,工具类通常包含在各种库和框架中,如Java的`java.util`包、Apache Commons、Guava等。下面...

    Java 使用克鲁斯卡尔求最小生成树(源代码)

    ### Java 使用克鲁斯卡尔求最小生成树(源代码) #### 知识点解析 **克鲁斯卡尔算法概述** 克鲁斯卡尔算法是一种著名的贪心算法,它用于解决无向加权图中的最小生成树问题。所谓最小生成树,是指在一个连通的无向...

    JAVA 一道编程题目

    #### 核心代码解析 ```java Collections.sort(list, String.CASE_INSENSITIVE_ORDER); ``` 这里的 `Collections.sort()` 方法接受两个参数: 1. **第一个参数**:`list` 是需要被排序的列表。 2. **第二个参数**...

    java 基础知识

    1. **Java编译器**:将Java源代码编译成字节码。 2. **Java虚拟机(JVM)**:解释执行字节码。 3. **Java API**:提供标准的Java类库或Java接口库,帮助开发者更轻松地进行开发工作。 #### 二、Java API详解 Java ...

Global site tag (gtag.js) - Google Analytics