三木猿 发表于 2020-9-18 10:59

java学习日记第二天(实用的工具类和源码解析一Arrays)

本帖最后由 三木猿 于 2020-9-18 11:17 编辑

每日名言
         学者须先立志。今日所以悠悠者,只是把学问不曾做一件事看,遇事则且胡乱恁地打过了,此只是志不立。
                                                                                                                                                                                     ——朱熹
工作中经常会用到一些工具类,本次我们对这些工具类进行解析讲解。
工具类的特征:
      1.构造器必须是私有的,私有后就不能被new出来,因为使用时都是直接调用的,所以不开放构造器
      2.工具类的方法必须是static或final修饰的,这样可以保证方法不可变,使用时也可以直接使用
1,Arrays工具类
      Arrays 主要对数组提供了一些高效的操作,比如说排序、查找、填充、拷贝、相等判断等等。

方法声明 功能描述
public static List asList(T… a)返回由指定数组支持的固定大小的列表
public static String toString(int[] a) 返回指定数组的内容的字符串表示形式
public static void sort(int[] a) 按照数字顺序排列指定的数组
public static void fill(Object[] a, Object val) 将指定的对象引用分配给指定的对象数组的每个元素
public static int binarySearch(Object[] a, Object key) 使用二叉搜索算法搜索指定对象的指定数组
public static int[] copyOfRange(int[] original, int from, int to)将指定数组的指定范围复制到新数组中

      1.1排序(Arrays.sort)
            Arrays.sort 方法主要用于排序,入参支持 int、long、double 等各种基本类型的数组,也支持自定义类的数组,这里写一个自定义类的小demo
         public class test {    // 自定义类
    class SortDTO {
      private Integer sortTarget;

      public SortDTO(Integer sortTarget) {
            this.sortTarget = sortTarget;
      }

      public Integer getSortTarget() {
            return sortTarget;
      }

      public void setSortTarget(Integer sortTarget) {
            this.sortTarget = sortTarget;
      }
    }

    @Test
    public void testSort() {
      List<SortDTO> list = ImmutableList.of(
                new SortDTO(300),
                new SortDTO(50),
                new SortDTO(200),
                new SortDTO(220)
      );
      // 我们先把数组的大小初始化成 list 的大小,保证能够正确执行 toArray
      SortDTO[] array = new SortDTO;
      list.toArray(array);
      System.out.println("排序之前:" + JSON.toJSONString(array));
      Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
      System.out.println("排序之后:" + JSON.toJSONString(array));
    }
}

这是sort默认调用的核心源码
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
      assert a != null && lo >= 0 && lo <= hi && hi <= a.length;

      int nRemaining = hi - lo;
      if (nRemaining < 2)
            return;// array的大小为0或者1就不用排了

      // 当数组大小小于MIN_MERGE(32)的时候,就用一个"mini-TimSort"的方法排序,jdk1.7新加
      if (nRemaining < MIN_MERGE) {
            //这个方法比较有意思,其实就是将我们最长的递减序列,找出来,然后倒过来
            int initRunLen = countRunAndMakeAscending(a, lo, hi);
            //长度小于32的时候,是使用binarySort的
            binarySort(a, lo, hi, lo + initRunLen);
            return;
      }
      //先扫描一次array,找到已经排好的序列,然后再用刚才的mini-TimSort,然后合并,这就是TimSort的核心思想
      ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen);
      int minRun = minRunLength(nRemaining);
      do {
            // Identify next run
            int runLen = countRunAndMakeAscending(a, lo, hi);

            // If run is short, extend to min(minRun, nRemaining)
            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen);
                runLen = force;
            }

            // Push run onto pending-run stack, and maybe merge
            ts.pushRun(lo, runLen);
            ts.mergeCollapse();

            // Advance to find next run
            lo += runLen;
            nRemaining -= runLen;
      } while (nRemaining != 0);

      // Merge all remaining runs to complete sort
      assert lo == hi;
      ts.mergeForceCollapse();
      assert ts.stackSize == 1;
    }
   1.2.二分查找法
            Arrays.binarySearch 方法主要用于快速从数组中查找出对应的值。其支持的入参类型非常多,如 byte、int、long 各种类型的数组。
         返回参数是查找到的对应数组下标的值,如果查询不到,则返回负数。我们来写个demo
public class test {
    // 自定义类
    class SortDTO {
      private Integer sortTarget;

      public SortDTO(Integer sortTarget) {
            this.sortTarget = sortTarget;
      }

      public Integer getSortTarget() {
            return sortTarget;
      }

      public void setSortTarget(Integer sortTarget) {
            this.sortTarget = sortTarget;
      }
    }

    @Test
    public void testSort() {
      List<SortDTO> list = ImmutableList.of(
                new SortDTO(300),
                new SortDTO(50),
                new SortDTO(200),
                new SortDTO(220)
      );
      SortDTO[] array = new SortDTO;
      list.toArray(array);
      System.out.println("搜索之前:"+JSON.toJSONString(array));
      Arrays.sort(array, Comparator.comparing(SortDTO::getSortTarget));
      System.out.println("先排序,结果为:"+ JSON.toJSONString(array));
      int index = Arrays.binarySearch(array, new SortDTO(200),
                Comparator.comparing(SortDTO::getSortTarget));
      if(index<0){
            throw new RuntimeException("没有找到 200");
      }
      System.out.println("搜索结果:"+ JSON.toJSONString(array));
    }
}


从上述代码中我们需要注意两点:
               1. 如果被搜索的数组是无序的,一定要先排序,否则二分搜索很有可能搜索不到,我们 demo里面也先对数组进行了排序;
               2. 搜索方法返回的是数组的下标值。如果搜索不到,返回的下标值就会是负数,这时我们需要判断一下正负。如果是负数,还从数组中获取数据的话,
会报数组越界的错误。demo 中对这种情况进行了判断,如果是负数,会提前抛出明确的异常
我们再来看看二分查找法的源码
    // a:我们要搜索的数组,fromIndex:从那里开始搜索,默认是0; toIndex:搜索到何时停止,默认
    // key:我们需要搜索的值 c:外部比较器
    private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex,
                                       T key, Comparator<? super T> c) {
      // 如果比较器 c 是空的,直接使用 key 的 Comparable.compareTo 方法进行排序
      // 假设 key 类型是 String 类型,String 默认实现了 Comparable 接口,就可以直接使用 compar
      if (c == null) {
            // 这是另外一个方法,使用内部排序器进行比较的方法
            return binarySearch0(a, fromIndex, toIndex, key);
      }
      int low = fromIndex;
      int high = toIndex - 1;
      // 开始位置小于结束位置,就会一直循环搜索
      while (low <= high) {
            // 假设 low =0,high =10,那么 mid 就是 5,所以说二分的意思主要在这里,每次都是计算索
            int mid = (low + high) >>> 1;
            T midVal = a;
            // 比较数组中间值和给定的值的大小关系
            int cmp = c.compare(midVal, key);
            // 如果数组中间值小于给定的值,说明我们要找的值在中间值的右边
            if (cmp < 0)
                low = mid + 1;
            // 我们要找的值在中间值的左边
            else if (cmp > 0)
                high = mid - 1;
            else
            // 找到了
                return mid; // key found
      }
      // 返回的值是负数,表示没有找到
      return -(low + 1); // key not found.
    }
         二分的主要意思是每次查找之前,都找到中间值,然后拿我们要比较的值和中间值比较,根据结果修改比较的上限或者下限,通过循环最终找到相等的位置索引
1.3数组拷贝   
      数组拷贝我们经常遇到,有时需要拷贝整个数组,有时需要拷贝部分,比如 ArrayList 在add(扩容) 或 remove(删除元素不是最后一个) 操作时,会进行一些拷贝。拷贝整个数组      
   我们可以使用 copyOf 方法,拷贝部分我们可以使用 copyOfRange 方法,以 copyOfRange为例,看下底层源码的实现:
    // original 原始数组数据
    // from 拷贝起点
    // to 拷贝终点
    public static char[] copyOfRange(char[] original, int from, int to) {
      // 需要拷贝的长度
      int newLength = to - from;
      if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
      // 初始化新数组
      char[] copy = new char;
      // 调用 native 方法进行拷贝,参数的意思分别是:
      // 被拷贝的数组、从数组那里开始、目标数组、从目的数组那里开始拷贝、拷贝的长度
      System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));
      return copy;
    }
从源码中,我们发现,Arrays 的拷贝方法,实际上底层调用的是 System.arraycopy 这个native 方法,如果你自己对底层拷贝方法比较熟悉的话,也可以直接使用。
参考链接:https://blog.csdn.net/xsj_blog/article/details/78824045
                  https://www.cnblogs.com/williamjie/p/9103469.html

梦里余杭 发表于 2020-9-18 11:05

{:301_975:}学习学习学习

KatharsisKing 发表于 2020-9-18 11:33

吾爱吧52 发表于 2020-9-18 11:55

感谢分享!

H.one 发表于 2020-9-18 12:07

最近在学Java嘿嘿感谢楼主的分享&#128591;&#128591;

QingYi. 发表于 2020-9-18 12:09

学习下java

小熊孩 发表于 2020-9-18 14:54

跟着大佬学编程第二天

username11 发表于 2020-10-16 14:59

大哥 这怕不是第二天的哦
页: [1]
查看完整版本: java学习日记第二天(实用的工具类和源码解析一Arrays)