前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家java】聊聊Java中的java.util.Arrays类和java.util.Collections工具类

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

作者头像
YourBatman
发布2019-09-03 14:59:47
7410
发布2019-09-03 14:59:47
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

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

备注:本博文基于JDK8讲解

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

Arrays的方法分类介绍

先看几张截图分类

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

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

asList:快速构建一个list

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

代码语言:javascript
复制
 public static <T> List<T> asList(T... var0) {
        return new Arrays.ArrayList(var0);
    }
代码语言:javascript
复制
 private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {

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

代码语言:javascript
复制
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 方法)。如果没有对数组进行排序,则结果是不明确的。如果数组包含多个带有指定值的元素,则无法保证找到的是哪一个。

代码语言:javascript
复制
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 复制、截取数组

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

代码语言:javascript
复制
 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:比较二维数组内容

代码语言:javascript
复制
  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
代码语言:javascript
复制
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
代码语言:javascript
复制
  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,但是我们看看源码:

代码语言:javascript
复制
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],所以此处我们使用的时候一定要万分注意,回调给我们的是角标,而不是值,而不是值

代码语言:javascript
复制
 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:二元迭代,对原数组内容进行二元操作

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

代码语言:javascript
复制
 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:算法精华

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

代码语言:javascript
复制
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方法供访问)
代码语言:javascript
复制
public static final List EMPTY_LIST:空的列表(不可变的)
public static final Map EMPTY_MAP:空的映射(不可变的)
public static final Set EMPTY_SET:空的 set(不可变的)
排序

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

代码语言:javascript
复制
    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 交换两个位置的值
代码语言:javascript
复制
//list-- 在该列表中的调剂元素。
//i-- 要交换的一个元素的索引。
//j-- 要交换的其它元素的索引。
// 请注意i/j都不要超出范围
public static void swap(List<?> list,int i,int j);

案例:

代码语言:javascript
复制
    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);
    }
查找及替换操作
代码语言:javascript
复制
   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
代码语言:javascript
复制
    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)
代码语言:javascript
复制
    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”。

代码语言:javascript
复制
    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里存在一些问题,可以考虑使用)
代码语言:javascript
复制
    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提供给我们的非常好用的两个工具类。工欲善其事必先利其器,掌握了更多的好用工具,我们才能事半功倍

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年08月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Arrays的方法分类介绍
    • asList:快速构建一个list
      • binarySearch:二分法查找,数组必须有序
        • compare和compareUnsigned
          • copyOf和copyOfRange 复制、截取数组
            • equals和deepEquals:比较数组内容是否相等
              • toString和deepToString
                • hashCode和deepHashCode
                  • fill:将一个数组全部置为 val ,或在下标范围内将数组置为 val
                    • setAll和parallelSetAll:JDK有歧义的实现方式
                      • stream 这个就不解释了
                        • parallelPrefix:二元迭代,对原数组内容进行二元操作
                          • sort和parallelSort:算法精华
                            • spliterator:最优的遍历
                            • Collections工具类介绍
                              • public的可用字段摘要(都用对应的get方法供访问)
                                • 排序
                                  • swap 交换两个位置的值
                                    • 查找及替换操作
                                      • 同步和只读
                                        • singletonXXX
                                          • 其它
                                          • 总结
                                          相关产品与服务
                                          容器服务
                                          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档