style: candy
[重学Java基础][Java内置工具类][Part.1] Arrays工具类
简介
Arrays位于java.util包下,是一个工具类
全限定路径
java.util.Arrays
提供了一些操作数组的方法 用于简化代码
代码示例
基于Java 9
asList 方法
public static List asList(T… a)
由方法签名可知 此方法是一个泛型方法
功能是把可变参数T… a 传入的可变数组序列转为集合类
要点
-
(1)该方法不适用于基本数据类型(byte,short,int,long,float,double,boolean)
-
(2)该方法将数组与列表链接起来,当更新其中之一时,另一个自动更新
-
(3)不支持add和remove方法
- 实例
转换为Integer类型的List
List<Integer> integerList=Arrays.asList(1,2,3);
转换为String类型的List
List<String> stringList = Arrays.asList("a", "b", "c");
转换为TestObject(自定义类)类型的List
List<TestObject> objectList=Arrays.asList(to1,to2);
从源码看其返回的是一个ArrayList类
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
但是 这个ArrayList 并不是
java.util.ArrayList
而是
java.util.Arrays.ArrayList
是一个Arrays方法的内部类
此Arrays.ArrayList的内部设置的元素集合 private final E[] a
是不可变的 所以Arrays.asList()方法返回的这个集合是不可变的
并未提供add,remove方法
调用add,remove方法会抛出异常
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
构造方法
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
……
}
asList方法新建了一个Arrays.ArrayList对象 调用了其构造方法 传入了一个数组对象
众所周知 java的对象传参就是传递引用
所以传入的是外部对象的引用
则外部对象改变时 此列表list的内容也会改变
如果传入一个基本类型数组 则会被认为只有传入了一个数组对象的引用
而不是其数组内部的所有成员
例如
int[] data = {1,2,3,4,5};
List list = Arrays.asList(data);
System.out.println("列表中的元素数量是:" + list.size());
输出为
列表中的元素数量是:1
不要使用基本类型数组 可以使用包装类型数组
sort方法
sort()排序方法 传入对象进行升序排列
默认使用字典序
方法的重载非常多 用于适配所有基本类型和引用类型
这个排序运用的是优化的快速排序算法TimSort算法 速度很快
所以如果要进行排序 建议都用此方法
比较典型的就是以下几种
默认排序
sort(Object[] a):void
默认排序 指定排序范围
sort(Object[] a, int fromIndex, int toIndex):void
使用指定的比较器规则排序
sort(T[] a, Comparator<? super T> c):void
使用指定的比较器规则排序 且指定排序范围
sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c):void
源码
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
对象类型的比较分成两种 一种是Java6及之前的排序算法legacyMergeSort
一种是Java7之后引入的更严谨 更快速的排序算法TimSort算法
如果非要使用legacyMergeSort JDK运行参数必须配置
-Djava.util.Arrays.useLegacyMergeSort=`true‘
- 实例
默认排序
Integer[] ints = {2, 5, 17, 3, 2, 99, 45, 0};
Arrays.sort(ints);
for (Integer anInt : ints) {
System.out.print(anInt+" ");
}
结果
0 2 2 3 5 17 45 99
默认排序 指定排序起始位置
Integer[] ints2 = {2, 5, 17, 3, 2, 99, 45, 0};
Arrays.sort(ints2,0,3);
for (Integer anInt : ints2) {
System.out.print(anInt+" ");
}
结果 只有前三个进行了排序
2 5 17 3 2 99 45 0
使用指定的比较器排序
逆序排列
Integer[] ints3 = {2, 5, 17, 3, 2, 99, 45, 0};
Arrays.sort(ints3,(x,y)->y.compareTo(x));
for (Integer anInt : ints3) {
System.out.print(anInt+" ");
}
结果
99 45 17 5 3 2 2 0
binarySearch方法
binarySearch()二分搜索方法 查找数组中关键字的下标
使用二分法查找,数组必须有序
要点
1、如果找到关键字,则返回值为关键字在数组中的位置索引,且索引从0开始
2、如果没有找到关键字,返回值为负的插入点值,所谓插入点值就是第一个比关键字大的元素在数组中的位置索引,而且这个位置索引从1开始
3、由于返回值和元素叙述有关 所以调用binarySearch()方法前要先调用sort方法对数组进行排序,否则得出的返回值不定
此方法的重载非常多 用于适配所有基本类型和引用类型
典型
Arrays.binarySearch(Object[] a,Object key):int
Arrays.binarySearch(Object[] a, int fromIndex, int toIndex,Object key):int
源码核心方法
private static int binarySearch0(Object[] a, int fromIndex, int toIndex,
Object key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
@SuppressWarnings("rawtypes")
Comparable midVal = (Comparable)a[mid];
@SuppressWarnings("unchecked")
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; //找到目标
}
return -(low + 1); //未找到目标
}
典型的二分搜索算法
- 实例
binarySearch(Object[] a, Object key)
a: 要搜索的数组
key:要搜索的值
如果key在数组中,则返回搜索值的索引;否则返回-1或负的插入点值。
(从上面源码上可知返回的就是-(low+1))
插入点是索引键将要插入数组的那一点,即第一个大于该键的元素的索引。
要点:
[1] 搜索值不是数组元素,且在数组范围内,从1开始计数,得“ - 插入点索引值”;
[2] 搜索值是数组元素,从0开始计数,得搜索值的索引值;
[3] 搜索值不是数组元素,且大于数组内元素,索引值为 – (length + 1);
[4] 搜索值不是数组元素,且小于数组内元素,索引值为 – 1。
待排序数组
Integer[] array ={1,3,4,5,8,9};
排序
Arrays.sort(array);
int position_6 = Arrays.binarySearch(array,6);
int position_4 = Arrays.binarySearch(array,4);
int position_0 = Arrays.binarySearch(array,0);
int position_10 = Arrays.binarySearch(array,10);
System.out.println("position_6 = "+ position_6 +", position_4 = " + position_4 + ", position_0 = " + position_0 +", position_10 = "+ position_10);
输出
position_6 = -5, position_4 = 2, position_0 = -1, position_10 = -7
第一个查找目标6 原数组中无此元素 所以返回负的插入点值 6在此数组中比5大比8小
排在5后面位置 8前面位置 从1开始计索引序数为5 又是原数组中无的元素 所以最终返回-5
第二个查找目标4 原数组中有此元素 所以返回此元素在数组中的下标索引序数 为2
第三个查找目标0 原数组中无此元素 所以返回负的插入点值 0比此数组中所有元素都小
所以插入点应该在最前面 不在范围索引序数从1开始 返回-1
第三个查找目标10 原数组中无此元素 所以返回负的插入点值 10比此数组中所有元素都大
所以插入点应该在最后面 不在范围索引序数从1开始 最后面则加上数组长度length 最终返回
-(length+1) 即-7
** binarySearch(Object[], int fromIndex, int toIndex, Object key)**
a:要搜索的数组
fromIndex:指定范围的开始处索引(包含)
toIndex:指定范围的结束处索引(不包含)
key:要搜索的值
如果要搜索的元素key在指定的范围内,则返回搜索值的索引;否则返回-1或“-”(插入点)。
要点:
[1] 该搜索键在范围内,但不是数组元素,由1开始计数,得“ - 插入点索引值”;
[2] 该搜索键在范围内,且是数组元素,由0开始计数,得搜索值的索引值;
[3] 该搜索键不在范围内,且小于范围(数组)内元素,返回–(fromIndex + 1);
[4] 该搜索键不在范围内,且大于范围(数组)内元素,返回 –(toIndex + 1)。
Integer[] arr ={1,3,4,5,8,9};
Arrays.sort(arr);
int position_6 = Arrays.binarySearch(arr,1, 4, 6);
int position_4 = Arrays.binarySearch(arr,1, 4, 4);
int position_2 = Arrays.binarySearch(arr,1, 4 ,2);
int position_10 = Arrays.binarySearch(arr,1, 3, 10);
int position_0 = Arrays.binarySearch(arr,1, 3, 0);
System.out.println("position_4 = "+ position_4 +", position_4 = " + position_4 +", position_2 = " + position_2 +", position_10 = "+ position_10 +
", position_0 = " + position_0);
运行结果
position_6 = -5, position_4 = 2, position_2 = -2, position_10 = -4, position_0 = -2
第一个查找目标6 查找数组子段为 1~4 实际包含{3,4,5}三个元素
这个数组段中无此元素 所以返回负的插入点值 在原数组中查找
6在原数组中比5大比8小 插入点的索引序数为5 所以返回-5
第二个查找目标4 查找数组子段为 1~4 实际包含{3,4,5}三个元素 子段中有此元素 所以返回此元素在数组中的下标索引序数 为2
第三个查找目标2 查找数组子段为 1~4 实际包含{3,4,5}三个元素 子段中无此元素 所以返回负的插入点值 序数从1开始 2在原数组中的插入点 1和3之间 返回-2
第四个查找目标10 查找数组子段为 1~3 实际包含{3,4}两个元素 子段中无此元素 所以返回负的插入点值 10也不在原数组中 所以插入点为子段尾部 序数从1开始 返回-4
第五个查找目标0 查找数组子段为 1~3 实际包含{3,4}两个元素 子段中无此元素 所以返回负的插入点值 0也不在原数组中 所以插入点为子段首部 序数从1开始 返回-2
toString方法 deepToString方法
toString()数组转换为String方法
deepToString()深度数组转换为String方法 递归的把所有元素中是数组的内容转为字符串输出
此方法的重载较多 用于适配所有基本类型和引用类型
示例
Integer[] arrayTest={6,1,9,2,5,7,6,10,6,12};
//数组toString
System.out.println(Arrays.toString(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}};
//二维数组toString
System.out.println(Arrays.toString(stuGrades));
结果
[[Ljava.lang.Integer;@4d1b0d2a, [Ljava.lang.Integer;@954b04f, [Ljava.lang.Integer;@149494d8]
所以多维数组必须用deepToString()
Integer[][] stuGrades={{1,3,5,7,9},{2,4,6,8},{1,5,10}};
//二维数组deepToString
System.out.println(Arrays.deepToString(stuGrades));
结果
[[1, 3, 5, 7, 9], [2, 4, 6, 8], [1, 5, 10]]
对象数组
DeepClazz deepClazz=new DeepClazz(1,"a");
DeepClazz deepClazz2=new DeepClazz(2,"b");
DeepClazz[] deepClazzes={deepClazz,deepClazz2};
System.out.println(Arrays.deepToString(deepClazzes));
结果
[DeepClazz{a=1, string='a'}, DeepClazz{a=2, string='b'}]
注意 对象ToString的正确输出结果需要正确重写对象的ToString()方法
copyOf方法
返回一个增大了且包含有原数组的所有内容的新数组方法
其实就是数组扩容方法
典型方法
public static T[] copyOf(T[] original, int newLength)
要点
1.original为原数组 返回一个新的数组
2.newLength一般大于原数组大小值 为扩容后新数组的大小
3.若newLength小于原数组大小值 则新数组内容为原数组对应长度的内容
4.扩容空间以null填充
源码
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
读源码 核心是调用了 System.arraycopy进行了内容复制
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
复制的新数组长度为newLength
实例
Integer[] arrayTest={6,1,9,2,5,7,6,10,6,12};
Integer[] arrayTest2=Arrays.copyOf(arrayTest,11);
Integer[] arrayTest3=Arrays.copyOf(arrayTest,4);
System.out.println(Arrays.toString(arrayTest));
System.out.println(Arrays.toString(arrayTest2));
System.out.println(Arrays.toString(arrayTest3));
结果
[6, 1, 9, 2, 5, 7, 6, 10, 6, 12]
[6, 1, 9, 2, 5, 7, 6, 10, 6, 12, null] 扩容一位 填充null
[6, 1, 9, 2] 原数组前4位
除了copyOf方法外 还有一个类似的指定复制范围的copyOfRange方法
指定从原数组复制内容的起始结束位置
public static T[] copyOfRange(T[] original, int from, int to)
Integer[] arrayTest3=Arrays.copyOfRange(arrayTest,1,6);
System.out.println(Arrays.toString(arrayTest3));
结果
[1, 9, 2, 5, 7]
hashCode和deepHashCode方法
返回参数散列码的方法
public static int hashCode(Object a[])
public static int deepHashCode(Object a[])
源码
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
public static int deepHashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a) {
int elementHash = 0;
if (element instanceof Object[])
elementHash = deepHashCode((Object[]) element);
else if (element instanceof byte[])
elementHash = hashCode((byte[]) element);
else if (element instanceof short[])
elementHash = hashCode((short[]) element);
else if (element instanceof int[])
elementHash = hashCode((int[]) element);
else if (element instanceof long[])
elementHash = hashCode((long[]) element);
else if (element instanceof char[])
elementHash = hashCode((char[]) element);
else if (element instanceof float[])
elementHash = hashCode((float[]) element);
else if (element instanceof double[])
elementHash = hashCode((double[]) element);
else if (element instanceof boolean[])
elementHash = hashCode((boolean[]) element);
else if (element != null)
elementHash = element.hashCode();
result = 31 * result + elementHash;
}
return result;
}
HashCode()方法的源码还是比较简单的 所有元素的hashcode取值之和 (所以这要求对象正确的重写hashCode()方法)
deepHashCode()方法就略复杂一些 当element为 基本数据类型数组时,elementHash使用 Arrays.hashCode(Object a[])计算。当element为 非数组时,elementHash使用 element.hashCode()计算 element为子类时 使用Arrays.deephashCode递归计算
equals和deepEquals方法
- euqlas()方法 比较两个参数是否值相等
重载比较多
典型
public static boolean equals(Object[] a, Object[] a2)
比较入参 对象数组Object[] a, Object[] a2是否值相等
源码
public static boolean equals(Object[] a, Object[] 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 (!Objects.equals(a[i], a2[i]))
return false;
}
return true;
}
源码中经过一系列简单判断后 还是循环调用equals方法比较每个元素是否值相等
要点
1.要比较的对象 必须正确重写equals方法
- deepequals方法 比较两个参数是否深度值相等
public static boolean deepEquals(Object[] a1, Object[] a2)
public static boolean deepEquals(Object[] a1, Object[] a2) {
if (a1 == a2)
return true;
if (a1 == null || a2==null)
return false;
int length = a1.length;
if (a2.length != length)
return false;
for (int i = 0; i < length; i++) {
Object e1 = a1[i];
Object e2 = a2[i];
if (e1 == e2)
continue;
if (e1 == null)
return false;
// Figure out whether the two elements are equal
boolean eq = deepEquals0(e1, e2);
if (!eq)
return false;
}
return true;
}
static boolean deepEquals0(Object e1, Object e2) {
assert e1 != null;
boolean eq;
if (e1 instanceof Object[] && e2 instanceof Object[])
eq = deepEquals ((Object[]) e1, (Object[]) e2);
else if (e1 instanceof byte[] && e2 instanceof byte[])
eq = equals((byte[]) e1, (byte[]) e2);
else if (e1 instanceof short[] && e2 instanceof short[])
eq = equals((short[]) e1, (short[]) e2);
else if (e1 instanceof int[] && e2 instanceof int[])
eq = equals((int[]) e1, (int[]) e2);
else if (e1 instanceof long[] && e2 instanceof long[])
eq = equals((long[]) e1, (long[]) e2);
else if (e1 instanceof char[] && e2 instanceof char[])
eq = equals((char[]) e1, (char[]) e2);
else if (e1 instanceof float[] && e2 instanceof float[])
eq = equals((float[]) e1, (float[]) e2);
else if (e1 instanceof double[] && e2 instanceof double[])
eq = equals((double[]) e1, (double[]) e2);
else if (e1 instanceof boolean[] && e2 instanceof boolean[])
eq = equals((boolean[]) e1, (boolean[]) e2);
else
eq = e1.equals(e2);
return eq;
}
deepEquals()方法比equals()复杂一些 当Object[]为 基本数据类型数组时,使用 Arrays.equals(Object a[])计算。当element为 非数组时,Object[]为对象子类时 使用Arrays.deepequals()递归计算
要点
1.要比较的对象 必须正确重写equals方法
实例
DeepClazz deepClazz1=new DeepClazz(1,"a");
DeepClazz deepClazz2=new DeepClazz(2,"b");
DeepClazz[] deepClazzes1={deepClazz1,deepClazz2};
DeepClazz[] deepClazzes2={deepClazz2,deepClazz1};
System.out.println(Arrays.equals(deepClazzes1,deepClazzes2););
System.out.println(Arrays.deepEquals(deepClazzes1,deepClazzes2));
fill方法
全填充方法
public static void fill(int[] a, int val)
将数组(int[] a所有元素 填充为val的值
源码
public static void fill(Object[] a, Object val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
fill的源码 真是分外简单
从这也能看出 fill是直接操作原数组的 并不创建新数组
实例
int[] arrays=new int[10];
Arrays.fill(arrays,5);
System.out.println(Arrays.toString(arrays));
Arrays.fill(arrays,5);
System.out.println(Arrays.toString(arrays));
指定起始位置填充
Arrays.fill(arrays,0,4,6);
System.out.println(Arrays.toString(arrays));
结果
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
[6, 6, 6, 6, 5, 5, 5, 5, 5, 5]
spliterator方法
返回一个数组对应的分割迭代器
public static Spliterator spliterator(T[] array)
分割迭代器是一个较复杂特性
具体可看这篇文章
JAVA8 stream 中Spliterator的使用
并行方法
Arrays工具还提供了三种并行方法
并行二元迭代方法
Arrays.parallelPrefix(T[] array, BinaryOperator op);
对数组T[] array 的所有元素 都进行BinaryOperator op传入的操作
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));
//二元迭代,对原数组内容进行二元操作
Arrays.parallelPrefix(arrayPP1,(x,y)->x*y);
System.out.println( Arrays.toString(arrayPP1));
Integer[] arrayPP2 = Arrays.copyOf(arrayTest, arrayTest.length);
//在指定下标范围内,对原数组内容进行二元操作,下标含头不含尾
Arrays.parallelPrefix(arrayPP2,0,5,(x,y)->x*y);
System.out.println( Arrays.toString(arrayPP2));
结果
[6, 1, 9, 2, 5, 7, 6, 10, 6, 12]
[6, 6, 54, 108, 540, 3780, 22680, 226800, 1360800, 16329600]
[6, 6, 54, 108, 540, 7, 6, 10, 6, 12]
并行全部按表达式运算方法
Arrays.parallelSetAll(T[] array, IntFunction<? extends T> generator));
对数组T[] array的所有元素 都进行函数式接口IntFunction<? extends T> generator 传入的操作
Integer[] arrayPSA = Arrays.copyOf(arrayTest, arrayTest.length);
//对原有数组对每个元素重新赋值,下面例子是 下标*5 然后赋到数组对应元素
Arrays.parallelSetAll(arrayPSA,a->a<<2);
System.out.println(Arrays.toString(arrayPSA));
结果
[0, 4, 8, 12, 16, 20, 24, 28, 32, 36]
并行排序方法
Arrays.parallelSort((T[] a);
和排序方法操作一样 只不过底层采用了并行流 可以更快速的处理大数据量的排序