专栏首页BAT的乌托邦【小家java】聊聊Java中的java.util.Arrays类和java.util.Collections工具类

【小家java】聊聊Java中的java.util.Arrays类和java.util.Collections工具类


java.util.Arrays类能方便的操作数组,它所有的方法都是静态的。Java1.2为我们提供的。其中Java5和Java8都提供了更多增强方法。 Java有个命名习惯或者说是规范,后面加s的都是工具类,比如Arrays、Collections、Executors等等

备注:本博文基于JDK8讲解

有很多开发了很多年的人,只使用过它的asList方法去快速构建一个List,但其实它是非常强大的,可以很大程度上简化我们操作数组的方式。

Arrays的方法分类介绍

先看几张截图分类

有了这个分类,现在从上到家进行讲解(会进行归类):

asList:快速构建一个list

这个可能不需要我讲解,其实大家都会了。我这里贴出它的实现

 public static <T> List<T> asList(T... var0) {
        return new Arrays.ArrayList(var0);
    }
 private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {

它是自己内部实现的一个ArrayList,使用起来个ArrayList差不多。但是需要注意:注意:注意,它的长度不可变,不能调用remove方法,比如下面就报错:

public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        list.remove(1); //java.lang.UnsupportedOperationException
    }

备注:此处不能传null,否则包空指针异常

binarySearch:二分法查找,数组必须有序

使用二进制搜索算法来搜索指定的 int 型数组,以获得指定的值。

binarySearch()方法提供了多种重载形式,用于满足各种类型数组的查找需要,binarySearch()有两种参数类型。

必须在进行此调用之前对数组进行排序(sort 方法)。如果没有对数组进行排序,则结果是不明确的。如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。

public static void main(String[] args) {
        int a[] = new int[]{1, 3, 4, 6, 8, 9};
        int x1 = Arrays.binarySearch(a, 5);
        int x2 = Arrays.binarySearch(a, 4);
        int x3 = Arrays.binarySearch(a, 0);
        int x4 = Arrays.binarySearch(a, 1);
        System.out.println(x1); //-4 不存在时从1开始(因为是有序的  所有有角标)
        System.out.println(x2); //2
        System.out.println(x3); //-1 不存在时从1开始哦  所以值是-1 不是-0
        System.out.println(x4); //0 存在时 从0开始
    }

因为有这么多的限制,所以数组的查找我们其实用得还是比较少的

compare和compareUnsigned

备注:此两个方法是JDK9提供的方法。JDK里没有哦,所以本文不介绍此方法

copyOf和copyOfRange 复制、截取数组

这个在复制数组和截取数组的时候非常有用,推荐使用:

 public static void main(String[] args) {
        Integer[] arrayTest = {6, 1, 9, 2, 5, 7, 6, 10, 6, 12};
        //复制出新的数组,复制长度由 newLength 决定,长度可大于被复制数组的长度 也可以小于
        Integer[] copyArray1 = Arrays.copyOf(arrayTest, 2); //长度小于
        Integer[] copyArray11 = Arrays.copyOf(arrayTest, 15); //长度大于
        System.out.println(Arrays.toString(copyArray1)); //[6, 1]
        System.out.println(Arrays.toString(copyArray11)); //[6, 1, 9, 2, 5, 7, 6, 10, 6, 12, null, null, null, null, null]
        //复制指定下标范围内的值,含头不含尾(这是通用规则)
        Integer[] copyArray2 = Arrays.copyOfRange(arrayTest, 2, 7); //截取部分
        System.out.println(Arrays.toString(copyArray2));
    }
equals和deepEquals:比较数组内容是否相等

equals:比较一位数组内容 deepEquals:比较二维数组内容

  public static void main(String[] args) {
        Integer[] 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}};
        Integer[] equals1 = Arrays.copyOf(arrayTest, arrayTest.length); //完整拷贝
        //比较一维数组内容是否相等
        System.out.println(Arrays.equals(equals1, arrayTest)); //true

        Integer[][] equals2 = Arrays.copyOf(stuGrades, stuGrades.length); 
        //比较二维数组内容是否相等
        System.out.println(Arrays.deepEquals(stuGrades, equals2)); //true
    }
toString和deepToString
public static void main(String[] args) {
        Integer[] arrayTest = {6, 1, 9, 2, 5, 7, 6, 10, 6, 12};

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

基本同上,例子略

fill:将一个数组全部置为 val ,或在下标范围内将数组置为 val
  public static void main(String[] args) {
        Integer[] arrayTest = {6, 1, 9, 2, 5, 7, 6, 10, 6, 12};

        //Integer[] arrayTest = new Integer[5];
        //将一个数组置为 val(5)
        Arrays.fill(arrayTest,5);
        System.out.println(Arrays.toString(arrayTest)); //[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
        //将一个数组指定范围内置为 val(10)  含头不含尾
        Arrays.fill(arrayTest,2,3,10); //通过角标 可以控制可以批量换值
        System.out.println(Arrays.toString(arrayTest)); //[5, 5, 10, 5, 5, 5, 5, 5, 5, 5]

    }
setAll和parallelSetAll:JDK有歧义的实现方式

这个有点类似于Stream里的Map,但是JDK的实现有bug。 setAll的语义,作者的元思想肯定是希望处理each element,但是我们看看源码:

public static <T> void setAll(T[] array, IntFunction<? extends T> generator) {
        Objects.requireNonNull(generator);
        for (int i = 0; i < array.length; i++)
            array[i] = generator.apply(i);
    }

generator.apply(i);它竟然用的是i,而不是array[i],所以此处我们使用的时候一定要万分注意,回调给我们的是角标,而不是值,而不是值

 public static void main(String[] args) {
        Integer[] arrayTest = {6, 1, 9, 2, 5, 7, 6, 10, 6, 12};

        Integer[] setAllArr = Arrays.copyOf(arrayTest, arrayTest.length);
        //一个数组全部做表达式操作
        System.out.println(Arrays.toString(setAllArr)); //[6, 1, 9, 2, 5, 7, 6, 10, 6, 12]
        //一定要注意 此处我们需要用setAllArr[i]才是我们想要的结果
        Arrays.setAll(setAllArr, i -> setAllArr[i] * 3);
        //Arrays.setAll(setAllArr, i -> i * 3);
        System.out.println(Arrays.toString(setAllArr)); //[18, 3, 27, 6, 15, 21, 18, 30, 18, 36]
    }

看到这种使用方式,很逆天有没有。真不知道是JDK的设计者脑子进水了,还是进水了,因为处理角标没任何意义,至少我是不太同意这种设计的。

一次同样的parallelSetAll也是采用的角标,他们的区别只是一个串行,一个并行。

stream 这个就不解释了
parallelPrefix:二元迭代,对原数组内容进行二元操作

这个解释起来比较麻烦,直接先看一个例子吧

 public static void main(String[] args) {
        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)); //[6, 1, 9, 2, 5, 7, 6, 10, 6, 12]

        //二元迭代,对原数组内容进行二元操作
        Arrays.parallelPrefix(arrayPP1, (x, y) -> x * y);
        System.out.println(Arrays.toString(arrayPP1)); //[6, 6, 54, 108, 540, 3780, 22680, 226800, 1360800, 16329600]

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

从效果中我们可以看出来,这个就是拿前面的结果,和当前元素做运算。这个运算规则,由我们自己来定义。

使用场景:这个在一些数学运算中,会比较好用

sort和parallelSort:算法精华

排序一直以来效率是很依赖于算法的,所以我们抽查一个源码看看它的精妙之处:

static void sort(byte[] a, int left, int right) {
    // Use counting sort on large arrays
    if (right - left > COUNTING_SORT_THRESHOLD_FOR_BYTE) {
        int[] count = new int[NUM_BYTE_VALUES];

        for (int i = left - 1; ++i <= right;
            count[a[i] - Byte.MIN_VALUE]++
        );
        for (int i = NUM_BYTE_VALUES, k = right + 1; k > left; ) {
            while (count[--i] == 0);
            byte value = (byte) (i + Byte.MIN_VALUE);
            int s = count[i];

            do {
                a[--k] = value;
            } while (--s > 0);
        }
    } else { // Use insertion sort on small arrays
        for (int i = left, j = i; i < right; j = ++i) {
            byte ai = a[i + 1];
            while (ai < a[j]) {
                a[j + 1] = a[j];
                if (j-- == left) {
                    break;
                }
            }
            a[j + 1] = ai;
        }
    }
}

如果大于域值那么就使用计数排序法,否则就使用插入排序。

parallelSort是java8中新出的一种排序API,这是一种并行排序,Arrays.parallelSort使用了Java7的Fork/Join框架使排序任务可以在线程池中的多个线程中进行,Fork/Join实现了一种任务窃取算法,一个闲置的线程可以窃取其他线程的闲置任务进行处理。

性能对比结论:数组数据量越大,parallelSort的优势就越明显。

jdk源码中写的排序算法都很精简,值得学习

spliterator:最优的遍历

这是JDK为了高级遍历数组而提供的一个方法。具体使用方式,会在后续讲解spliterator迭代器的时候专题讲解

Collections工具类介绍

此类完全由在 collection 上进行操作或返回 collection 的静态方法组成 如果为此类的方法所提供的collection 或类对象为 null,则这些方法都将抛出NullPointerException。

public的可用字段摘要(都用对应的get方法供访问)
public static final List EMPTY_LIST:空的列表(不可变的)
public static final Map EMPTY_MAP:空的映射(不可变的)
public static final Set EMPTY_SET:空的 set(不可变的)
排序

集合排序,不可为使用不多,啥都不说,直接上例子,解释都放在例子里面

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(12);
        list.add(-15);
        list.add(7);
        list.add(4);
        list.add(35);
        list.add(9);

        System.out.println("源列表:" + list); //[12, -15, 7, 4, 35, 9]

        // 排序(自然顺序)
        Collections.sort(list);
        System.out.println("自然序:" + list); //[-15, 4, 7, 9, 12, 35]

        // 逆序
        Collections.reverse(list);
        System.out.println("逆序:" + list); //[35, 12, 9, 7, 4, -15]


        // 随机排序(扑克牌洗牌经常使用) 每次结果都不一样
        Collections.shuffle(list);
        System.out.println("随机序:" + list); //[9, 4, -15, 12, 7, 35]

        // 定制排序的用法(根据指定比较器产生的顺序对指定列表进行排序),将int类型转成string进行比较(注意,不再是数字了,排序有变化)
        Collections.sort(list, Comparator.comparing(String::valueOf));
        System.out.println("定制序:" + list); //[-15, 12, 35, 4, 7, 9]

        // 旋转(旋转效果有点意思)
        Collections.rotate(list, 3); //[4, 7, 9, -15, 12, 35]
        System.out.println("旋转3:" + list);

        Collections.rotate(list, -3);
        System.out.println("旋转-3:" + list); //[-15, 12, 35, 4, 7, 9]
    }

对比下结果:

swap 交换两个位置的值
//list-- 在该列表中的调剂元素。
//i-- 要交换的一个元素的索引。
//j-- 要交换的其它元素的索引。
// 请注意i/j都不要超出范围
public static void swap(List<?> list,int i,int j);

案例:

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(12);
        list.add(-15);
        list.add(7);
        list.add(4);
        list.add(35);
        list.add(9);

        System.out.println("swap之前:" + list);
        Collections.swap(list, 0, 2); //把0位置元素和2位置交换(备注:若是不可变集合,此处是会抛错的)
        System.out.println("swap之后:" + list);
    }
查找及替换操作
   public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(12);
        list.add(-15);
        list.add(7);
        list.add(4);
        list.add(35);
        list.add(9);

        System.out.println("源列表:" + list);

        // 最大值
        System.out.println("最大值:" + Collections.max(list)); //35

        // 最小值
        System.out.println("最小值:" + Collections.min(list)); //-15

        // 替换 把所有的-15替换成12
        Collections.replaceAll(list, -15, 12);
        System.out.println("-15替换12:" + list); //[12, 12, 7, 4, 35, 9]

        // 出现次数(这个特别有用  某个元素出现的此处)  支持找null出现的次数哦~~~~
        System.out.println("12出现次数:" + Collections.frequency(list, 12)); //2

        // 排序  因为二分查找必须有序  否则可能不准
        Collections.sort(list);
        System.out.println("排序后:" + list); //[4, 7, 9, 12, 12, 35]

        // 二分查找 (注意,返回的是查找对象的索引,List必须是有序的)
        System.out.println("-15下标:" + Collections.binarySearch(list, -15)); //-1 没找到就是-1
        System.out.println("7下标:" + Collections.binarySearch(list, 7)); //1
        System.out.println("35下标:" + Collections.binarySearch(list, 35)); //5

        //indexOfSubList:返回指定源列表中第一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回-1。
        //lastIndexOfSubList:同上 (最后一次)   若出现多次的请,会有差异
        System.out.println(Collections.indexOfSubList(list, Arrays.asList(7, 9))); //1
        System.out.println(Collections.lastIndexOfSubList(list, Arrays.asList(7, 9))); //1

        //fill:用后面元素,把所有元素都给替换掉
        Collections.fill(list, 1);
        System.out.println("fill后的:" + list); //[1, 1, 1, 1, 1, 1]
    }
同步和只读

这个使用起来比较简单,但是能提高效率和安全性。特别是只读的,建议大家多使用 SynchronizedList(), SynchronizedSet(),unmodifiableCollection等等

singletonXXX
    public static <T> Set<T> singleton(T o) {
        return new SingletonSet<>(o);
    }

    public static <T> List<T> singletonList(T o) {
        return new SingletonList<>(o);
    }

    public static <K,V> Map<K,V> singletonMap(K key, V value) {
        return new SingletonMap<>(key, value);
    }

以前我们要初始化一个元素,到List,我们经常这么用:Arrays.asList(),那么今后,若只有一个元素需要构建list,请使用singletonXXX吧~~~

singletonXXX优势分析: 这个方法主要用于只有一个元素的优化,减少内存分配,无需分配额外的内存,可以从SingletonList内部类看得出来,由于只有一个element,因此可以做到内存分配最小化,相比之下ArrayList的DEFAULT_CAPACITY=10个

同样需要注意的是:他们返回都是只读的List,如果调用修改接口,将会抛出UnsupportedOperationException

其它
  • addAll(Collection<? super T> c, T… elements):将所有指定元素添加到指定collection 中
  • copy(List<? super T> dest, List<? extends T> src):将所有元素从一个列表复制到另一个列表(挺好用)
  • disjoint(Collection<?> c1, Collection<?> c2):如果两个指定collection 中没有相同的元素,则返回 true (判断两个集合是否有重合的元素) 建议使用,毕竟JDK的算法还不错
  • nCopies(int n, T o):返回由指定对象的n 个副本组成的不可变列表 (可以copy)
    public static void main(String[] args) {
        //若需要生成相同元素,可用这个快速生成
        List<Integer> list = Collections.nCopies(5, new Integer(1));
        System.out.println(list); //[1, 1, 1, 1, 1]

        //注意,这里返回true,所以需要注意:长度虽然是5  但是都是副本的拷贝,变一个其余都变的
        System.out.println(list.size());
        System.out.println(list.get(0) == list.get(1)); //true
    }
  • asLifoQueue(Deque deque):以后进先出(Lifo) Queue 的形式返回某个 Deque 的视图

Deque是接口,具体继承关系为:Collection–>Queue–>Deque–>LinkedList、ArrayDeque、LinkedBlockingDeque

**Queue(队列)接口与List、Set同一级别,都是继承了Collection接口。Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。**它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。 如果要使用前端而不移出该元素,使用element()或者peek()方法。

Deque(双端队列)接口支持两端插入和移除元素。名称deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。

    public static void main(String[] args) {
        //LinkedList 是先进先出的First-in-first-out fifo
        LinkedList<Integer> llist1 = new LinkedList<>(Arrays.asList(2, 4, 6));
        System.out.println(llist1); //[2, 4, 6]

        //offer方法底层调用add方法,一模一样的
        System.out.println(llist1.add(5)); //true 添加成功返回的true
        System.out.println(llist1); //[2, 4, 6, 5]  发现添加到尾部的

        //element和peek都是把队列首部元素取出  但是不移除   poll()方法也是取出,但是会移除
        System.out.println(llist1.element()); //2
        System.out.println(llist1.peek()); //2
        System.out.println(llist1); //[2, 4, 6, 5, 9]

        System.out.println("-------------------------");

        //变成Last-in-first-out  lifo的队列  也就是后进先出(上面LinkedList是先进先出,需要注意)
        Queue q = Collections.asLifoQueue(llist1);
        System.out.println(q);

        System.out.println(q.add(5)); //true
        System.out.println(q); //[5, 2, 4, 6, 5]
    }
  • checkedCollection、checkedList等等:返回类型检查的集合,更安全了。(备注:现在都使用泛型集合了,不太建议使用了。当然MyBatis里存在一些问题,可以考虑使用)
    public static void main(String[] args) {
        //这样子,下面程序不抱错,显然是纯在安全隐患的
        //List<String> list = Arrays.asList("12","23");
        //List obj = list;
        ////list中存在了一个非String类型,程序执行到这里不会报错
        //obj.add(112);

        List<String> list = Arrays.asList("12", "23");
        List<String> safeList = Collections.checkedList(list, String.class);
        List obj = safeList;
        //检查容器视图受限于虚拟机可运行的运行时检查   这里就报错了
        obj.add(new Date());//只有执行到这一步才会抛出java.lang.ClassCastException
    }

总结

ArryasCollections是JDK提供给我们的非常好用的两个工具类。工欲善其事必先利其器,掌握了更多的好用工具,我们才能事半功倍

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Jad反编译及eclipse反编译插件JadClipse

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • JavaScript对象

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • Memcache集群环境下缓存解决方案

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • java启动和停止sh

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • Window下安装Maven及Eclipse中安装m2eclipse插件

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • iBatis搭建JAVA项目

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • JavaScript闭包及实现循环绑定事件

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • ES6--Set、Map、Symbol、Proxy及Reflect

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • 解决PKIX问题:unable to find valid certification path to requested target【X509TrustManager】

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • 解决PKIX问题:unable to find valid certification path to requested target

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛

扫码关注云+社区

领取腾讯云代金券