Java之Arrays工具类
JDK 8
---
Arrays工具类 是 JDK中 操作数组 的一个工具类,,本文展示 其常用的一些 public函数 的使用,涉及 复制数组、填充数组、排序、查找 等。
打印小工具:
private static Consumer<Object> cs = System.out::println;
目录
高频函数:
将多个同类型参数转换为list。
将数组转换为List。
建立一个 ArrayList 返回。
public static void testAsList() {
// asList(T...)
cs.accept("------测试asList:");
List<Integer> list1 = Arrays.asList(23);
cs.accept(list1);
list1 = Arrays.asList(23,3,5,-12,345,424242);
cs.accept(list1);
// List<String> liste = Arrays.asList(null); // NullPointerException
// cs.accept(liste);
List<String> list2 = Arrays.asList("apple", "tesla", "baidu", "ibm", "google", null);
cs.accept(list2);
Integer[] iarr1 = {2,3,4,1234,6,78,-23,90};
List ilist = Arrays.asList(iarr1);
cs.accept(ilist);
}
结果:
------测试asList:
[23]
[23, 3, 5, -12, 345, 424242]
[apple, tesla, baidu, ibm, google, null]
[2, 3, 4, 1234, 6, 78, -23, 90]
注:ArrayList 转换为 数组呢?
调用其 toArray 方法——2个!
ArrayList 底层是用 数组来存储元素的。
transient Object[] elementData;
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
//...
System.arraycopy(elementData, 0, a, 0, size);
}
使用 各个toString函数,可以输出 数组中的内容。
public static void testToString() {
cs.accept("------测试toString:");
cs.accept("iarr1:");
int[] iarr1 = {23, 3, 5, -12, 345, 424242, -234};
cs.accept(iarr1);
cs.accept(Arrays.toString(iarr1));
cs.accept("iarr2:");
Integer[] iarr2 = {23, 3, 5, -12, 345, 424242, -234};
cs.accept(iarr2);
cs.accept(Arrays.toString(iarr2));
cs.accept("sarr1:");
String[] sarr1 = {"apple", "tesla", "baidu", "ibm", "google", null};
cs.accept(sarr1);
cs.accept(Arrays.toString(sarr1));
}
结果:
------测试toString:
iarr1:
[I@816f27d
[23, 3, 5, -12, 345, 424242, -234]
iarr2:
[Ljava.lang.Integer;@87aac27
[23, 3, 5, -12, 345, 424242, -234]
sarr1:
[Ljava.lang.String;@3e3abc88
[apple, tesla, baidu, ibm, google, null]
1、非对象
使用 DualPivotQuicksort 算法,by Vladimir Yaroslavskiy
2、对象
配置了 LegacyMergeSort.userRequested,使用 legacyMergeSort;
否则,使用 ComparableTimSort 算法,基于 TimSort 算法;
--
无Comparator 参数的sort,对象数组中不能有 null!
/**
* sort(byte[])
* sort(byte[], int, int)
* sort(T[], Comparator<? super T>)
* sort(T[], int, int, Comparator<? super T>)
* @author ben
* @date 2021-08-11 13:17:15 CST
*/
public static void testSort() {
cs.accept("------测试sort:");
// 基本的数据类型,底层使用 DualPivotQuicksort 算法
int[] iarr1 = {23, 3, 5, -12, 345, 424242, -234};
cs.accept("iarr1-int 排序前:");
cs.accept(Arrays.toString(iarr1));
Arrays.sort(iarr1);
cs.accept("排序后:");
cs.accept(Arrays.toString(iarr1));
// 包装器类型,实际是对象
// 底层使用 ComparableTimSort 算法
// Integer[] iarr2 = {23, 3, 5, -12, 345, 424242, -234, null};
Integer[] iarr2 = {23, 3, 5, -12, 345, 424242, -234};
cs.accept("iarr2-Integer 排序前:");
cs.accept(Arrays.toString(iarr2));
Arrays.sort(iarr2); // 数组存在 null,抛出 NullPointerException
cs.accept("排序后:");
cs.accept(Arrays.toString(iarr2));
// 字符串,就是 对象,同上面的 Integer数组
// String[] sarr1 = {"apple", "tesla", "baidu", "ibm", "google", null};
String[] sarr1 = {"apple", "tesla", "baidu", "ibm", "Google"};
cs.accept("sarr1 排序前:");
cs.accept(Arrays.toString(sarr1));
Arrays.sort(sarr1); // 数组存在 null,抛出 NullPointerException
cs.accept("排序后:");
cs.accept(Arrays.toString(sarr1));
// sort(T[], Comparator<? super T>)
String[] sarr2 = {"apple", "tesla", "baidu", null, "ibm", "alibaba", "Google", "x", null};
cs.accept("sarr2 排序前:");
cs.accept(Arrays.toString(sarr2));
Arrays.sort(sarr2, (s1,s2)->{
// 按照字符串长度排序:短的在前,有null时,null在前
// 支持 null
if (Objects.equals(s1, s2)) {
return 0;
}
if (s1 == null && s2 != null) {
return -1;
}
if (s1 != null && s2 == null) {
return 1;
}
return s1.length() - s2.length();
});
cs.accept("排序后:");
cs.accept(Arrays.toString(sarr2));
}
结果:
------测试sort:
iarr1-int 排序前:
[23, 3, 5, -12, 345, 424242, -234]
排序后:
[-234, -12, 3, 5, 23, 345, 424242]
iarr2-Integer 排序前:
[23, 3, 5, -12, 345, 424242, -234]
排序后:
[-234, -12, 3, 5, 23, 345, 424242]
sarr1 排序前:
[apple, tesla, baidu, ibm, Google]
排序后:
[Google, apple, baidu, ibm, tesla]
sarr2 排序前:
[apple, tesla, baidu, null, ibm, alibaba, Google, x, null]
排序后:
[null, null, x, ibm, apple, tesla, baidu, Google, alibaba]
疑问:
对象数组中的 null 怎么一次性删除?有没有什么 “现成的工具类方法”?
填充同一个数据,可以指定范围。
注意 和 setAll 区分!
public static void testFill() {
cs.accept("------测试fill:");
cs.accept("barr:");
boolean[] barr = new boolean[10];
cs.accept(Arrays.toString(barr));
cs.accept("barr after fill:");
Arrays.fill(barr, true);
cs.accept(Arrays.toString(barr));
cs.accept("barr after fill 2:");
Arrays.fill(barr, 0, 5, false);
cs.accept(Arrays.toString(barr));
// Arrays.fill(barr, -1, 5, false); // ArrayIndexOutOfBoundsException
cs.accept("barr2:");
Boolean[] barr2 = new Boolean[10];
cs.accept(Arrays.toString(barr2));
cs.accept("barr2 after fill:");
Arrays.fill(barr2, true);
cs.accept(Arrays.toString(barr2));
cs.accept("barr2 after fill 2:");
Arrays.fill(barr2, 5, barr2.length, false);
cs.accept(Arrays.toString(barr2));
// Arrays.fill(barr2, 5, 23, false); // ArrayIndexOutOfBoundsException
}
结果:
------测试fill:
barr:
[false, false, false, false, false, false, false, false, false, false]
barr after fill:
[true, true, true, true, true, true, true, true, true, true]
barr after fill 2:
[false, false, false, false, false, true, true, true, true, true]
barr2:
[null, null, null, null, null, null, null, null, null, null]
barr2 after fill:
[true, true, true, true, true, true, true, true, true, true]
barr2 after fill 2:
[true, true, true, true, true, false, false, false, false, false]
按照数组元素序号(从0开始) 填充数据;
不需要序号的话,可以 设置第二个参数 返回其它值,比如,随机数。
public static void testSetAll() {
cs.accept("------测试setAll:");
double[] dbarr = new double[10];
// Double[] dbarr = new Double[10]; // java.lang.ArrayStoreException: java.lang.Integer
cs.accept("dbarr:");
cs.accept(Arrays.toString(dbarr));
Arrays.setAll(dbarr, (ival)->{
return ival * 10;
});
cs.accept("setAll之后:");
cs.accept(Arrays.toString(dbarr));
Double[] dbarr2 = new Double[10];
cs.accept("\ndbarr2:");
cs.accept(Arrays.toString(dbarr2));
Arrays.setAll(dbarr2, (ival)->{
// 转换后无异常
return Double.valueOf(ival) * 10;
});
cs.accept("setAll之后:");
cs.accept(Arrays.toString(dbarr2));
}
结果:
------测试setAll:
dbarr:
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
setAll之后:
[0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0]
dbarr2:
[null, null, null, null, null, null, null, null, null, null]
setAll之后:
[0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0]
根据已有数组,建立新数组;
新数组的 size,可以 更小 或 更大;
更大时,使用 默认值填充——对象的默认值为 null。
public static void testCopyOf() {
cs.accept("------测试copyOf:");
int[] iarr1 = new int[5];
cs.accept("iarr1.length=" + iarr1.length);
cs.accept(Arrays.toString(iarr1));
// 增大
int[] iarr2 = Arrays.copyOf(iarr1, 10);
cs.accept("after copyOf: \niarr2.length=" + iarr2.length);
cs.accept(Arrays.toString(iarr2));
// 减小
int[] iarr3 = Arrays.copyOf(iarr1, 3);
cs.accept("after copyOf: \niarr3.length=" + iarr3.length);
cs.accept(Arrays.toString(iarr3));
// 指定范围copy
String[] sarr1 = {"a", "bc", "def", "ghij", "klmno"};
cs.accept("\nsarr1.length=" + sarr1.length);
cs.accept(Arrays.toString(sarr1));
// 新数组长度减小
String[] sarr2 = Arrays.copyOfRange(sarr1, 2, sarr1.length);
cs.accept("sarr2.length=" + sarr2.length);
cs.accept(Arrays.toString(sarr2));
// 新数组长度增大:新元素的值为 初始值——对象就为null
String[] sarr3 = Arrays.copyOfRange(sarr1, 2, sarr1.length + 5);
cs.accept("sarr3.length=" + sarr3.length);
cs.accept(Arrays.toString(sarr3));
}
结果:
------测试copyOf:
iarr1.length=5
[0, 0, 0, 0, 0]
after copyOf:
iarr2.length=10
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
after copyOf:
iarr3.length=3
[0, 0, 0]
sarr1.length=5
[a, bc, def, ghij, klmno]
sarr2.length=3
[def, ghij, klmno]
sarr3.length=8
[def, ghij, klmno, null, null, null, null, null]
底层都调用了 binarySearch0 函数——二分法检索 算法。
找到了,返回 序号;
没有找到,返回 最后计算的 -(low + 1)——源码如是说。
public static void testBinarySearch() {
cs.accept("------测试binarySearch:");
int[] iarr1 = {23, 3, 5, -12, 345, 424242, -234};
Arrays.sort(iarr1);
cs.accept("iarr1 排序后:\n" + Arrays.toString(iarr1));
cs.accept("find 3: " + Arrays.binarySearch(iarr1, 3));
cs.accept("find 2: " + Arrays.binarySearch(iarr1, 2));
cs.accept("find 20: " + Arrays.binarySearch(iarr1, 20));
cs.accept("find 20: " + Arrays.binarySearch(iarr1, 20000));
Integer[] iarr2 = {23, 3, 5, -12, 345, 424242, -234};
Arrays.sort(iarr2);
cs.accept("iarr2 排序后:\n" + Arrays.toString(iarr2));
cs.accept("find 3: " + Arrays.binarySearch(iarr2, 3));
cs.accept("find 2: " + Arrays.binarySearch(iarr2, 2));
// cs.accept("find 3: " + Arrays.binarySearch(iarr2, null)); // NullPointerException
// 未演示 Comparator版本,需要结合 sort使用——同一个 Comparator
}
结果:
------测试binarySearch:
iarr1 排序后:
[-234, -12, 3, 5, 23, 345, 424242]
find 3: 2
find 2: -3
find 20: -5
find 20: -7
iarr2 排序后:
[-234, -12, 3, 5, 23, 345, 424242]
find 3: 2
find 2: -3
将数组转换为 流对象,再进行 流处理。
public static void testStream() {
cs.accept("------测试Stream:");
cs.accept("iarr1:");
int[] iarr1 = {23, 3, 5, -12, 345, 424242, -234};
IntStream iarr1Stream = Arrays.stream(iarr1);
iarr1Stream.forEach(System.out::println);
// 再次调用异常,因为流被关闭了
// java.lang.IllegalStateException: stream has already been operated upon or closed
// iarr1Stream.forEach(System.out::println);
// 对象 to 流
cs.accept("iarr2:");
Integer[] iarr2 = {23, 3, 5, -12, 345, 424242, -234};
Stream<Integer> iarr2Stream = Arrays.stream(iarr2);
iarr2Stream.forEach(System.out::println);
}
结果:
------测试Stream:
iarr1:
23
3
5
-12
345
424242
-234
iarr2:
23
3
5
-12
345
424242
-234
注:IntStream、LongStream、DoubleStream 需要探究下。
使用并行方式 做prefix计算,会改变 数组——第一个值必变,其它值为 根据 第二个 BinaryOperator 接口对象 计算的结果。
public static void testParallelPrefix() {
cs.accept("------测试parallelPrefix:");
int[] iarr1 = {23, 3, 5, -12, 345, 424242, -234};
cs.accept(Arrays.toString(iarr1));
Arrays.parallelPrefix(iarr1, (i1, i2)->{
return i1 + i2;
});
cs.accept("执行 parallelPrefix(i1 + i2) 后:");
cs.accept(Arrays.toString(iarr1));
}
结果:
------测试parallelPrefix:
[23, 3, 5, -12, 345, 424242, -234]
执行 parallelPrefix(i1 + i2) 后:
[23, 26, 31, 19, 364, 424606, 424372]
并性版的setAll,可以和 setAll比较下性能,千万、上亿级别长度时。
public static void testParallelSetAll() {
cs.accept("------测试parallelSetAll:");
int[] iarr1 = new int[10];
cs.accept(Arrays.toString(iarr1));
Arrays.parallelSetAll(iarr1, (ival)->{
return ival;
});
cs.accept("执行 parallelSetAll 后:");
cs.accept(Arrays.toString(iarr1));
}
结果:
------测试parallelSetAll:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
执行 parallelSetAll 后:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
并行方式排序。
底层使用 ForkJoinPool、TimSort、ArraysParallelSortHelpers 等实现排序。
比较了 100万、1000万、1亿 长度的数组的 sort和parallelSort 的性能,parallelSort在 1000+ 级别优胜。
public static void testParallelSort() {
cs.accept("------测试parallelSort:");
Random rand = new Random(System.currentTimeMillis());
int[] iarr1 = new int[1_000_000];
Arrays.setAll(iarr1, (ival)->{
return rand.nextInt(2000);
});
int[] iarr2 = Arrays.copyOf(iarr1, iarr1.length);
// cs.accept("排序前:iarr1=\n" + Arrays.toString(iarr1));
// cs.accept("排序前:iarr2=\n" + Arrays.toString(iarr2));
cs.accept("");
long s1 = System.nanoTime();
Arrays.parallelSort(iarr1);
long s2 = System.nanoTime();
// cs.accept("排序parallelSort后:iarr1=\n" + Arrays.toString(iarr1));
cs.accept("parallelSort耗时:\n" + (s2-s1) + "ns\n");
long s3 = System.nanoTime();
Arrays.sort(iarr2);
long s4 = System.nanoTime();
// cs.accept("排序sort后:iarr2=\n" + Arrays.toString(iarr2));
cs.accept("sort耗时:\n" + (s4-s3) + "ns");
}
结果:
------测试parallelSort:
parallelSort耗时:
157193900ns
sort耗时:
46495500ns
上面测试是的 100万 数据的数组,此时,sort的性能优于 parallelSort。
将数组长度变更为 1000万、1亿测试,结果分别如下:
------测试parallelSort:
# 1000万:parallelSort 领先
parallelSort耗时:
296669600ns
sort耗时:
414690000ns
------测试parallelSort:
# 1亿:parallelSort 领先更多了
parallelSort耗时:
1501550900ns
sort耗时:
4807463200ns
注:上面测试的电脑CPU为8核心(i5-8250U)。
hashCode
deepHashCode
equals
deepEquals
deepToString
spliterator
做什么用的呢?
后记:
Arrays很强大,作者如下:
* @author Josh Bloch
* @author Neal Gafter
* @author John Rose
* @since 1.2
*/
public class Arrays {