十八、Array 类

Array 类

本文为书籍《Java编程的逻辑》1和《剑指Java:核心原理与应用实践》2阅读笔记

数组是存储多个同类型元素的基本数据结构,数组中的元素在内存连续存放,可以通过数组下标直接定位任意元素。数组操作是计算机程序中的常见基本操作。Java中有一个类Arrays,包含一些对数组操作的方法。

4.1 toString

ArraystoString()方法可以方便地输出一个数组的字符串形式,以便查看。它有 9 9 9个重载的方法,包括 8 8 8个基本类型数组和 1 1 1个对象类型数组:

    public static String toString(long[] a)
        
    public static String toString(int[] a)
        
    public static String toString(short[] a)

    public static String toString(char[] a)
        
    public static String toString(byte[] a)

    public static String toString(boolean[] a)

    public static String toString(float[] a)

    public static String toString(double[] a)

    public static String toString(Object[] a)

示例代码如下,如果不使用Arrays.toString方法,直接输出数组自身,不易查看数组内容。

    @Test
    public void testArraysToString() {
        int[] intArray = new int[] { 1, 2, 3, 4 };
        assertTrue("[1, 2, 3, 4]".equals(Arrays.toString(intArray)));
        assertTrue(("[I@" + Integer.toHexString(intArray.hashCode())).equals(intArray.toString()));
    }

4.2 排序

对每种基本类型的数组,Arrays都有sort方法(boolean除外):

    public static void sort(int[] a) {
        DualPivotQuicksort.sort(a, 0, 0, a.length);
    }

    public static void sort(int[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, 0, fromIndex, toIndex);
    }

    public static void sort(long[] a) {
        DualPivotQuicksort.sort(a, 0, 0, a.length);
    }

    public static void sort(long[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, 0, fromIndex, toIndex);
    }

    public static void sort(short[] a) {
        DualPivotQuicksort.sort(a, 0, a.length);
    }

    public static void sort(short[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex);
    }

    public static void sort(char[] a) {
        DualPivotQuicksort.sort(a, 0, a.length);
    }

    public static void sort(char[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex);
    }

    public static void sort(byte[] a) {
        DualPivotQuicksort.sort(a, 0, a.length);
    }

    public static void sort(byte[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex);
    }

    public static void sort(float[] a) {
        DualPivotQuicksort.sort(a, 0, 0, a.length);
    }

    public static void sort(float[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, 0, fromIndex, toIndex);
    }

    public static void sort(double[] a) {
        DualPivotQuicksort.sort(a, 0, 0, a.length);
    }

    public static void sort(double[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, 0, fromIndex, toIndex);
    }

    public static void parallelSort(byte[] a) {
        DualPivotQuicksort.sort(a, 0, a.length);
    }

    public static void parallelSort(byte[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex);
    }

    public static void parallelSort(char[] a) {
        DualPivotQuicksort.sort(a, 0, a.length);
    }

    public static void parallelSort(char[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex);
    }

    public static void parallelSort(short[] a) {
        DualPivotQuicksort.sort(a, 0, a.length);
    }

    public static void parallelSort(short[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, fromIndex, toIndex);
    }

    public static void parallelSort(int[] a) {
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), 0, a.length);
    }

    public static void parallelSort(int[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), fromIndex, toIndex);
    }

    public static void parallelSort(long[] a) {
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), 0, a.length);
    }

    public static void parallelSort(long[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), fromIndex, toIndex);
    }

    public static void parallelSort(float[] a) {
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), 0, a.length);
    }

    public static void parallelSort(float[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), fromIndex, toIndex);
    }

    public static void parallelSort(double[] a) {
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), 0, a.length);
    }

    public static void parallelSort(double[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        DualPivotQuicksort.sort(a, ForkJoinPool.getCommonPoolParallelism(), fromIndex, toIndex);
    }

parallelSort使用并行排序合并(sort-merge)算法,将数组分解为子数组,然后对它们本身进行排序和合并,其中,使用的所有并行任务都由ForkJoin公共池执行。

对于基本类型的数组,Java采用的算法是双枢轴快速排序(DualPivotQuicksort)。这个算法是Java 7引入的,在此之前,Java采用的算法是普通的快速排序。双枢轴快速排序是对快速排序的优化,新算法的实现代码位于类java.util.DualPivotQuicksort中。

    @Test
    public void testArrayPrimitiveTypeSort() {
        int[] intArray = new int[] { 7, 5, 1, 3, 2, 4, 3, 0 };
        Arrays.sort(intArray);
        assertTrue("[0, 1, 2, 3, 3, 4, 5, 7]".equals(Arrays.toString(intArray)));
    }

基本数据类型排序按照从小到大升序排列。

除了基本类型,sort还可以直接接受对象类型,但对象需要实现Comparable接口。

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

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

对于对象类型,Java采用的算法是TimSortTimSort也是在Java 7引入的,在此之前, Java采用的是归并排序。TimSort实际上是对归并排序的一系列优化,TimSort的实现代码位于类java.util.TimSort中。在这些排序算法中,如果数组长度比较小,它们还会采用效率更高的插入排序。为什么基本类型和对象类型的算法不一样呢?排序算法有一个稳定性的概念,所谓稳定性就是对值相同的元素,如果排序前和排序后,算法可以保证它们的相对顺序不变,那算法就是稳定的,否则就是不稳定的。快速排序更快,但不稳定,而归并排序是稳定的。对于基本类型,值相同就是完全相同,所以稳定不稳定没有关系。但对于对象类型,相同只是比较结果一样,它们还是不同的对象,其他实例变量也不见得一样,稳定不稳定可能就很有关系了,所以采用归并排序。

比如下面String数组的例子:

    @Test
    public void testArraysSortObject(){
        String [] stringArray = new String[]{"Break", "abc", "hello", "world"};
        Arrays.sort(stringArray);
        assertTrue("[Break, abc, hello, world]".equals(Arrays.toString(stringArray)));
    }

Break之所以排在最前面,是因为大写字母的ASCII码比小写字母都小。

那如果排序的时候希望忽略大小写呢?sort还有另外两个重载方法,可以接受一个比较器作为参数:

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

方法声明中的T表示泛型,这里表示的是,这个方法可以支持所有对象类型,只要传递这个类型对应的比较器就可以了。Comparator就是比较器,它是一个接口,Java 7中的定义是:

    /**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.<p>
     *
     * The implementor must ensure that {@link Integer#signum
     * signum}{@code (compare(x, y)) == -signum(compare(y, x))} for
     * all {@code x} and {@code y}.  (This implies that {@code
     * compare(x, y)} must throw an exception if and only if {@code
     * compare(y, x)} throws an exception.)<p>
     *
     * The implementor must also ensure that the relation is transitive:
     * {@code ((compare(x, y)>0) && (compare(y, z)>0))} implies
     * {@code compare(x, z)>0}.<p>
     *
     * Finally, the implementor must ensure that {@code compare(x,
     * y)==0} implies that {@code signum(compare(x,
     * z))==signum(compare(y, z))} for all {@code z}.
     *
     * @apiNote
     * It is generally the case, but <i>not</i> strictly required that
     * {@code (compare(x, y)==0) == (x.equals(y))}.  Generally speaking,
     * any comparator that violates this condition should clearly indicate
     * this fact.  The recommended language is "Note: this comparator
     * imposes orderings that are inconsistent with equals."
     *
     * @param o1 the first object to be compared.
     * @param o2 the second object to be compared.
     * @return a negative integer, zero, or a positive integer as the
     *         first argument is less than, equal to, or greater than the
     *         second.
     * @throws NullPointerException if an argument is null and this
     *         comparator does not permit null arguments
     * @throws ClassCastException if the arguments' types prevent them from
     *         being compared by this comparator.
     */
    int compare(T o1, T o2);

    /**
     * Indicates whether some other object is &quot;equal to&quot;
     * this comparator.  This method must obey the general contract of
     * {@link Object#equals(Object)}.  Additionally, this method can
     * return {@code true} <i>only</i> if the specified object is also
     * a comparator and it imposes the same ordering as this
     * comparator.  Thus, {@code comp1.equals(comp2)} implies that
     * {@link Integer#signum signum}{@code (comp1.compare(o1,
     * o2))==signum(comp2.compare(o1, o2))} for every object reference
     * {@code o1} and {@code o2}.<p>
     *
     * Note that it is <i>always</i> safe <i>not</i> to override
     * {@code Object.equals(Object)}.  However, overriding this method may,
     * in some cases, improve performance by allowing programs to determine
     * that two distinct comparators impose the same order.
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} only if the specified object is also
     *          a comparator and it imposes the same ordering as this
     *          comparator.
     * @see Object#equals(Object)
     * @see Object#hashCode()
     */
    boolean equals(Object obj);

最主要的是compare这个方法,它比较两个对象,返回一个表示比较结果的值, − 1 -1 1表示o1小于o2 0 0 0表示o1等于o2 1 1 1表示o1大于o2。排序是通过比较来实现的,sort方法在排序的过程中需要对对象进行比较的时候,就调用比较器的compare方法。

示例如下:

    @Test
    public void testArraysSortComparator(){
        String[] stringArray = new String[] { "Break", "abc", "hello", "world" };
        Arrays.sort(stringArray, String.CASE_INSENSITIVE_ORDER);
        assertTrue("[abc, Break, hello, world]".equals(Arrays.toString(stringArray)));
    }

sort方法默认是从小到大排序,如果希望按照从大到小排序呢?对于对象类型,可以指定一个不同的Comparator,可以用匿名内部类来实现Comparator,比如:

    @Test
    public void testArraysSortComparatorReverse() {
        String[] stringArray = new String[] { "Break", "abc", "hello", "world" };
        Arrays.sort(stringArray, new Comparator<String>() {

            @Override
            public int compare(String o1, String o2) {
                return o2.compareToIgnoreCase(o1);
            }

        });
        assertTrue("[world, hello, Break, abc]".equals(Arrays.toString(stringArray)));
    }

4.3 查找

Arrays包含很多与sort对应的查找方法,可以在已排序的数组中进行二分查找。二分查找既可以针对基本类型数组,也可以针对对象数组,对对象数组,也可以传递Comparator,也可以指定查找范围。函数定义如下:

    public static int binarySearch(long[] a, long key)
        
    public static int binarySearch(long[] a, int fromIndex, int toIndex, long key)
        
    public static int binarySearch(int[] a, int key)
        
    public static int binarySearch(int[] a, int fromIndex, int toIndex, int key)
        
    public static int binarySearch(short[] a, short key)
        
    public static int binarySearch(short[] a, int fromIndex, int toIndex, short key) 

    public static int binarySearch(char[] a, char key)
        
    public static int binarySearch(char[] a, int fromIndex, int toIndex, char key)
        
    public static int binarySearch(byte[] a, byte key)

    public static int binarySearch(byte[] a, int fromIndex, int toIndex, byte key)
        
    public static int binarySearch(double[] a, double key)

    public static int binarySearch(double[] a, int fromIndex, int toIndex, double key)
        
    public static int binarySearch(float[] a, float key)
        
    public static int binarySearch(float[] a, int fromIndex, int toIndex, float key)

    public static int binarySearch(Object[] a, Object key)
        
    public static int binarySearch(Object[] a, int fromIndex, int toIndex, Object key)
        
    public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c)

    public static <T> int binarySearch(T[] a, int fromIndex, int toIndex, T key, Comparator<? super T> c)

如果能找到,binarySearch返回找到的元素索引,如果不能找到,返回一个负数,这个负数等于-(插入点+1)。插入点表示,如果在这个位置插入没找到的元素,可以保持原数组有序。比如,-6 == Arrays.binarySearch(intArray, 4),表示,如果在 5 5 5这个索引位置处插入 4 4 4,可以使数组有序,即数组变为{ -1, 0, 1, 2, 3, 4, 5 }

    @Test
    public void testArraysBinarySearch() {
        int[] intArray = new int[] { -1, 0, 1, 2, 3, 5 };
        System.out.println(Arrays.binarySearch(intArray, 1));
        assertTrue(2 == Arrays.binarySearch(intArray, 1));
        assertTrue(-6 == Arrays.binarySearch(intArray, 4));
    }

需要注意的是,,如果数组中有多个匹配的元素,则返回哪一个是不确定的。

4.4 copyOf 数组复制

数组扩容等操作通常都涉及数组的复制操作,Arrays类的copyOf方法和copyOfRange方法可以满足不同场合的排序,定义如下:

public static int[] copyOf(int[] original, int newLength)
public static <T> T[] copyOfRange(T[] original, int from, int to)

以常用的整型数组为例,进行代码演示,具体代码如下所示:

    @Test
    public void testArraysCopyOf() {
        int[] originIntArray = new int[] { 1, 2, 3, 4, 5, 6 };
        int[] lengthSubTwoIntArray = Arrays.copyOf(originIntArray, originIntArray.length - 2);
        assertTrue("[1, 2, 3, 4]".equals(Arrays.toString(lengthSubTwoIntArray)));
        int[] lengthPlusTwoIntArray = Arrays.copyOf(originIntArray, originIntArray.length + 2);
        assertTrue("[1, 2, 3, 4, 5, 6, 0, 0]".equals(Arrays.toString(lengthPlusTwoIntArray)));
    }

4.5 equals 判断数组的元素是否相等

如果需要比较两个数组的元素是否完全相等,那么可以直接使用Arrays类的equals方法来比较,该方法为重载方法,参数类型支持任意类型的数组。如果是基本数据类型,则直接比较数组的长度和元素值;如果是引用数据类型,则比较数组的长度及每个元素的equals方法。

    @Test
    public void testArraysEquals() {
        assertTrue(Arrays.equals(new String[] { new String("12"), new String("34") }, new String[] { "12", "34" }));
    }

4.5 fill 批量设置

给数组中的每个元素设置一个相同的值。

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

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

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

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

4.6 hashCode 计算数组 hash 值

提供hashCode计算数组hash值。

    public static int hashCode(int[] a) {
        if (a == null) {
            return 0;
        }
        return switch (a.length) {
            case 0 -> 1;
            case 1 -> 31 + a[0];
            default -> ArraysSupport.vectorizedHashCode(a, 0, a.length, 1, ArraysSupport.T_INT);
        };
    }

4.7 ArrayUtils

Apache有一个开源包(http://commons.apache.org/proper/commons-lang/),里面有一个类ArrayUtils(位于包org.apache.commons.lang3),包含了更多的常用数组操作。

  1. add:Copies the given array and adds the given element at the end of the new array;
  2. addAll:Adds all the elements of the given arrays into a new array. The new array contains all of the element of array1 followed by all of the elements array2. When an array is returned, it is always a new array;
  3. addFirst:Copies the given array and adds the given element at the beginning of the new array;
  4. clone:Shallow clones an array returning a typecast result and handling null;
  5. contains:Checks if the object is in the given array;
  6. get:Gets the nTh element of an array or a default value if the index is out of bounds;
  7. getLength:Returns the length of the specified array.
  8. hashCode:Get a hash code for an array handling multi-dimensional arrays correctly.
  9. indexOf:Finds the index of the given object in the array starting at the given index. This method returns INDEX_NOT_FOUND (-1) for a null input array.
  10. indexesOf:Finds the indices of the given value in the array starting at the given index.
  11. insert:Inserts elements into an array at the given index (starting from zero). When an array is returned, it is always a new array.
  12. isArrayIndexValid:Returns whether a given array can safely be accessed at the given index.
  13. isEmpty:Checks if an array of Objects is empty or null.
  14. isNotEmpty:Checks if an array of Objects is not empty and not null.
  15. isSameLength:Checks whether two arrays are the same length, treating null arrays as length 0.
  16. isSameType:Checks whether two arrays are the same type taking into account multidimensional arrays.
  17. isSorted:This method checks whether the provided array is sorted according to natural ordering.
  18. lastIndexOf:Finds the last index of the given value within the array.
  19. newInstance:Creates a new array with the specified component type and length.
  20. nullToEmpty:Defensive programming technique to change a null reference to an empty one.
  21. remove:Removes the element at the specified position from the specified array. All subsequent elements are shifted to the left (subtracts one from their indices).
  22. removeAll:Removes multiple array elements specified by index.
  23. removeAllOccurrences:Removes the occurrences of the specified element from the specified int array.
  24. removeElement:Removes the first occurrence of the specified element from the specified array. All subsequent elements are shifted to the left (subtracts one from their indices). If the array doesn’t contain such an element, no elements are removed from the array.
  25. removeElements:Removes occurrences of specified elements, in specified quantities, from the specified array. All subsequent elements are shifted left. For any element-to-be-removed specified in greater quantities than contained in the original array, no change occurs beyond the removal of the existing matching items.
  26. reverse:Reverses the order of the given array.
  27. setAll:Sets all elements of the specified array, using the provided generator supplier to compute each element.
  28. shift:Shifts the order of the given array.
  29. shuffle:Randomly permutes the elements of the specified array using the Fisher-Yates algorithm.
  30. subarray:Produces a new array containing the elements between the start and end indices.
  31. swap:Swaps two elements in the given array.
  32. toArray:Create a type-safe generic array.
  33. toMap:Converts the given array into a {@link java.util.Map}. Each element of the array must be either a java.util.Map.Entry or an Array, containing at least two elements, where the first element is used as key and the second as value.
  34. toObject:Converts an array of primitive typeto objects.
  35. toPrimitive:Converts an array of object to primitives handlingnull.
  36. toString:Outputs an array as a String, treating null as an empty array.
  37. toStringArray:Returns an array containing the string representation of each element in the argument array.

  1. 马俊昌.Java编程的逻辑[M].北京:机械工业出版社,2018. ↩︎

  2. 尚硅谷教育.剑指Java:核心原理与应用实践[M].北京:电子工业出版社,2023. ↩︎

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值