java中的Arrays这个工具类你真的会用吗(一文秒懂)

Java源码系列三-工具类Arrays

​ 今天分享java的源码的第三弹,Arrays这个工具类的源码。因为近期在复习数据结构,了解到Arrays里面的排序算法和二分查找等的实现,收益匪浅,决定研读一下Arrays这个类的源码。不足之处,欢迎在评论区交流和指正。

1.认识Arrays这个类:

​ 首先它在java的utils包下,属于Java Collections Framework中的一员。它的初衷就是一个工具类,封装了操纵数组的各种方法,比如排序,二分查找,数组的拷贝等等。满足了我们日常对数组操做的基本需求,了解它的底层实现,不仅能帮助我们更好的使用它,而且还能培养我们更好的代码的思维。

2.构造方法

​ 因为是一个工具类,所以它的构造方法定义为私有的,且所有的实现方法都是静态方法。也就是说这个类不能被实例化,通俗的讲,就是不能new。只能通过类名来直接调用方法(反射除外)。这样做的目的是强化该类不可实列化的能力,突出该类作为工具类的根本职能。源码如下:

 // Suppresses default constructor, ensuring non-instantiability.
 private Arrays()
{
}

3.常用方法的解析

3.1快速插入集合元素的方法asList(T... a):

基本使用:

 /**
  * 数组转化为集合
  */
 @Test
 public void toArrayTest(){
  List<Integer> list = Arrays.asList(2,4,5,6,6);
  for (Integer integer : list) {
   System.out.print(integer+" ");
  }
 }

输出结果:

2 4 5 6 6

看一下源码:

@SafeVarargs
 @SuppressWarnings("varargs")
 public static <T> List<T> asList(T... a) {
  return new ArrayList<>(a);
 }

// ArrayList的构造方法和属性
  private final E[] a;
  ArrayList(E[] array) {
   a = Objects.requireNonNull(array);
  }

​ 这个方法的实现比较简单,就是调用ArrayList的构造方法,并且参数是一个数组,也就是将我们要构造的数传入到ArrayList的构造方法中去,进行实例化。

3.2.二分查找的方法

Arrays类中的二分查找八种基本类型都有涉及,但都是方法的重载。其实现原理都是一样,这里以int类型为例,进行说明。

基本使用:

 @Test
 public void binarySearchTest(){
  int[] arrays = {1,4,6,7,9,3};
  // 查找元素为7的下标值
  int result = Arrays.binarySearch(arrays,7);
  System.out.println(result);
 }

结果:

3

这个方法主要涉及的一下三个方法:

// 我们常用的方法
public static int binarySearch(int[] a, int key) {
  return binarySearch0(a, 0, a.length, key);
 }

/*
 参数说明如下: a 待查找的数组
 fromIndex 查找的开始位置
 toIndex  查找的结束位置
 key   查找的目标值
 */
public static int binarySearch(int[] a, int fromIndex, int toIndex,
         int key) {
  // 进行异常检查
  rangeCheck(a.length, fromIndex, toIndex);
  return binarySearch0(a, fromIndex, toIndex, key);
 }

 // Like public version, but without range checks.
 private static int binarySearch0(int[] a, int fromIndex, int toIndex,
          int key) {
  int low = fromIndex;
  int high = toIndex - 1;

  while (low <= high) {

   // 找出查找范围的中间值
   int mid = (low + high) >>> 1;
   int midVal = a[mid];

   // 进行比较
   if (midVal < key)
    low = mid + 1;
   else if (midVal > key)
    high = mid - 1;
   else
    return mid; // key found
  }
  return -(low + 1); // key not found.
 }

当然实现的核心方法还是上述私有方法binarySearch0()这个方法,实现的逻辑也不复杂。

第一步就是声明两个变量存储查找区域的开始和结束。

第二步 循环,比较,不断的缩小比较的范围,直到找到数组中的值和目标值相同,返回下标,如果没有找到就返回一个负数也就是下面的这l两行代码:

return mid; // key found
return -(low + 1); // key not found.

我认为:这个二分法实现的亮点就在于求中间值的移位运算:

int mid = (low + high) >>> 1;

有人就纳闷了,为什么还要使用移位运算,除法不行吗?主要还是为了性能考量。因为移位运算占两个机器周期,而乘除法占四个运算周期,所以移位运算的速度肯定比乘除法的运算速度快很多,计算量小了可能区别不大,但是计算量很大,就区别很明显了。

3.3 数组的拷贝

 @Test
 public void testCopyArrange(){
   // 原数组
  int [] srcArray = {11,2,244,5,6,54};
  // 拷贝原数组长度为3的部分
  int[] descArray = Arrays.copyOf(srcArray,3);

  System.out.println(Arrays.toString(descArray));
 }

输出结果:

[11, 2, 244]

源码分析:

/* 参数说明:
 original 原数组
 newLength 拷贝的数组长度
 */
public static int[] copyOf(int[] original, int newLength) {
   // 声明一个新数组的长度,存储拷贝后的数组
  int[] copy = new int[newLength];
  System.arraycopy(original, 0, copy, 0,
       Math.min(original.length, newLength));
  return copy;
 }

public static native void arraycopy(Object src, int srcPos,
          Object dest, int destPos,
          int length);

分析: 主要还是调用了本地的方法arraycopy完成数组的指定长度拷贝,可以看到源码并没有对数组的长度进行检查,主要是arraycopy()这个方法时使了Math.min()方法,保证了你声明的长度在一个安全的范围之内,如果你拷贝的长度超出了数组的长度,就默认拷贝整个数组。至于native修饰的方法的使用,可以看看这里

 System.arraycopy(original, 0, copy, 0,
       Math.min(original.length, newLength));

当然如果需要拷贝数组指定的区间 ,可以使用Arrays的copyOfRange(int[] original, int from, int to) 实现原理和arraycopy()方法的原理类似:

@Test
 public void testCopy(){
  int [] srcArray = {11,2,244,5,6,54};
   // 拷贝指定范围的数组
  int[] descArray = Arrays.copyOfRange(srcArray,0,3);
  System.out.println(Arrays.toString(descArray));
 }

输出结果:

[11, 2, 244]

注: copyOfRange(int[] original, int from, int to)中的参数to是不包含在拷贝的结果中的,上述的例子,就只能拷贝到索引为2的元素,不包含索引为3的元素,这点需要注意。

3.4 equals方法

主要重写了Object类的equals方法,用来比较两个数组内容是否相等,也就是他们中的元素是否相等。

基本用法:

 @Test
 public void equalTest(){
  int[] array ={1,2,3,4};
  int[] result ={1,2,3,4};
  System.out.println(Arrays.equals(array,result));
  System.out.println(array == result);
 }

结果:

true
false

看源码之前,有必要讲一下重写了equals方法之后,两个对象比较的是值,也就是他们的内容,这点非常的重要。重写equals方法的注意事项可以移步这里

源码如下:

 public static boolean equals(int[] a, int[] a2) {
  // 基于地址的比较
  if (a==a2)
   return true;
  if (a==null || a2==null)
   return false;

  int length = a.length;
  // 基于长度的比较
  if (a2.length != length)
   return false;

  // 比较每个元素是否相等
  for (int i=0; i<length; i++)
   if (a[i] != a2[i])
    return false;

  return true;
 }

源码说明如下:

源码判断了四次,分别是首地址比较,是否为空,以及长度的比较,最后对于数组的各个元素进行比较。

有必要说明下第一个判断,也就是首地址的比较。当我们声明一个数组变量时,这个变量就代表数组的首地址,看下面这个代码:

 @Test
 public void equalTest(){
  int[] array ={1,2,3,4};
  System.out.println(array);

 }

结果:

[I@4f2410ac     // [代表数组 I代表整数 @分隔符 后边内存地址十六进制

​ 这表示的是一个地址。还是因为在声明一个数组时,会在堆里面创建一块内存区域,但是这块内存区域相对于堆来说可能很小,不好找。为了方便查找,所以将数组内存中的首地址表示出来。虚拟机将地址传给变量名array。这也是引用类型,传的是地址,也就是理解成array指向内存地址(类似于家庭的地址),每次运行可能地址都不一样,因为虚拟机开辟的内存空间可能不一样。

理解了这个,那么a==a2就好理解了,如果两个数组内存地址都相同,那么两个数组的肯定是相等的。

还有我认为程序写的比较好的地方就是源码中对数组每个元素的比较,也就是下面这段代码;

for (int i=0; i<length; i++)
   if (a[i] != a2[i])
    return false;

  return true;

使用a[i] != a2[i] 作为判断条件,就可以减少比较次数,提高了性能。试想一下如果这里是相等的比较,那每次都要遍历整个数组,如果数据量大了,无疑在性能上会慢很多。又一次感叹到源码的魅力。

3.5 排序相关的方法sort()和parallelSort()

Arrays 这个类中主要涉及了两种类型的排序方法串行 sort()和并行parallelSort()这两个方法,当然对象的排序和基本类型的排序也不太一样。这里还是以int[]类型的为例。进行说明。

首先比较两个方法的性能:

 public final int UPPER_LIMIT = 0xffffff;
 final int ROUNDS = 10;
 final int INCREMENT = 5;
 final int INIT_SIZE = 1000;

 @Test
 public void sortAndParallelSortTest(){

  // 构造不同容量的集合
  for (int capacity = INIT_SIZE; capacity < UPPER_LIMIT ; capacity*= INCREMENT) {
   ArrayList<Integer> list = new ArrayList<>(capacity);
   for (int j = 0; j < capacity; j++) {
    list.add((int) (Math.random()*capacity));
   }

   double avgTimeOfParallelSort = 0;
   double avgTimeOfSort = 0;

   for (int j = 0; j <= ROUNDS ; j++) {
    // 每次排序都打乱顺序
    Collections.shuffle(list);

    Integer[] arr1 = list.toArray(new Integer[capacity]);
    Integer[] arr2 = arr1.clone();

    avgTimeOfParallelSort += counter(arr1,true);
    avgTimeOfSort += counter(arr2, false);
   }
   // 输出结果
   output(capacity,avgTimeOfParallelSort/ROUNDS,avgTimeOfSort/ROUNDS);
  }
 }

 private void output(int capacity, double v, double v1) {
  System.out.println("=======================测试排序的时间=========");
  System.out.println("Capacity"+capacity);
  System.out.println("ParallelSort"+v);
  System.out.println("Sort"+v1);
  System.out.println("比较快的排序是:"+(v < v1 ? "ParallelSort":"Sort"));
 }

 // 计算消耗的时间
 private double counter(Integer[] arr1, boolean b) {
  long begin,end;
  begin = System.nanoTime();
  if(b){
   Arrays.parallelSort(arr1);
  }else{
   Arrays.parallelSort(arr1);
  }
  end = System.nanoTime();
  return BigDecimal.valueOf(end-begin,9).doubleValue();
 }

部分的测试的结果:

=======================测试排序的时间=========
Capacity1000
ParallelSort6.284099999999999E-4
Sort5.599599999999999E-4
比较快的排序是:Sort
=======================测试排序的时间=========
Capacity5000
ParallelSort0.00163599
Sort0.0018313699999999995
比较快的排序是:ParallelSort

可以看到在数据量比较小的情况下,使用sort()方法更快,一旦过了一个阈值,就是ParallelSort()这个方法性能好。这个阈值是多少呢。

我们先看一下parallelSort的源码:

public static void parallelSort(int[] a) {
  int n = a.length, p, g;
  if (n <= MIN_ARRAY_SORT_GRAN ||
   (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
   DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
  else
   new ArraysParallelSortHelpers.FJInt.Sorter
    (null, a, new int[n], 0, n, 0,
     ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
     MIN_ARRAY_SORT_GRAN : g).invoke();
 }

可以看到当数组的长度小于MIN_ARRAY_SORT_GRAN或者p = ForkJoinPool.getCommonPoolParallelism()) == 1 (在单线程下)的时候,调用sort()排序的底层实现的DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);Arrays的开头定义的常量如下:

private static final int MIN_ARRAY_SORT_GRAN = 1 << 13; // 这个值是8192

对比两者,也就是在数组的长度比较大或者是多线程的情况下,优先考虑并行排序,否则使用串行排序。

两个排序的核心思想:

  • sort()方法的核心还是快排和优化后的归并排序, 快速排序主要是对哪些基本类型数据(int,short,long等)排序, 而合并排序用于对对象类型进行排序。
  • parallelSort()它使用并行排序-合并排序算法。它将数组分成子数组,这些子数组本身先进行排序然后合并。

由于并行排序和串行排序的底层比较复杂,且篇幅有限,想要详细了解底层实现的话,可以移步到串行排序并行排序

3.6 toString方法

基本用法:

 @Test
 public void toStringTest(){
   int[] array = {1,3,2,5};
  System.out.println(Arrays.toString(array));
 }

结果:

[1, 3, 2, 5]

源码分析如下:

 public static String toString(int[] a) {
  // 1.判断数组的大小
  if (a == null)
   return "null";
  int iMax = a.length - 1;
  if (iMax == -1)
   return "[]";

  // 2.使用StringBuilder进行追加
  StringBuilder b = new StringBuilder();
  b.append('[');
  for (int i = 0; ; i++) {
   b.append(a[i]);
   if (i == iMax)
    return b.append(']').toString();
   b.append(", ");
  }
 }

具体的实现,已在源码的注释中进行了说明。这个方法对于基本数据类型来说,很方便的遍历数组。

追本溯源,方能阔步前行。

参考资料

https://www.jb51.net/article/180770.htm
https://www.jb51.net/article/116323.htm

javaSE的官方问档。

到此这篇关于java中的Arrays这个工具类你真的会用吗(一文秒懂)的文章就介绍到这了,更多相关java中Arrays工具类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java Arrays工具类用法详解

    本文实例讲述了Java Arrays工具类用法.分享给大家供大家参考,具体如下: Arrays类 功能描述 Arrays类是一个工具类,其中包含了数组操作的很多方法,比如搜索和排序: Arrays类中的方法均为static修饰的,可以直接通过Arrays.xxx(xxx)的形式调用方法. 几个重要方法   1.asList(T... a) 由给定的数组a,返回一个固定大小的List对象.在这里,着重解释一下前面这句话的深层含义,我们可以看Arrays类的源码,来帮助我们理解. 生成的List对象

  • java的Arrays工具类实战

    java.util.Arrays类能方便地操作数组,它提供的所有方法都是静态的.静态方法是属于类的,不是属于类的对象.所以可以直接使用类名加方法名进行调用.Arrays作为一个工具类,能很好的操作数组.下面介绍主要使用的几个函数. 1.fill方法 fill方法主要用来填充数组,这里我们举最简单的int类型吧(其它类型的一样) 看Arrays的fill源码 示例代码: Java代码 publicstaticvoidmain(String[] args) { inta[]=newint[5]; /

  • java Arrays工具类实例详解

    Arrays工具类属于java中常用的工具类 public static void sort(int[] a) public static void sort(int[] a,int fromIndex, int toIndex) public static void sort(long[] a) public static void sort(long[] a,int fromIndex, int toIndex) public static void sort(short[] a) publ

  • java中的Arrays这个工具类你真的会用吗(一文秒懂)

    Java源码系列三-工具类Arrays ​ 今天分享java的源码的第三弹,Arrays这个工具类的源码.因为近期在复习数据结构,了解到Arrays里面的排序算法和二分查找等的实现,收益匪浅,决定研读一下Arrays这个类的源码.不足之处,欢迎在评论区交流和指正. 1.认识Arrays这个类: ​ 首先它在java的utils包下,属于Java Collections Framework中的一员.它的初衷就是一个工具类,封装了操纵数组的各种方法,比如排序,二分查找,数组的拷贝等等.满足了我们日常

  • Java中解密微信加密数据工具类

    当我们开发微信公众号,小程序等,微信返回给我们的数据往往是经过加密的,我们需要使用 sessionKey 配合解密,才能得到我们想要的数据 1.引入依赖 <!-- lombok依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <

  • Java中关于Collections集合工具类的详细介绍

    Collections 是一个操作 Set.List 和 Map 等集合的工具类. Collections 中提供了一系列静态的方法对集合元素进行排序.查询和修改等操作,还提供了对集合对象设置不可变.对集合对象实现同步控制等方法. 排序操作 reverse(List):反转 List 中元素的顺序 shuffle(List):对 List 集合元素进行随机排序 sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序 sort(List,Comparator):根据指定的 C

  • Java中Arrays数组工具类的基本使用详解

    目录 方法一览表 快速定位详细操作 asList() toString() 和 deepToString() sort() 和 parallelSort() binarySearch() compare() 和 compareUnsigned() copyOf() 和 copyOfRange() equals() deepEquals() 比较equals()和deepEquals()方法  fill() mismatch() parallelPrefix() setAll() 和 parall

  • Java常用类库Apache Commons工具类说明及使用实例详解

    Apache Commons包含了很多开源的工具,用于解决平时编程经常会遇到的问题,减少重复劳动.下面是我这几年做开发过程中自己用过的工具类做简单介绍. 组件 功能介绍 BeanUtils 提供了对于JavaBean进行各种操作,克隆对象,属性等等. Betwixt XML与Java对象之间相互转换. Codec 处理常用的编码方法的工具类包 例如DES.SHA1.MD5.Base64等. Collections java集合框架操作. Compress java提供文件打包 压缩类库. Con

  • 利用java生成二维码工具类示例代码

    二维码介绍 二维条形码最早发明于日本,它是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的,在代码编制上巧妙地利用构成计算机内部逻辑基础的"0"."1"比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理. 如下为java生成二维码工具类,可以选择生成文件,或者直接在页面生成,话不多说了,来一起看看详细的示例代码吧. 示例代码 import java.aw

  • java中的arrays.sort()代码详解

    Arrays.sort(T[], Comparator < ? super T > c) 方法用于对象数组按用户自定义规则排序. 官方Java文档只是简要描述此方法的作用,并未进行详细的介绍,本文将深入解析此方法. 1. 简单示例 sort方法的使用非常的简单明了,下面的例子中,先定义一个比较Dog大小的Comparator,然后将其实例对象作为参数传给sort方法,通过此示例,你应该能够快速掌握Arrays.sort()的使用方法. import java.util.Arrays; impo

  • java公众平台通用接口工具类HttpConnectUtil实例代码

    实例如下: package com.common.util; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.URL; import

  • Java语言描述MD5加密工具类实例代码

    编程中经常有用到MD5加密的情况,Java语言并没有像PHP一样提供原生的MD5加密字符串的函数,需要MD5加密的时候,往往需要自己写. 代码如下: import java.security.MessageDigest; public class MD5 { //公盐 private static final String PUBLIC_SALT = "demo" ; //十六进制下数字到字符的映射数组 private final static String[] hexDigits =

  • java关闭流连接IO工具类

    本文实例为大家分享了java关闭流连接IO工具类的具体代码,供大家参考,具体内容如下 package com.demo.utils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; /** * 关闭流连接IO工具类 * @author dongyangyang * @Date 2016

随机推荐