【Java杂记】工具类:数组工具类 Arrays

分手后的思念是犯贱 2022-12-13 01:25 346阅读 0赞

在文章开头,我们先看看工具类通用的特征:

  • 构造器必须是私有的。这样的话,工具类就无法被 new 出来,因为工具类在使用的时候,无需初始化,直接使用即可,所以不会开放出构造器出来。
  • 工具类的工具方法必须被 static、final 关键字修饰。这样的话就可以保证方法不可变,并且可以直接使用,非常方便。

我们需要注意的是,尽量不在工具方法中,对共享变量有做修改的操作访问(如果必须要做的话,必须加锁),因为会有线程安全的问题。除此之外,工具类方法本身是没有线程安全问题的,可以放心使用。

Arrays 主要对数组提供了一些高效的操作,比如说排序、查找、填充、拷贝、相等判断等等

1.Arrays.toString()

将数组转化为字符串,也可以看做是输出当前数组所有元素。下面是 toString 方法的源码:

  1. // 这里除了int外,Arrays还提供了 double、long、char等...
  2. public static String toString(int[] a) {
  3. if (a == null)
  4. return "null";
  5. int iMax = a.length - 1;
  6. if (iMax == -1)
  7. return "[]";
  8. // 通过一个StringBuilder来拼接数组中的所有元素
  9. StringBuilder b = new StringBuilder();
  10. // 左边 [
  11. b.append('[');
  12. for (int i = 0; ; i++) {
  13. b.append(a[i]);
  14. // 如果数组遍历完了,加上右边],然后toString转为String
  15. if (i == iMax)
  16. return b.append(']').toString();
  17. b.append(", ");
  18. }
  19. }

2.Arrays.sort()

数组排序, 使用了双轴快速排序算法,效率高

排序算法分为以下几种情况:

  • 如果数组长度大于等于286且连续性好(有序程度高),就用归并排序
  • 如果大于等于286且连续性不好(有序程度低),就用双轴快速排序
  • 如果长度小于286且大于等于47,就用双轴快速排序
  • 如果长度小于47,就用插入排序
  • 入参支持 int、long、double 等各种基本类型的数组
  • 同样支持自定义类的数组,但必须要有比较器

    • 实现Comparable接口,重写compareTo方法
    • 传入Comparator比较器,重写comparing方法

下面是一个自定义类型的代码示例:

  1. // 自定义类
  2. class SortDTO {
  3. private String sortTarget;
  4. public SortDTO(String sortTarget) {
  5. this.sortTarget = sortTarget;
  6. }
  7. public String getSortTarget() {
  8. return sortTarget;
  9. }
  10. @Override
  11. public String toString() {
  12. return "SortDTO{" +
  13. "sortTarget='" + sortTarget + '\'' +
  14. '}';
  15. }
  16. }
  17. public void testSort(){
  18. List<SortDTO> list = new ArrayList<SortDTO>(){
  19. {
  20. add(new SortDTO("test01"));
  21. add(new SortDTO("test03"));
  22. add(new SortDTO("test02"));
  23. }
  24. };
  25. // 先把数组的大小初始化成 list 的大小,保证能够正确执行 toArray
  26. SortDTO[] array = new SortDTO[list.size()];
  27. list.toArray(array);
  28. System.out.println("排序之前:" + Arrays.toString(array));
  29. // sort 需要传入自定义比较器
  30. // Arrays.sort(array, (a, b) -> {
  31. // return a.getSortTarget().compareTo(b.getSortTarget());
  32. // });
  33. Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
  34. System.out.println("排序之后:" + Arrays.toString(array));
  35. }

执行结果

  1. 排序之前:[SortDTO{sortTarget='test01'}, SortDTO{sortTarget='test03'}, SortDTO{sortTarget='test02'}]
  2. 排序之后:[SortDTO{sortTarget='test01'}, SortDTO{sortTarget='test02'}, SortDTO{sortTarget='test03'}]

3.Arrays.binarySearch()

二分查找。

  • 支持的入参类型非常多,如 byte、int、long 各种类型的数组
  • 若被搜索的数组是无序的,一定要先排序,否则二分搜索很有可能搜索不到
  • 返回参数是查找到的对应数组下标的值,如果查询不到,则返回负数

    watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkzNTkyNw_size_16_color_FFFFFF_t_70_pic_center

下面是一个自定义类的代码示例:

  1. public void testBinarySearch(){
  2. List<SortDTO> list = new ArrayList<SortDTO>(){
  3. {
  4. add(new SortDTO("100"));
  5. add(new SortDTO("300"));
  6. add(new SortDTO("500"));
  7. add(new SortDTO("200"));
  8. }
  9. };
  10. SortDTO[] array = new SortDTO[list.size()];
  11. list.toArray(array);
  12. System.out.println("搜索之前:" + Arrays.toString(array));
  13. // 必须要先排序
  14. Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
  15. System.out.println("先排序,结果为:" + Arrays.toString(array));
  16. // 将排序好的数组传入,若是自定义类型还要传入比较器
  17. int index = Arrays.binarySearch(array, new SortDTO("200"), Comparator.comparing(SortDTO::getSortTarget));
  18. // 返回的index小于0表示没找到
  19. if(index<0){
  20. throw new RuntimeException("没有找到200");
  21. }
  22. System.out.println("搜索结果:" + array[index]);
  23. }

执行结果

  1. 搜索之前:[SortDTO{sortTarget='100'}, SortDTO{sortTarget='300'}, SortDTO{sortTarget='500'}, SortDTO{sortTarget='200'}]
  2. 先排序,结果为:[SortDTO{sortTarget='100'}, SortDTO{sortTarget='200'}, SortDTO{sortTarget='300'}, SortDTO{sortTarget='500'}]
  3. 搜索结果:SortDTO{sortTarget='200'}

接下来,我们来看下二分法底层代码的实现:

二分的主要意思是每次查找之前,都找到中间值,然后拿我们要比较的值和中间值比较,根据结果修改比较的上限或者下限,通过循环最终找到相等的位置索引

  1. // a:我们要搜索的数组,fromIndex:从那里开始搜索,默认是0; toIndex:搜索到何时停止,默认是数组大小
  2. // key:我们需要搜索的值 c:外部比较器
  3. private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex,
  4. T key, Comparator<? super T> c) {
  5. // 如果比较器 c 是空的,直接使用 key 的 Comparable.compareTo 方法进行排序
  6. // 假设 key 类型是 String 类型,String 默认实现了 Comparable 接口,
  7. // 就可以直接使用 compareTo 方法进行排序
  8. if (c == null) {
  9. // 这是另外一个方法,使用内部排序器进行比较的方法
  10. return binarySearch0(a, fromIndex, toIndex, key);
  11. }
  12. int low = fromIndex;
  13. int high = toIndex - 1;
  14. // 开始位置小于结束位置,就会一直循环搜索
  15. while (low <= high) {
  16. // 假设 low =0,high =10,那么 mid 就是 5,所以说二分的意思主要在这里,每次都是计算索引的中间值
  17. int mid = (low + high) >>> 1;
  18. T midVal = a[mid];
  19. // 比较数组中间值和给定的值的大小关系
  20. int cmp = c.compare(midVal, key);
  21. // 如果数组中间值小于给定的值,说明我们要找的值在中间值的右边
  22. if (cmp < 0)
  23. low = mid + 1;
  24. // 我们要找的值在中间值的左边
  25. else if (cmp > 0)
  26. high = mid - 1;
  27. else
  28. // 找到了
  29. return mid; // key found
  30. }
  31. // 返回的值是负数,表示没有找到
  32. return -(low + 1); // key not found.
  33. }

4.Arrays.copyOf/copyOfRange()

  • 拷贝整个数组我们可以使用 copyOf 方法,如 ArrayList#add
  • 拷贝部分我们可以使用 copyOfRange 方法,如 ArrayList#remove(除最后一个元素)

Arrays 的拷贝方法,实际上底层调用的是 System.arraycopy 这个 native 方法,如果你自己对底层拷贝方法比较熟悉的话,也可以直接使用。

  1. // original 原始数组数据
  2. // from 拷贝起点
  3. // to 拷贝终点
  4. public static char[] copyOfRange(char[] original, int from, int to) {
  5. // 需要拷贝的长度
  6. int newLength = to - from;
  7. if (newLength < 0)
  8. throw new IllegalArgumentException(from + " > " + to);
  9. // 初始化新数组
  10. char[] copy = new char[newLength];
  11. // 调用 native 方法进行拷贝,参数的意思分别是:
  12. // 被拷贝的数组、从数组那里开始、目标数组、从目的数组那里开始拷贝、拷贝的长度
  13. System.arraycopy(original, from, copy, 0,
  14. Math.min(original.length - from, newLength));
  15. return copy;
  16. }

5.Arrays.asList()

asList 的作用是将数组转换为 List

  1. List list = Arrays.asList("a","b","c")

这里注意, Arrays.asList 在使用时有两个坑:

  • 坑 1:数组被修改后,会直接影响到新 List 的值。
  • 坑 2:不能对新 List 进行 add、remove 等操作,否则运行时会报 UnsupportedOperationException 错误。

    public void testArrayToList(){

    Integer[] array = new Integer[]{

    1. 1,2,3,4,5,6};

    List list = Arrays.asList(array);

    // 坑1:修改数组的值,会直接影响原 list
    log.info(“数组被修改之前,集合第一个元素为:{}”,list.get(0));
    array[0] = 10;
    log.info(“数组被修改之前,集合第一个元素为:{}”,list.get(0));

    // 坑2:使用 add、remove 等操作 list 的方法时,
    // 会报 UnsupportedOperationException 异常
    list.add(7);
    }

我们就从源码中看看这两个坑从何来:
5d7ef4ad0001a58020880820.png_pic_center

从上图中,我们可以发现Arrays.asList 方法返回的 List 并不是 java.util.ArrayList,而是自己内部的一个静态类

  1. 该静态类没有自己创建一个数组,而是直接持有数组的引用,所以修改数组,该list就会改变
  2. 并且没有实现 add、remove 等方法,所以调用add,remove时会抛出异常

若是要变成 java.util.ArrayList 可以 new ArrayList(Arrays.asList(arr)),即 array–> Collection —> ArrayList

发表评论

表情:
评论列表 (有 0 条评论,346人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java工具--Arrays

    平时在做编程题的时候遇到数组处理总是很麻烦,需要自己写,后来我发现了一个很好用的工具类Arrays Java提供的Arrays包含许多类方法,可以直接用Arrays.类方法

    相关 Arrays 工具

    JDK 提供了一个工具类专门用来操作数组的工具类,即 Arrays,该 Arrays 工具类提供了大量的静态方法,在实际项目开发中,推荐使用,这样既快捷又不会发生错误。但在面试