[重学Java基础][Java内置工具类][Part.1] Arrays工具类


style: candy

[重学Java基础][Java内置工具类][Part.1] Arrays工具类

简介

Arrays位于java.util包下,是一个工具类

全限定路径

java.util.Arrays

提供了一些操作数组的方法 用于简化代码

代码示例

基于Java 9

asList 方法

public static List asList(T… a)

由方法签名可知 此方法是一个泛型方法
功能是把可变参数T… a 传入的可变数组序列转为集合类

要点

  • (1)该方法不适用于基本数据类型(byte,short,int,long,float,double,boolean)

  • (2)该方法将数组与列表链接起来,当更新其中之一时,另一个自动更新

  • (3)不支持add和remove方法


  • 实例
  转换为Integer类型的List
  List<Integer> integerList=Arrays.asList(1,2,3);

  转换为String类型的List
  List<String> stringList = Arrays.asList("a", "b", "c");

  转换为TestObject(自定义类)类型的List
  List<TestObject> objectList=Arrays.asList(to1,to2);

从源码看其返回的是一个ArrayList类

 public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

但是 这个ArrayList 并不是

java.util.ArrayList

而是

java.util.Arrays.ArrayList

是一个Arrays方法的内部类
此Arrays.ArrayList的内部设置的元素集合 private final E[] a
不可变的 所以Arrays.asList()方法返回的这个集合是不可变的
并未提供add,remove方法
调用add,remove方法会抛出异常

  private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;
		
		构造方法
		ArrayList(E[] array) {
   		a = Objects.requireNonNull(array);
		}
     ……
    }

asList方法新建了一个Arrays.ArrayList对象 调用了其构造方法 传入了一个数组对象
众所周知 java的对象传参就是传递引用
所以传入的是外部对象的引用
则外部对象改变时 此列表list的内容也会改变

如果传入一个基本类型数组 则会被认为只有传入了一个数组对象的引用
而不是其数组内部的所有成员

例如

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

List list = Arrays.asList(data);

System.out.println("列表中的元素数量是:" + list.size());

输出为

列表中的元素数量是:1

不要使用基本类型数组 可以使用包装类型数组


sort方法

sort()排序方法 传入对象进行升序排列
默认使用字典序
方法的重载非常多 用于适配所有基本类型和引用类型
这个排序运用的是优化的快速排序算法TimSort算法 速度很快
所以如果要进行排序 建议都用此方法

比较典型的就是以下几种

默认排序
sort(Object[] a):void
默认排序 指定排序范围
sort(Object[] a, int fromIndex, int toIndex):void
使用指定的比较器规则排序
sort(T[] a, Comparator<? super T> c):void
使用指定的比较器规则排序 且指定排序范围
sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c):void

源码

public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }

对象类型的比较分成两种 一种是Java6及之前的排序算法legacyMergeSort
一种是Java7之后引入的更严谨 更快速的排序算法TimSort算法
如果非要使用legacyMergeSort JDK运行参数必须配置

-Djava.util.Arrays.useLegacyMergeSort=`true‘

  • 实例
    默认排序
 Integer[] ints = {2, 5, 17, 3, 2, 99, 45, 0};

        Arrays.sort(ints);

        for (Integer anInt : ints) {
            System.out.print(anInt+" ");
        }

结果

0 2 2 3 5 17 45 99 

默认排序 指定排序起始位置

       Integer[] ints2 = {2, 5, 17, 3, 2, 99, 45, 0};
        Arrays.sort(ints2,0,3);

        for (Integer anInt : ints2) {
            System.out.print(anInt+" ");
        }

结果 只有前三个进行了排序

2 5 17 3 2 99 45 0 

使用指定的比较器排序

逆序排列

        Integer[] ints3 = {2, 5, 17, 3, 2, 99, 45, 0};
        Arrays.sort(ints3,(x,y)->y.compareTo(x));

        for (Integer anInt : ints3) {
            System.out.print(anInt+" ");
        }

结果

99 45 17 5 3 2 2 0 

binarySearch方法

binarySearch()二分搜索方法 查找数组中关键字的下标
使用二分法查找,数组必须有序

要点
1、如果找到关键字,则返回值为关键字在数组中的位置索引,且索引从0开始
2、如果没有找到关键字,返回值为负的插入点值,所谓插入点值就是第一个比关键字大的元素在数组中的位置索引,而且这个位置索引从1开始
3、由于返回值和元素叙述有关 所以调用binarySearch()方法前要先调用sort方法对数组进行排序,否则得出的返回值不定

此方法的重载非常多 用于适配所有基本类型和引用类型

典型

Arrays.binarySearch(Object[] a,Object key):int
Arrays.binarySearch(Object[] a, int fromIndex, int toIndex,Object key):int

源码核心方法

private static int binarySearch0(Object[] a, int fromIndex, int toIndex,
                                     Object key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            @SuppressWarnings("rawtypes")
            Comparable midVal = (Comparable)a[mid];
            @SuppressWarnings("unchecked")
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; //找到目标
        }
        return -(low + 1);  //未找到目标
    }

典型的二分搜索算法

  • 实例

binarySearch(Object[] a, Object key)

a: 要搜索的数组

key:要搜索的值

如果key在数组中,则返回搜索值的索引;否则返回-1或负的插入点值。
(从上面源码上可知返回的就是-(low+1))
插入点是索引键将要插入数组的那一点,即第一个大于该键的元素的索引。

要点:

[1] 搜索值不是数组元素,且在数组范围内,从1开始计数,得“ - 插入点索引值”;

[2] 搜索值是数组元素,从0开始计数,得搜索值的索引值;

[3] 搜索值不是数组元素,且大于数组内元素,索引值为 – (length + 1);

[4] 搜索值不是数组元素,且小于数组内元素,索引值为 – 1。

        待排序数组
        Integer[] array ={1,3,4,5,8,9};

        排序
        Arrays.sort(array);

        int position_6 = Arrays.binarySearch(array,6);

        int position_4 = Arrays.binarySearch(array,4);

        int position_0 = Arrays.binarySearch(array,0);

        int position_10 = Arrays.binarySearch(array,10);

        System.out.println("position_6 = "+ position_6 +", position_4 = " + position_4 + ", position_0 = " + position_0 +", position_10 = "+ position_10);

输出

position_6 = -5, position_4 = 2, position_0 = -1, position_10 = -7

第一个查找目标6 原数组中无此元素 所以返回负的插入点值 6在此数组中比5大比8小
排在5后面位置 8前面位置 从1开始计索引序数为5 又是原数组中无的元素 所以最终返回-5

第二个查找目标4 原数组中有此元素 所以返回此元素在数组中的下标索引序数 为2

第三个查找目标0 原数组中无此元素 所以返回负的插入点值 0比此数组中所有元素都小
所以插入点应该在最前面 不在范围索引序数从1开始 返回-1

第三个查找目标10 原数组中无此元素 所以返回负的插入点值 10比此数组中所有元素都大
所以插入点应该在最后面 不在范围索引序数从1开始 最后面则加上数组长度length 最终返回
-(length+1) 即-7

** binarySearch(Object[], int fromIndex, int toIndex, Object key)**

a:要搜索的数组

fromIndex:指定范围的开始处索引(包含)

toIndex:指定范围的结束处索引(不包含)

key:要搜索的值

如果要搜索的元素key在指定的范围内,则返回搜索值的索引;否则返回-1或“-”(插入点)。

要点:

[1] 该搜索键在范围内,但不是数组元素,由1开始计数,得“ - 插入点索引值”;

[2] 该搜索键在范围内,且是数组元素,由0开始计数,得搜索值的索引值;

[3] 该搜索键不在范围内,且小于范围(数组)内元素,返回–(fromIndex + 1);

[4] 该搜索键不在范围内,且大于范围(数组)内元素,返回 –(toIndex + 1)。


Integer[] arr ={1,3,4,5,8,9};
Arrays.sort(arr);
int position_6 = Arrays.binarySearch(arr,1, 4, 6);

int position_4 = Arrays.binarySearch(arr,1, 4, 4);

int position_2 = Arrays.binarySearch(arr,1, 4 ,2);

int position_10 = Arrays.binarySearch(arr,1, 3, 10);

int position_0 = Arrays.binarySearch(arr,1, 3, 0);

System.out.println("position_4 = "+ position_4 +", position_4 = " + position_4 +", position_2 = " + position_2 +", position_10 = "+ position_10 +
 ", position_0 = " + position_0);

运行结果

position_6 = -5, position_4 = 2, position_2 = -2, position_10 = -4, position_0 = -2

第一个查找目标6 查找数组子段为 1~4 实际包含{3,4,5}三个元素
这个数组段中无此元素 所以返回负的插入点值 在原数组中查找
6在原数组中比5大比8小 插入点的索引序数为5 所以返回-5

第二个查找目标4 查找数组子段为 1~4 实际包含{3,4,5}三个元素 子段中有此元素 所以返回此元素在数组中的下标索引序数 为2

第三个查找目标2 查找数组子段为 1~4 实际包含{3,4,5}三个元素 子段中无此元素 所以返回负的插入点值 序数从1开始 2在原数组中的插入点 1和3之间 返回-2

第四个查找目标10 查找数组子段为 1~3 实际包含{3,4}两个元素 子段中无此元素 所以返回负的插入点值 10也不在原数组中 所以插入点为子段尾部 序数从1开始 返回-4

第五个查找目标0 查找数组子段为 1~3 实际包含{3,4}两个元素 子段中无此元素 所以返回负的插入点值 0也不在原数组中 所以插入点为子段首部 序数从1开始 返回-2


toString方法 deepToString方法

toString()数组转换为String方法
deepToString()深度数组转换为String方法 递归的把所有元素中是数组的内容转为字符串输出
此方法的重载较多 用于适配所有基本类型和引用类型

示例

	Integer[] arrayTest={6,1,9,2,5,7,6,10,6,12};
        //数组toString
        System.out.println(Arrays.toString(arrayTest));

结果

[6, 1, 9, 2, 5, 7, 6, 10, 6, 12]

多维数组

 Integer[][] stuGrades={{1,3,5,7,9},{2,4,6,8},{1,5,10}};
        //二维数组toString
        System.out.println(Arrays.toString(stuGrades));

结果

[[Ljava.lang.Integer;@4d1b0d2a, [Ljava.lang.Integer;@954b04f, [Ljava.lang.Integer;@149494d8]

所以多维数组必须用deepToString()

 Integer[][] stuGrades={{1,3,5,7,9},{2,4,6,8},{1,5,10}};
        //二维数组deepToString
        System.out.println(Arrays.deepToString(stuGrades));

结果

[[1, 3, 5, 7, 9], [2, 4, 6, 8], [1, 5, 10]]

对象数组

 DeepClazz deepClazz=new DeepClazz(1,"a");
 DeepClazz deepClazz2=new DeepClazz(2,"b");
 DeepClazz[] deepClazzes={deepClazz,deepClazz2};
 System.out.println(Arrays.deepToString(deepClazzes));

结果

[DeepClazz{a=1, string='a'}, DeepClazz{a=2, string='b'}]

注意 对象ToString的正确输出结果需要正确重写对象的ToString()方法


copyOf方法

返回一个增大了且包含有原数组的所有内容的新数组方法
其实就是数组扩容方法

典型方法

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

要点
1.original为原数组 返回一个新的数组
2.newLength一般大于原数组大小值 为扩容后新数组的大小
3.若newLength小于原数组大小值 则新数组内容为原数组对应长度的内容
4.扩容空间以null填充

源码

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

读源码 核心是调用了 System.arraycopy进行了内容复制

 System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));

复制的新数组长度为newLength

实例


       Integer[] arrayTest={6,1,9,2,5,7,6,10,6,12};

        Integer[] arrayTest2=Arrays.copyOf(arrayTest,11);
        Integer[] arrayTest3=Arrays.copyOf(arrayTest,4);

        System.out.println(Arrays.toString(arrayTest));
        System.out.println(Arrays.toString(arrayTest2));
        System.out.println(Arrays.toString(arrayTest3));

结果

[6, 1, 9, 2, 5, 7, 6, 10, 6, 12]
[6, 1, 9, 2, 5, 7, 6, 10, 6, 12, null] 扩容一位 填充null
[6, 1, 9, 2] 原数组前4位

除了copyOf方法外 还有一个类似的指定复制范围的copyOfRange方法
指定从原数组复制内容的起始结束位置

public static T[] copyOfRange(T[] original, int from, int to)

    Integer[] arrayTest3=Arrays.copyOfRange(arrayTest,1,6);

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

结果

[1, 9, 2, 5, 7]


hashCode和deepHashCode方法

返回参数散列码的方法

public static int hashCode(Object a[])

public static int deepHashCode(Object a[])

源码

    public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }

	public static int deepHashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a) {
        int elementHash = 0;
        if (element instanceof Object[])
            elementHash = deepHashCode((Object[]) element);
        else if (element instanceof byte[])
            elementHash = hashCode((byte[]) element);
        else if (element instanceof short[])
            elementHash = hashCode((short[]) element);
        else if (element instanceof int[])
            elementHash = hashCode((int[]) element);
        else if (element instanceof long[])
            elementHash = hashCode((long[]) element);
        else if (element instanceof char[])
            elementHash = hashCode((char[]) element);
        else if (element instanceof float[])
            elementHash = hashCode((float[]) element);
        else if (element instanceof double[])
            elementHash = hashCode((double[]) element);
        else if (element instanceof boolean[])
            elementHash = hashCode((boolean[]) element);
        else if (element != null)
            elementHash = element.hashCode();

        result = 31 * result + elementHash;
    }

    return result;
}

HashCode()方法的源码还是比较简单的 所有元素的hashcode取值之和 (所以这要求对象正确的重写hashCode()方法)

deepHashCode()方法就略复杂一些 当element为 基本数据类型数组时,elementHash使用 Arrays.hashCode(Object a[])计算。当element为 非数组时,elementHash使用 element.hashCode()计算 element为子类时 使用Arrays.deephashCode递归计算


equals和deepEquals方法

  • euqlas()方法 比较两个参数是否值相等

重载比较多
典型

public static boolean equals(Object[] a, Object[] a2)

比较入参 对象数组Object[] a, Object[] a2是否值相等

源码

    public static boolean equals(Object[] a, Object[] 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 (!Objects.equals(a[i], a2[i]))
                return false;
        }

        return true;
    }

源码中经过一系列简单判断后 还是循环调用equals方法比较每个元素是否值相等

要点
1.要比较的对象 必须正确重写equals方法

  • deepequals方法 比较两个参数是否深度值相等

public static boolean deepEquals(Object[] a1, Object[] a2)

    public static boolean deepEquals(Object[] a1, Object[] a2) {
        if (a1 == a2)
            return true;
        if (a1 == null || a2==null)
            return false;
        int length = a1.length;
        if (a2.length != length)
            return false;

        for (int i = 0; i < length; i++) {
            Object e1 = a1[i];
            Object e2 = a2[i];

            if (e1 == e2)
                continue;
            if (e1 == null)
                return false;

            // Figure out whether the two elements are equal
            boolean eq = deepEquals0(e1, e2);

            if (!eq)
                return false;
        }
        return true;
    }

	static boolean deepEquals0(Object e1, Object e2) {
    assert e1 != null;
    boolean eq;
    if (e1 instanceof Object[] && e2 instanceof Object[])
        eq = deepEquals ((Object[]) e1, (Object[]) e2);
    else if (e1 instanceof byte[] && e2 instanceof byte[])
        eq = equals((byte[]) e1, (byte[]) e2);
    else if (e1 instanceof short[] && e2 instanceof short[])
        eq = equals((short[]) e1, (short[]) e2);
    else if (e1 instanceof int[] && e2 instanceof int[])
        eq = equals((int[]) e1, (int[]) e2);
    else if (e1 instanceof long[] && e2 instanceof long[])
        eq = equals((long[]) e1, (long[]) e2);
    else if (e1 instanceof char[] && e2 instanceof char[])
        eq = equals((char[]) e1, (char[]) e2);
    else if (e1 instanceof float[] && e2 instanceof float[])
        eq = equals((float[]) e1, (float[]) e2);
    else if (e1 instanceof double[] && e2 instanceof double[])
        eq = equals((double[]) e1, (double[]) e2);
    else if (e1 instanceof boolean[] && e2 instanceof boolean[])
        eq = equals((boolean[]) e1, (boolean[]) e2);
    else
  eq = e1.equals(e2);
    return eq;
}

deepEquals()方法比equals()复杂一些 当Object[]为 基本数据类型数组时,使用 Arrays.equals(Object a[])计算。当element为 非数组时,Object[]为对象子类时 使用Arrays.deepequals()递归计算

要点
1.要比较的对象 必须正确重写equals方法

实例

       DeepClazz deepClazz1=new DeepClazz(1,"a");
       DeepClazz deepClazz2=new DeepClazz(2,"b");

       DeepClazz[] deepClazzes1={deepClazz1,deepClazz2};
       DeepClazz[] deepClazzes2={deepClazz2,deepClazz1};
       System.out.println(Arrays.equals(deepClazzes1,deepClazzes2););
       System.out.println(Arrays.deepEquals(deepClazzes1,deepClazzes2));

fill方法

全填充方法

public static void fill(int[] a, int val)

将数组(int[] a所有元素 填充为val的值

源码

public static void fill(Object[] a, Object val) {
        for (int i = 0, len = a.length; i < len; i++)
            a[i] = val;
    }

fill的源码 真是分外简单
从这也能看出 fill是直接操作原数组的 并不创建新数组

实例

       int[] arrays=new int[10];

       Arrays.fill(arrays,5);
       System.out.println(Arrays.toString(arrays));

       Arrays.fill(arrays,5);
       System.out.println(Arrays.toString(arrays));

       指定起始位置填充
       Arrays.fill(arrays,0,4,6);
       System.out.println(Arrays.toString(arrays));

结果

[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
[6, 6, 6, 6, 5, 5, 5, 5, 5, 5]

spliterator方法

返回一个数组对应的分割迭代器

public static Spliterator spliterator(T[] array)

分割迭代器是一个较复杂特性
具体可看这篇文章
JAVA8 stream 中Spliterator的使用


并行方法

Arrays工具还提供了三种并行方法

并行二元迭代方法

Arrays.parallelPrefix(T[] array, BinaryOperator op);

对数组T[] array 的所有元素 都进行BinaryOperator op传入的操作

       Integer[] arrayTest={6,1,9,2,5,7,6,10,6,12};

        Integer[] arrayPP1 = Arrays.copyOf(arrayTest, arrayTest.length);
        System.out.println( Arrays.toString(arrayPP1));
        //二元迭代,对原数组内容进行二元操作
        Arrays.parallelPrefix(arrayPP1,(x,y)->x*y);
        System.out.println( Arrays.toString(arrayPP1));

        Integer[] arrayPP2 = Arrays.copyOf(arrayTest, arrayTest.length);
        //在指定下标范围内,对原数组内容进行二元操作,下标含头不含尾
        Arrays.parallelPrefix(arrayPP2,0,5,(x,y)->x*y);
        System.out.println( Arrays.toString(arrayPP2));

结果

[6, 1, 9, 2, 5, 7, 6, 10, 6, 12]
[6, 6, 54, 108, 540, 3780, 22680, 226800, 1360800, 16329600]
[6, 6, 54, 108, 540, 7, 6, 10, 6, 12]

并行全部按表达式运算方法

Arrays.parallelSetAll(T[] array, IntFunction<? extends T> generator));

对数组T[] array的所有元素 都进行函数式接口IntFunction<? extends T> generator 传入的操作

  Integer[] arrayPSA = Arrays.copyOf(arrayTest, arrayTest.length);
        //对原有数组对每个元素重新赋值,下面例子是 下标*5 然后赋到数组对应元素
        Arrays.parallelSetAll(arrayPSA,a->a<<2);
        System.out.println(Arrays.toString(arrayPSA));

结果

[0, 4, 8, 12, 16, 20, 24, 28, 32, 36]

并行排序方法

Arrays.parallelSort((T[] a);

和排序方法操作一样 只不过底层采用了并行流 可以更快速的处理大数据量的排序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值