java中的Arrays这个工具类你真的会用吗

2.构造方法

​ 因为是一个工具类,所以它的构造方法定义为私有的,且所有的实现方法都是静态方法。也就是说这个类不能被实例化,通俗的讲,就是不能new。只能通过类名来直接调用方法(反射除外)。这样做的目的是强化该类不可实列化的能力,突出该类作为工具类的根本职能。源码如下:

// Suppresses default constructor, ensuring non-instantiability.

    private Arrays() {}

3.常用方法的解析

3.1快速插入集合元素的方法asList(T... a):

基本使用:

  /**

    * 数组转化为集合

    */

    @Test

    public void toArrayTest(){

        List<Integer> list = Arrays.asList(2,4,5,6,6);

        for (Integer integer : list) {

            System.out.print(integer+" ");

        }

    }

输出结果:

2 4 5 6 6

看一下源码:

@SafeVarargs

@SuppressWarnings("varargs")

public static <T> List<T> asList(T... a) {

        return new ArrayList<>(a);

    }

// ArrayList的构造方法和属性

      private final E[] a;

        ArrayList(E[] array) {

            a = Objects.requireNonNull(array);

        }

​ 这个方法的实现比较简单,就是调用ArrayList的构造方法,并且参数是一个数组,也就是将我们要构造的数传入到ArrayList的构造方法中去,进行实例化。

3.2.二分查找的方法

Arrays类中的二分查找八种基本类型都有涉及,但都是方法的重载。其实现原理都是一样,这里以int类型为例,进行说明。

基本使用:

    @Test

    public void binarySearchTest(){

        int[] arrays = {1,4,6,7,9,3};

        // 查找元素为7的下标值

        int result = Arrays.binarySearch(arrays,7);

        System.out.println(result);

    }

结果:

3

这个方法主要涉及的一下三个方法:

// 我们常用的方法

public static int binarySearch(int[] a, int key) {

        return binarySearch0(a, 0, a.length, key);

    }

/*

  参数说明如下: a  待查找的数组

    fromIndex    查找的开始位置

    toIndex      查找的结束位置

    key          查找的目标值

*/

public static int binarySearch(int[] a, int fromIndex, int toIndex,

                                  int key) {

        // 进行异常检查

        rangeCheck(a.length, fromIndex, toIndex);

        return binarySearch0(a, fromIndex, toIndex, key);

    }

    // Like public version, but without range checks.

    private static int binarySearch0(int[] a, int fromIndex, int toIndex,

                                    int key) {

        int low = fromIndex;

        int high = toIndex - 1;

        while (low <= high) {


            // 找出查找范围的中间值

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

            int midVal = a[mid];


            // 进行比较

            if (midVal < key)

                low = mid + 1;

            else if (midVal > key)

                high = mid - 1;

            else

                return mid; // key found

        }

        return -(low + 1);  // key not found.

    }

当然实现的核心方法还是上述私有方法binarySearch0()这个方法,实现的逻辑也不复杂。

第一步就是声明两个变量存储查找区域的开始和结束。

第二步 循环,比较,不断的缩小比较的范围,直到找到数组中的值和目标值相同,返回下标,如果没有找到就返回一个负数也就是下面的这l两行代码:

return mid; // key found

return -(low + 1);  // key not found.

我认为:这个二分法实现的亮点就在于求中间值的移位运算:

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

有人就纳闷了,为什么还要使用移位运算,除法不行吗?主要还是为了性能考量。因为移位运算占两个机器周期,而乘除法占四个运算周期,所以移位运算的速度肯定比乘除法的运算速度快很多,计算量小了可能区别不大,但是计算量很大,就区别很明显了。

3.3 数组的拷贝

    @Test

    public void testCopyArrange(){

          // 原数组

        int [] srcArray = {11,2,244,5,6,54};

        // 拷贝原数组长度为3的部分

        int[] descArray = Arrays.copyOf(srcArray,3);


        System.out.println(Arrays.toString(descArray));

    }

输出结果:

[11, 2, 244]

源码分析:

/* 参数说明:

  original  原数组

  newLength  拷贝的数组长度

*/

public static int[] copyOf(int[] original, int newLength) {

        // 声明一个新数组的长度,存储拷贝后的数组

        int[] copy = new int[newLength];

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

                        Math.min(original.length, newLength));

        return copy;

    }

public static native void arraycopy(Object src,  int  srcPos,

                                        Object dest, int destPos,

                                        int length);

分析: 主要还是调用了本地的方法arraycopy完成数组的指定长度拷贝,可以看到源码并没有对数组的长度进行检查,主要是arraycopy()这个方法时使了Math.min()方法,保证了你声明的长度在一个安全的范围之内,如果你拷贝的长度超出了数组的长度,就默认拷贝整个数组。至于native修饰的方法的使用,可以看看这里。

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

                        Math.min(original.length, newLength));

当然如果需要拷贝数组指定的区间 ,可以使用Arrays的copyOfRange(int[] original, int from, int to) 实现原理和arraycopy()方法的原理类似:

    @Test

    public void testCopy(){

        int [] srcArray = {11,2,244,5,6,54};

        // 拷贝指定范围的数组

        int[] descArray = Arrays.copyOfRange(srcArray,0,3);

        System.out.println(Arrays.toString(descArray));

    }

输出结果:

[11, 2, 244]

注: copyOfRange(int[] original, int from, int to)中的参数to是不包含在拷贝的结果中的,上述的例子,就只能拷贝到索引为2的元素,不包含索引为3的元素,这点需要注意。

3.4 equals方法

主要重写了Object类的equals方法,用来比较两个数组内容是否相等,也就是他们中的元素是否相等。

基本用法:

  @Test

    public void equalTest(){

        int[] array ={1,2,3,4};

        int[] result ={1,2,3,4};

        System.out.println(Arrays.equals(array,result));

        System.out.println(array == result);

    }

结果:

true

false

看源码之前,有必要讲一下重写了equals方法之后,两个对象比较的是值,也就是他们的内容,这点非常的重要。重写equals方法的注意事项可以移步这里。

源码如下:

public static boolean equals(int[] a, int[] a2) {

      // 基于地址的比较

    if (a==a2)

            return true;

        if (a==null || a2==null)

            return false;

        int length = a.length;

        //  基于长度的比较

        if (a2.length != length)

            return false;


        // 比较每个元素是否相等

        for (int i=0; i<length; i++)

            if (a[i] != a2[i])

                return false;

        return true;

    }

源码说明如下:

源码判断了四次,分别是首地址比较,是否为空,以及长度的比较,最后对于数组的各个元素进行比较。

有必要说明下第一个判断,也就是首地址的比较。当我们声明一个数组变量时,这个变量就代表数组的首地址,看下面这个代码:

    @Test

    public void equalTest(){

        int[] array ={1,2,3,4};

        System.out.println(array);


    }

结果:

[I@4f2410ac    // [代表数组 I代表整数 @分隔符 后边内存地址十六进制

​ 这表示的是一个地址。还是因为在声明一个数组时,会在堆里面创建一块内存区域,但是这块内存区域相对于堆来说可能很小,不好找。为了方便查找,所以将数组内存中的首地址表示出来。虚拟机将地址传给变量名array。这也是引用类型,传的是地址,也就是理解成array指向内存地址(类似于家庭的地址),每次运行可能地址都不一样,因为虚拟机开辟的内存空间可能不一样。

理解了这个,那么a==a2就好理解了,如果两个数组内存地址都相同,那么两个数组的肯定是相等的。

还有我认为程序写的比较好的地方就是源码中对数组每个元素的比较,也就是下面这段代码;

  for (int i=0; i<length; i++)

            if (a[i] != a2[i])

                return false;

        return true;

使用a[i] != a2[i] 作为判断条件,就可以减少比较次数,提高了性能。试想一下如果这里是相等的比较,那每次都要遍历整个数组,如果数据量大了,无疑在性能上会慢很多。又一次感叹到源码的魅力。

3.5 排序相关的方法sort()和parallelSort()

Arrays 这个类中主要涉及了两种类型的排序方法串行 sort()和并行parallelSort()这两个方法,当然对象的排序和基本类型的排序也不太一样。这里还是以int[]类型的为例。进行说明。

首先比较两个方法的性能:


    public final int UPPER_LIMIT = 0xffffff;

    final int ROUNDS = 10;

    final int INCREMENT = 5;

    final int INIT_SIZE = 1000;

    @Test

    public void sortAndParallelSortTest(){


        // 构造不同容量的集合

        for (int capacity = INIT_SIZE; capacity < UPPER_LIMIT ; capacity*= INCREMENT) {

            ArrayList<Integer> list = new ArrayList<>(capacity);

            for (int j = 0; j < capacity; j++) {

                list.add((int) (Math.random()*capacity));

            }


            double avgTimeOfParallelSort = 0;

            double avgTimeOfSort = 0;

            for (int j = 0; j <= ROUNDS ; j++) {

                // 每次排序都打乱顺序

                Collections.shuffle(list);

                Integer[] arr1 = list.toArray(new Integer[capacity]);

                Integer[] arr2 = arr1.clone();


                avgTimeOfParallelSort += counter(arr1,true);

                avgTimeOfSort += counter(arr2, false);

            }

            // 输出结果

            output(capacity,avgTimeOfParallelSort/ROUNDS,avgTimeOfSort/ROUNDS);

        }

    }

    private void output(int capacity, double v, double v1) {

        System.out.println("=======================测试排序的时间=========");

        System.out.println("Capacity"+capacity);

        System.out.println("ParallelSort"+v);

        System.out.println("Sort"+v1);

        System.out.println("比较快的排序是:"+(v < v1 ? "ParallelSort":"Sort"));

    }

    // 计算消耗的时间

    private double counter(Integer[] arr1, boolean b) {

        long begin,end;

        begin = System.nanoTime();

        if(b){

            Arrays.parallelSort(arr1);

        }else{

            Arrays.parallelSort(arr1);

        }

        end = System.nanoTime();

        return BigDecimal.valueOf(end-begin,9).doubleValue();

    }

部分的测试的结果:

=======================测试排序的时间=========

Capacity1000

ParallelSort6.284099999999999E-4

Sort5.599599999999999E-4

比较快的排序是:Sort

=======================测试排序的时间=========

Capacity5000

ParallelSort0.00163599

Sort0.0018313699999999995

比较快的排序是:ParallelSort

可以看到在数据量比较小的情况下,使用sort()方法更快,一旦过了一个阈值,就是ParallelSort()这个方法性能好。这个阈值是多少呢。

我们先看一下parallelSort的源码:

public static void parallelSort(int[] a) {

        int n = a.length, p, g;

        if (n <= MIN_ARRAY_SORT_GRAN ||

            (p = ForkJoinPool.getCommonPoolParallelism()) == 1)

            DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);

        else

            new ArraysParallelSortHelpers.FJInt.Sorter

                (null, a, new int[n], 0, n, 0,

                ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?

                MIN_ARRAY_SORT_GRAN : g).invoke();

    }

可以看到当数组的长度小于MIN_ARRAY_SORT_GRAN或者p = ForkJoinPool.getCommonPoolParallelism()) == 1 (在单线程下)的时候,调用sort()排序的底层实现的DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);Arrays的开头定义的常量如下:

private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;    // 这个值是8192

对比两者,也就是在数组的长度比较大或者是多线程的情况下,优先考虑并行排序,否则使用串行排序。

两个排序的核心思想:

sort()方法的核心还是快排和优化后的归并排序, 快速排序主要是对哪些基本类型数据(int,short,long等)排序, 而合并排序用于对对象类型进行排序。

parallelSort()它使用并行排序-合并排序算法。它将数组分成子数组,这些子数组本身先进行排序然后合并。

由于并行排序和串行排序的底层比较复杂,且篇幅有限,想要详细了解底层实现的话,可以移步到串行排序和并行排序

3.6 toString方法

基本用法:

@Test

    public void toStringTest(){

        int[] array = {1,3,2,5};

        System.out.println(Arrays.toString(array));

    }

结果:

[1, 3, 2, 5]

源码分析如下:

public static String toString(int[] a) {

        // 1.判断数组的大小

        if (a == null)

            return "null";

        int iMax = a.length - 1;

        if (iMax == -1)

            return "[]";


      // 2.使用StringBuilder进行追加

        StringBuilder b = new StringBuilder();

        b.append('[');

        for (int i = 0; ; i++) {

            b.append(a[i]);

            if (i == iMax)

                return b.append(']').toString();

            b.append(", ");

        }

    }

深圳网站建设www.sz886.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335