Arrays.java 源码分析
package java.util;
import java.lang.reflect.Array;
import java.util.concurrent.ForkJoinPool;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.DoubleBinaryOperator;
import java.util.function.IntBinaryOperator;
import java.util.function.IntFunction;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.IntUnaryOperator;
import java.util.function.LongBinaryOperator;
import java.util.function.UnaryOperator;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* 这个类提供大量的操作数组方法(排序、查找等)。这个包含一个静态工厂方法,用于将一个
* 数组创建一个列表。如果数组引用为null,所有将方法抛出一个异常
* (NullPointerException),除非是标记了的。这个类的方法都有简短的说明,这些说明是对
* 实现应该注意的点,而非实现的说明,开发者应该*定制自己的算法,主要符合实现的要求
* (比如排序算法不一定要归并排序,但必须是稳定的)
* @since 1.2
*/public class Arrays {
/**
* 并行排序的最小数组长度,数组长度小于这个数则不在划分数组。使用较小的数组长度会导致多个排序任务竞争内存而降低并行排序的速度。*/
private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
// 设置默认构造函数为私有的,确保不能被实例化
private Arrays() {}
//……,方法省略,逐一单独列出
}
一、 排序相关
1. 自然排序的比较器类
final修饰,不可被继承,该类实现了比较器( comparator
),排序时,不指定比较器,使用该比较器
/**
* 一个实现了对于一组可比较的元素的自然排序的比较器。当用户提供的比较器为null时使用
* 该比较器。 为了见哈U底层实现的共享,compare方法声明其第二个参数为Object类型的。
*
* Arrays类的实现(继承,重写该方法)需要注意:对于当前比较器,(这是一个经验问题)
* ComparableTimSort是否比TimeSort有性能优势。如果没有,则应该不使用
* ComparableTimSort。目前还没有实例来分割数组进行并行排序,因此,所有共有对象的
* 并行排序方法都使用相同的比较器作为基础实现
*/
static final class NaturalOrder implements Comparator<Object> {
@SuppressWarnings("unchecked")
public int compare(Object first, Object second) {
return ((Comparable<Object>)first).compareTo(second);
}
//提供一个单例
static final NaturalOrder INSTANCE = new NaturalOrder();
}
2. 范围检查方法 rangeCheck()
用于检查 fromIndex
和 toIndex
是否在范围内,如果不在则抛出异常
private static void rangeCheck(int arrayLength, int fromIndex, int toIndex) {
if (fromIndex > toIndex) { //开始下标大于结束下标,抛出异常
throw new IllegalArgumentException(
"fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
}
if (fromIndex < 0) {
throw new ArrayIndexOutOfBoundsException(fromIndex);
}
if (toIndex > arrayLength) {
throw new ArrayIndexOutOfBoundsException(toIndex);
}
}
3. 排序 sort(int[] a)
在排序中使用 DualPivotQuicksort
( Dual-Pivot
快速排序)进行排序,比普通的快速排序更快,在其他快速需要时间复杂度的为 O(n^2)
的数据集,该快速排序能够达到 O(nlogn)
/*
* 排序方法。注意一点,所有的公有的排序方法都使用相同的形式:如果有必要进行参数检查
* ,然后再将参数扩展成内部私有的实现方法的参数形式(除了legacyMergeSort)。
*/
/**
* 将数组按照数值顺序升序排序
*
* 实现注意点: 排序算法使用快速排序算法(Dual-Pivot Quicksort)。对于一些会使其他
* 快速排序的算法的时间复杂度达到(o(n^2))的数据集, 该算法可以提供O(nlogn)的时间
* 复杂度,这是一个比传统的(单轴)快速排序更快的实现。
* @param a 要排序的数组
*/
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
4. 对特定范围排序 sort(int[] a, int fromIndex, int toIndex)
对数组 [formIndex,toIndex)
范围之内的元素进行排序,其余与上面的方法一致
/**
* 对特定范围数组升序排序,数组范围不正确将会抛出IllegalArgumentException或
* ArrayIndexOutOfBoundsException
*/
public static void sort(int[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex); //检查范围是否正确
DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
}
注:对于其他的重载方法(3,4介绍的方法),除排序的数组类型不同,其他都一致,不再分析,对于float,double的数组,排序算法不能提供所有元素的全序关系排序(精度原因,Folat.NAN
不能比较大小,使用 Float.compareTo()
方法时,Folat.NAN
比任何值大,且与自己相等,-0.0
和 0.0
也会出现不同的情况)
5. 并行排序 parallelSort(byte[] a)
提供一个数值升序排序,将数组分成多个子数组,然后进行排序,下一层继续继续分,直到达到设置的最小粒度,然后调用相应的 Dual-Pivot快速排序
进行排序,最后将排序好的数组分片合并,这个算法需要一个不大于原数组大小的额外空间,使用 ForkJoin common pool
来执行并行的排序任务。
/**
* 提供一个数值升序排序
*
* 该方法使用算法是一个并行排序算法-合并已经排好序的数组的子数组。当子数组长度达到最小粒度,
* 子列表使用合适的Arrays.sort()来进行排序。如果数组小于设定的最小粒度,直接使用合适的
* Arrays.sort()来进行排序。这个算法需要一个不大于原数组大小的额外空间,使用ForkJoin common pool
* (ForkJoinPool#commonPool())来执行并行的排序任务。
*/
public static void parallelSort(byte[] a) {
int n = a.length, p, g;
//如果数组长度小于分组的最小粒度或者只有一个执行线程,使用Dual-Pivot快速排序进行排序
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
DualPivotQuicksort.sort(a, 0, n - 1);
else
//g表示每一分组的数量,前面三个参数分别为排序数组开始位置,需要排序的长度和额外空间的开始位置
new ArraysParallelSortHelpers.FJByte.Sorter
(null, a, new byte[n], 0, n, 0,
((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g).invoke();
}
6. 特定范围的并行排序
除排序的范围发生变化之外,与上面的方法没有任何区别,需要检查数组的范围是否正确
public static void parallelSort(byte[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);
int n = toIndex - fromIndex, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
DualPivotQuicksort.sort(a, fromIndex, toIndex - 1);
else
new ArraysParallelSortHelpers.FJByte.Sorter
(null, a, new byte[n], fromIndex, n, 0,
((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g).invoke();
}
同样的,其他的重载方法与5,6一样,数组类型不同。对于浮点数排序算法不能提供所有元素的全序关系排序
7. 泛型类型的并行排序
需要排序的类必须实现Comparable,必须是可比较的,最终使用的TimeSort进行排序,其他与上面的并行排序没有区别,可能会抛出 ClassCastException
和 IllegalArgumentException
/**
* 根据自然顺序,对泛型类型数组升序排序。数组的所有元素必须实现Comparable。
* 进一步说,所有元素必须是可比较的。也即数组中的e1,e2,e1.compareTo(e2)必须不能抛出异常
*
* 这个排序必须保证是稳定的:相同的元素不应该重新排序(相同元素的相对位置不能改变)。
*
* @param <T> 需要排序的数组类型
* @param a 排序的数组
* @throws ClassCastException 存在元素,不可相互比较
* @throws IllegalArgumentException (optional) 存在元素自然顺序违反Comparable的规定的顺序
*/
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> void parallelSort(T[] a) {
int n = a.length, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
TimSort.sort(a, 0, n, NaturalOrder.INSTANCE, null, 0, 0);
else
new ArraysParallelSortHelpers.FJObject.Sorter<T>
(null, a,
(T[])Array.newInstance(a.getClass().getComponentType(), n),
0, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g, NaturalOrder.INSTANCE).invoke();
}
并行排序中对于基本类型使用的是Dual-Pivot
快速排序,其他类型的排序使用TimeSort
排序
8. 特定范围的泛型类型并行排序
与上面的方法一致,只是范围指定为[fromIndex, toIndex)
,需要对范围进行检查
/**
* @throws IllegalArgumentException if {@code fromIndex > toIndex} or
* (optional) if the natural ordering of the array elements is
* found to violate the {@link Comparable} contract
* @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or
* {@code toIndex > a.length}
* @throws ClassCastException if the array contains elements that are
* not <i>mutually comparable</i> (for example, strings and
* integers).
*/
public static <T extends Comparable<? super T>>
void parallelSort(T[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);
int n = toIndex - fromIndex, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
TimSort.sort(a, fromIndex, toIndex, NaturalOrder.INSTANCE, null, 0, 0);
else
new ArraysParallelSortHelpers.FJObject.Sorter<T>
(null, a,
(T[])Array.newInstance(a.getClass().getComponentType(), n),
fromIndex, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g, NaturalOrder.INSTANCE).invoke();
}
9. 带比较器的并行排序
排序时使用比较器的得到的顺序进行排序,排序算法是稳定的,该方法可能会抛出 ClassCastException
或 IllegalArgumentException
/**
* 排序时使用比较器的得到的顺序进行排序,所有的元素必须是可以通过比较器相互比较的。
* 对于数组中的e1和e2,c.compare(e1, e2)必须不能抛出ClassCastException异常
*
* 这个排序必须保证是稳定的:相同的元素不应该重新排序(相同元素的相对位置不能改变)。
*
* @param <T> 需要排序的数组类型
* @param a 要排序的数组
* @param cmp 决定数组元素顺序的比较器,如果为null,使用默认的自然顺序比较器
* @throws ClassCastException if the array contains elements that are
* not <i>mutually comparable</i> using the specified comparator
* @throws IllegalArgumentException (optional) if the comparator is
* found to violate the {@link java.util.Comparator} contract
*/
@SuppressWarnings("unchecked")
public static <T> void parallelSort(T[] a, Comparator<? super T> cmp) {
if (cmp == null)
cmp = NaturalOrder.INSTANCE;
int n = a.length, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
TimSort.sort(a, 0, n, cmp, null, 0, 0);
else
new ArraysParallelSortHelpers.FJObject.Sorter<T>
(null, a,
(T[])Array.newInstance(a.getClass().getComponentType(), n),
0, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g, cmp).invoke();
}
10. 特定范围的带比较器的并行排序
与上面的方法一样,只是排序范围定义为[fromIndex, toIndex)
, 需要对范围进行检查是否符合要求
11. 复杂类型数组排序
复杂类型数组的排序使用,在以后的版本将会被废弃
/**
* 一个老的归并排序的实现可以使用系统的属性来设置。因为循环依赖,在一个封闭类内部不能有静态的boolean类型。
* 这个类将在以后的版本中废弃。
*/
static final class LegacyMergeSort {
private static final boolean userRequested =
java.security.AccessController.doPrivileged(
new sun.security.action.GetBooleanAction(
"java.util.Arrays.useLegacyMergeSort")).booleanValue(); //获得系统属性判断是否使用LegacyMergeSort归并排序
}
12. 对复杂类型的数组进行排序
对复杂类型数组进行排序的方法,数组中所有元素必须实现Comparable
接口。在一般情况下使用TimSort
进行排序,如果有指定,则使用传统的归并排序进行排序,根据数组原始排序情况选择不同的排序算法进行排序
/**
* 根据Comparable比较的自然顺序,对数组进行升序排序。数组中所有元素必须实现Comparable接口、
* 所有元素必须是相互可比较的,即对数组中的元素e1和e2,e1.compareTo(e2)不能抛出ClassCastException异常
*
* 这个排序必须保证是稳定的:相同的元素不应该重新排序(相同元素的相对位置不能改变)。
*
*
* 实现注意点:当数组部分有序则实现一个远远小于nlgn次比较的稳定的、自适应的可迭代的归并排序,
* 当数组是随机排序的,则使用传统的归并排序。如果数组基本排好序,则需要大约n次比较。
* 临时的空间需要从一个很小的常量(基本排好序)到大约n/2(随机排序的情况),n为数组长度。
*
* 该方法的实现对于升序和降序有相同的优势,并且在同一数组的不同部分利用升序或降序的优势
* 它可以很好的适应与两个以上的数组的合并:先简单的连接数组然后对连接后的数组进行排序
*
* 排序的算法使用TimSort,是对归并排序的优化,是一个归并排序做了大量优化的版本
*
* @param a 排序的数组
* @throws ClassCastException if the array contains elements that are not
* <i>mutually comparable</i> (for example, strings and integers)
* @throws IllegalArgumentException (optional) if the natural
* ordering of the array elements is found to violate the
* {@link Comparable} contract
*/
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
对于Arrays.sort()方法,传入的是基本类型则使用Dual-Pivot
快速排序,其他类型的排序使用TimeSort
排序
13. legacyMergeSort
将在以后的版本中被废弃
/** 将在以后的版本中被废弃 */
private static void legacyMergeSort(Object[] a) {
Object[] aux = a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
14. 对复杂类型的特定范围的排序
与12方法一致,只是范围限制为[fromIndex, toIndex)之内,使用的排序算法稳定
/**
* @param a 需要排序的数组
* @param fromIndex 开始的下标,包括
* @param toIndex 结束的下标,不包括
* @throws IllegalArgumentException if {@code fromIndex > toIndex} or
* (optional) if the natural ordering of the array elements is
* found to violate the {@link Comparable} contract
* @throws ArrayIndexOutOfBoundsException if {@code fromIndex < 0} or
* {@code toIndex > a.length}
* @throws ClassCastException if the array contains elements that are
* not <i>mutually comparable</i> (for example, strings and
* integers).
*/
public static void sort(Object[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex);
else
ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
}
15. 带范围的legacyMergeSort
与13方法一致,将来的版本会被废弃,范围限制为[fromIndex, toIndex)之内
private static void legacyMergeSort(Object[] a,
int fromIndex, int toIndex) {
Object[] aux = copyOfRange(a, fromIndex, toIndex);
mergeSort(aux, a, fromIndex, toIndex, -fromIndex);
}
16. 常量:INSERTIONSORT_THRESHOLD
使用插入排序的阈值,当小于等于这个阈值时,将使用插入排序
/**
* Tuning parameter: 数组长度小于等于该值将使用插入排序
* 优先于归并排序使用
* 将在将来版本被废弃
*/
private static final int INSERTIONSORT_THRESHOLD = 7;
17. 归并排序实现
这个方法是归并排序真正的实现,元素需要实现Comparable接口
/**
* Src 是原始数组,开始下标为0
* Dest 是目的数组,有一个偏移,可能比原始数组更大
* low dest数组开始排序下标
* high dest数组结束排序的下标
* off 是一个偏移,保证low和high在src中保持一致
* 将在将来的版本被废弃
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// 数组长度较小时,使用插入排序
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
// 递归排序, 将数据分成更小的子数组
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off); //递归
mergeSort(dest, src, mid, high, -off); //递归
// 如果已经排好序,直接将数组从src拷贝到dest,这是对于已经基本排序的数组快速的排序的优化
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
// 合并两个子数组,形成一个排序数组
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
18. 交换数据
交换数组中的数据
private static void swap(Object[] x, int a, int b) {
Object t = x[a];
x[a] = x[b];
x[b] = t;
}
19. 带比较器的复杂类型的排序
与上面的方法(sort())一致,这是元素的大小使用比较器来决定,其他与12一致,同理,legacyMergeSort(T[] a, Comparator
20. mergeSort(Object[] src, Object[] dest, int low, int high, int off, Comparator c)
这个方法是带有比较器的归并排序的实现,除了元素之间的比较使用比较器进行之外,与上面的17一致,此处不再分析
二、并行前缀计算相关
21. parallelPrefix(T[] array, BinaryOperator<T> op)
并行前缀计算,利用op操作,将当前值和前一个值(数组的第二个元素开始)传入op特定的函数内,进行计算,代替当前的值
/**
* 在数组元素原位置根据提供的操作,进行并行计算
* 例如对于并行前缀加操作(从第二个元素开始,与前一个元素相加),原始数组为[2, 1, 0, 3],计算后[2, 3, 3, 6]
* 对于大数组,并行前缀计算通常比连续的循环效率高。
*
* @param <T> 需要计算的数组的类型
* @param array 被方法在原来位置进行修改的数组
* @param 进行累计计算的没有副作用的,相关联的操作
* @throws NullPointerException if the specified array or function is null
*/
public static <T> void parallelPrefix(T[] array, BinaryOperator<T> op) {
Objects.requireNonNull(op);
if (array.length > 0)
new ArrayPrefixHelpers.CumulateTask<>
(null, op, array, 0, array.length).invoke();
}
22. public static <T> void parallelPrefix(T[] array, int fromIndex, int toIndex, BinaryOperator<T> op)
与上述方法一样,只是范围限定在[fromIndex, toIndex),也即从fromIndex+1开始计算。需要检查范围是否符合要求
其他不同的类型的参数的重载方法也都具有相同的功能,不再一一分析
三、查找相关
23. 二分查找
不同类型的参数的重载方法,以下面的方法为例进行分析,其他不在分析
使用二分查找算法,在数组中查找给定的值。数组必须是之前排好序的。如果没有排序好,查找结果是不确定的。如果数组中包含多个给定的元素,不能保证哪个元素将会被找到。返回被找到元素的下标,如果没有找到,则返回-插入点-1
。正真调用的是binarySearch0方法
/**
* 在long数组中使用二分查找算法,查找给定的元素。数组必须是之前排好序的。如果没有排序好,查找结果是不确定的。
* 如果数组中包含多个给定的元素,不能保证哪个元素将会被找到
*
* @param a the array to be searched
* @param key the value to be searched for
* @return 返回被找到元素的下标,如果没有找到,则返回"-插入点-1"。
* 插入点被定义为在哪个点插入数组:第一个比大于需要查找的值得下标,或者当数组中所有元素小于
* 需要查找的元素,返回的是数组长度(a.lenght)位置。保证查找到元素时,返回的是大于等于的下标。
*/
public static int binarySearch(long[] a, long key) {
return binarySearch0(a, 0, a.length, key);
}
25. public static int binarySearch(long[] a, int fromIndex, int toIndex,long key)
与上述方法一致,只是范围限定在[fromIndex, toIndex)之间,需要做范围检查
24. 二分查找算法的实现
二分查找算法真正的实现,在指定的范围的查找,这个方法中没有做范围检查,在上层调用中已经做范围检查。其他重载方法一样,只是查找的数据类型不同。对于对象类型的比较,通过实现Comparable,使用compareTo(),或者传入比较器,通过比较器比较元素的大小,比较器和Comparable接口的相关规则在之前的不同方法中已经多次分析,此处不再单独分析
private static int binarySearch0(long[] a, int fromIndex, int toIndex,
long key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1; //获得中间下标,使用 >>> 1 右移一位,相当于除2,高位补0
long midVal = a[mid];
if (midVal < key) //当中间的值小于查找的值,将范围限定在[mid+1,high]范围继续迭代
low = mid + 1;
else if (midVal > key) //当中间的值大于查找的值,将范围限定在[low,mid-1]范围继续迭代
high = mid - 1;
else
return mid; // 找到值,返回下标
}
return -(low + 1); // 返回插入位置+1的相反数
}
四、等同测试
25. equals(a,b)
基本类型的数组,对象类型的数组数组之间的相等比较,所有的重载方法,除了比较元素之间是否相等不同外,其余都相同,不在一一分析
*比较两个数组是否相同,首先比较两个数组是否是同指向同一数组,如果是则返回true,不同则继续判断,若两个数组存在一个为null,则不相同。然后比较数组之间的元素个数是否相同,个数不同返回false,否则比较两个数组对应位置的数据是否相同,如果不是返回false,否则返回true。
对于double和float类型,由于精度原因,之间使用!=
或==
比较会存在一定问题,使用Double.doubleToLongBits(a[i])!=Double.doubleToLongBits(a2[i])
和Float.floatToIntBits(a[i])!=Float.floatToIntBits(a2[i])
来比较元素的之间是否相等。
对于对象,两个null对象之间是相等的,否则使用对象的equals方法来判断对象之间是否相等*
/**
*
* @param a one array to be tested for equality
* @param a2 the other array to be tested for equality
* @return <tt>true</tt> if the two arrays are equal
*/
public static boolean equals(long[] a, long[] 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;
}
五、填充元素
26. fill(Object[] a, Object val)
填充元素,往数组中填充特定的元素,所有元素都填充相同的值,所有的不同类型的重载实现一样,特定范围的填充,是在特定的范围填充需要的值。所有重载方法不在一一分析
public static void fill(Object[] a, Object val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
六、拷贝数组
27. public static <T> T[] copyOf(T[] original, int newLength)
拷贝数组,并返回,需要传入原数组和新数组的长度,对象类型的数组,需要调用public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)
,将数组转换成新的类型。当前方法,不需要改变数组类型,传入原数组的类型即可,在那个方法中调用System.arraycopy()
来拷贝数据。其他基本类型的方法类似,都使用System.arraycopy()
来拷贝数据,不再一一分析。对于特定的范围的数组拷贝copyOfRange(T[] original, int from, int to)
,新的数组是to-from,其余一致。
/**
* 拷贝特定的数组,如果新的数组长度大于被拷贝的数组,则在超出部分填充null,若新的数组长度小于
* 被拷贝的数组,则拷贝新数组长度个对应的数据。对于所有两个数组都有效的索引号(下标),两个数组对应
* 位置数据相同。对于新数组有效的,但是对原数组无效的下标,在新数组填充null。这些下标当且仅当在新数组
* 的长度大于原数组的长度,返回的数组的类型与原数组一致
*
* @param <T> 数组的类型
* @param original 被拷贝的数组
* @param newLength 返回的数组(拷贝的数组)的长度
* @return a copy of the original array, truncated or padded with nulls
* to obtain the specified length
* @throws NegativeArraySizeException if <tt>newLength</tt> is negative
* @throws NullPointerException if <tt>original</tt> is null
* @since 1.6
*/
@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
28. 将数组拷贝成新类型的数组
真正拷贝对象类型数组的方法,最后一个参数是新数组的类型,将一个数组拷贝成newType类型的数组,如果与原数组一致,即普通的数组拷贝,调用System.arraycopy()
来拷贝数据
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;
}
七、其他
29. asList(T… a)
传入可变长的参数,也即数组。根据传入的数组生成List
,可以创建一个固定大小的列表
/**
* 返回指定数组支持的固定大小的列表。(Changes to the returned list "write through" to the array.)
* 这个方法和Collection的toArray方法是数组和集合之间的桥梁。返回的list是可序列化和实现了RandomAccess
*
* 这个方法方便地创建一个固定大小的list,被初始化包含多个元素
*
* @param <T> 数组的类型
* @param a 支撑列表的数组
* @return 数组的list视图
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a); //内部类,非java.util.ArrayList.
}
30. 内部类ArrayList<E>
不同于java.util.ArrayList的实现,是固定大小的,一旦传入数组后,列表的数量就确定了,不可改变,可以改变数组元素的值
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a; //保存数据,指向传入的数组,final一旦确定不可改
ArrayList(E[] array) {
a = Objects.requireNonNull(array); //指向传入的数组,要求传入的数组不为空
}
@Override
public int size() { //列表的大小,也即数组的大小,这个ArrayList是固定大小的,
return a.length;
}
//创建一个数组,浅复制
@Override
public Object[] toArray() {
return a.clone();
}
//当传入的数组大小小于列表元素个数,调用外部的copyOf方法,将生成特定类型的数组
//否则,将列表复制到传入的数组,并将超出的部分设置为null(复制对象引用到数组)
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
//获得index处的值
@Override
public E get(int index) {
return a[index];
}
//设置index处的值,并返回原来的值
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
//查找特定元素的下标,当o=null,则是找列表中的null元素
//否则使用equals判断是否相同,返回相等的值的索引
//如果不存在,则返回-1;
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
//是否包含元素o,调用indexOf(),返回数据在列表中的位置,不存在返回-1
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//创建一个Spliterator提供列表的遍历和元素分割
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
//遍历元素,每次遍历对元素执行特定的操作(根据传入的参数)
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
//根据传入的操作,替换元素的值
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
//排序,根据比较器比较大小,调用外部类对应的排序方法
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
31. hashCode(long[] a)
包括基本数据类型数组的参数和Object[]类型的重载,实现都类似,只分析其中之一。对于Object[]数组,数组中可以直接或间接的引用数组本身或则其他数组,hashCode中会调用每个元素的hashCode
32. deepHashCode(Object a[])
深层hashCode,当数组中的元素包含数组时,需要使用该方法,hashCode基于数组中的元素和数组元素生成,直至到达多维数组最后一层。数组不允许间接或直接引用自己,这样会造成死循环
/**
* 根据深层内容(多层,多维数组),返回一个hashCode。如果数组包含其他数组作为元素,hashCode基于
* 数组中的数组元素生成,这样循环下去。这个方法不允许数组包含数组本身,不管是直接引用还是间接引用
* 会造成死循环
*
* 对于两个数组a,b,Arrays.deepEquals(a, b)相当于Arrays.deepHashCode(a) == Arrays.deepHashCode(b)
*
* 这个方法返回的值和通过List的hashCode方法一样如果列表中的数据和数组中的数据对应位置的值相同,有一点不同:
* 如果数组中的元素时一个数组,不是数组的hashCode函数,而是,如果元素是一个基本类型的数组,
* 使用Arrays.hashCode(e)根据每个元素生成hashCode;如果是引用(对象)数组,使用Arrays.deepHashCode(e)
* 如果是null,则是0;
*
* @param a the array whose deep-content-based hash code to compute
* @return a deep-content-based hash code for <tt>a</tt>
* @see #hashCode(Object[])
* @since 1.5
*/
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); //如果数组元素时Object[],递归调用
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;
}
33. deepEquals(Object[] a1, Object[] a2)
判断两个数组是否深度相等,相等则返回true。使用于数组中嵌套数组的情况。如果数组间接的或者直接的引用自身,将导致死循环。
/**
* 如果两个数组是深度相等的,返回true。方法适用于任意类型的数组嵌套(数组中的元素时其他的数组,可能多维)
*
* 两个引用数组认为是深度相等的如果两个数组都为null,或者数组元素指向的数组包含相同的元素(所有
* 对应的引用元素都是深度相等的)。
*
* 两个可能为null的元素e1,e2深度相等如果满足下面的条件:
* - e1和e2都是Object[],并且Arrays.deepEquals(e1, e2)<递归>返回true
* - e1和e2是相同的基本类型数组,并且Arrays.equals(e1, e2)相等
* - e1 == e2(指向同一个对象/引用)
* - e1.equals(e2)返回true
* 允许在任意层的元素为null
*
* 如果数组间接的或者直接的引用自身,将导致死循环
*
* @param a1 one array to be tested for equality
* @param a2 the other array to be tested for equality
* @return <tt>true</tt> if the two arrays are equal
* @see #equals(Object[],Object[])
* @see Objects#deepEquals(Object, Object)
* @since 1.5
*/
public static boolean deepEquals(Object[] a1, Object[] a2) {
if (a1 == a2) //同一个引用(指向同一个数组)或者都是null,则相同
return true;
if (a1 == null || a2==null) //其中一个为null,返回false
return false;
int length = a1.length;
if (a2.length != length) //长度不同,返回false
return false;
for (int i = 0; i < length; i++) {
Object e1 = a1[i];
Object e2 = a2[i];
if (e1 == e2) //两个元素指向同一个对象,包括同时为null
continue;
if (e1 == null)
return false;
// Figure out whether the two elements are equal
boolean eq = deepEquals0(e1, e2); //调用deepEquals0(e1, e2);判断
if (!eq)
return false;
}
return true;
}
34. 对于Object的深度相等的判断
判断两个Object对象是否深度相等,Object可能为数组,如果是Object[],则需要继续判断这两个数组是否深度相等。其他基本类型的数组或者对象则调用相应的方法判断是否相等即可
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;
}
35. toString(long[] a)系列
根据传入的数组,转化成字符串。其他的重载方法实现一致。
/**
* 返回一个代表数组内容的字符串。字符串内容表示数组元素的一个列表,被包含在"[]"中。
* 用","连接数组间的元素和一个" ",每一个值使用String.valueOf(long)<其他类型(包括Object)则使用相应的方法>转化成字符串.
* 如果数组为null,则返回"null"。数组长度为0,则返回"[]"。
*
* @param a the array whose string representation to return
* @return a string representation of <tt>a</tt>
* @since 1.5
*/
public static String toString(long[] a) {
if (a == null)
return "null";
int iMax = a.length - 1;
if (iMax == -1)
return "[]";
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(a[i]);
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
36. deepToString(Object[] a)
与deepEquals和deepHashCode一样,当数组中包含嵌套数组时使用该方法,可以直接或间接引用自己,在遇到这种情况时,后面出现的该引用数组打印[…]。每一层使用”[]”将数组元素包起来。嵌套基本类型元素时,使用对应的toString来转化字符串,否则递归调用deepToString((Object[])element, buf, dejaVu)
,真正的转成字符串的操作都在需要递归的方法中
/**
* 返回一个代表数组深层内容的字符串。如果数组包含了其他数组,字符串也包含了那些数组的内容。
* 这个方法是为了多维数组转化成字符串设计的。
*
* 字符串内容表示数组元素的一个列表,被包含在"[]"中。用","连接数组间的元素和一个" ",
* 每一个值使用String.valueOf(Object)转化成字符串,除了元素是数组之外。
*
* 如果一个元素是一个基本类型的数组,使用适当的Arrays.toString(e)方法获得字符串。
* 如果元素e是一个引用数组,通过递归(deepToString((Object[])element, buf, dejaVu)方法)
* 获得字符串
*
* 在数组间接或直接引用自身时,为了防止死循环,这个自身引用将设备转化成"[...]"。
* 例如:一数组只有一个包含自己的元素。
* <pre>
* Object[] a = new Object[1];
* a[0] = a;
* </pre>
* 会转成"[[...]]"
*
* 这个方法将返回null,如果数组为null
*
* @param a the array whose string representation to return
* @return a string representation of <tt>a</tt>
* @see #toString(Object[])
* @since 1.5
*/
public static String deepToString(Object[] a) {
if (a == null)
return "null"; //为空时,返回"null";
int bufLen = 20 * a.length; //设置缓冲区域大小为数组长度的20倍
if (a.length != 0 && bufLen <= 0) //如果超出整数表示范围,取整数最大值
bufLen = Integer.MAX_VALUE;
StringBuilder buf = new StringBuilder(bufLen);
deepToString(a, buf, new HashSet<Object[]>());
return buf.toString();
}
37. deepToString真正转字符串的实现
具体的转化过程在上一个的方法中分析
private static void deepToString(Object[] a, StringBuilder buf,
Set<Object[]> dejaVu) {
if (a == null) {
buf.append("null");
return;
}
int iMax = a.length - 1;
if (iMax == -1) {
buf.append("[]");
return;
}
dejaVu.add(a);
buf.append('[');
for (int i = 0; ; i++) {
Object element = a[i];
if (element == null) {
buf.append("null");
} else {
Class<?> eClass = element.getClass();
if (eClass.isArray()) {
if (eClass == byte[].class)
buf.append(toString((byte[]) element));
else if (eClass == short[].class)
buf.append(toString((short[]) element));
else if (eClass == int[].class)
buf.append(toString((int[]) element));
else if (eClass == long[].class)
buf.append(toString((long[]) element));
else if (eClass == char[].class)
buf.append(toString((char[]) element));
else if (eClass == float[].class)
buf.append(toString((float[]) element));
else if (eClass == double[].class)
buf.append(toString((double[]) element));
else if (eClass == boolean[].class)
buf.append(toString((boolean[]) element));
else { //如果数组元素是一个引用数组时
if (dejaVu.contains(element)) //是当前遍历的数组元素的自身引用
buf.append("[...]");
else
deepToString((Object[])element, buf, dejaVu); //继续递归遍历
}
} else { //数组元素是一个普通的对象
buf.append(element.toString());
}
}
if (i == iMax)
break;
buf.append(", ");
}
buf.append(']');
//当前数组深度遍历完,则删除,保证不存在自身引用时,可以打印同一个数组内容
//(在数组遍历中遇到自身引用,会打印"[...]")
dejaVu.remove(a);
}
38. setAll系列
根据传入的generator生成数组对应类型的对象或数据,为数组元素赋值。对于每一个元素,将数组下标传入generator的方法,生成一个数据。每一个重载方法实现都一样,除了generator不同
/**
* 使用提供的generator提供的方法计算每个元素,为数组设值
*
* 如果generator抛出异常,是因为数组中左边元素时不确定的
*
* @param <T> type of elements of the array
* @param array array to be initialized
* @param generator a function accepting an index and producing the desired
* value for that position
* @throws NullPointerException if the generator is null
* @since 1.8
*/
public static <T> void setAll(T[] array, IntFunction<? extends T> generator) {
Objects.requireNonNull(generator);
for (int i = 0; i < array.length; i++)
array[i] = generator.apply(i);
}
39. parallelSetAll
与上面的方法一样,但是这个方法是设值过程是并行的。每一个重载方法实现都一样,除了generator不同。
/**
* 使用提供的generator提供的方法计算每个元素,并行的为数组设值
*
* 如果generator抛出异常,是因为数组中左边元素时不确定的
*
* @param <T> type of elements of the array
* @param array array to be initialized
* @param generator a function accepting an index and producing the desired
* value for that position
* @throws NullPointerException if the generator is null
* @since 1.8
*/
public static <T> void parallelSetAll(T[] array, IntFunction<? extends T> generator) {
Objects.requireNonNull(generator);
IntStream.range(0, array.length).parallel().forEach(i -> { array[i] = generator.apply(i); });
}
40. spliterator
获得一个分割器,可以用于遍历,重载方法实现一样。还有可以指定范围的版本
/**
* 根据数组创建一个Spliterator
*
* <p>The spliterator reports {@link Spliterator#SIZED},
* {@link Spliterator#SUBSIZED}, {@link Spliterator#ORDERED}, and
* {@link Spliterator#IMMUTABLE}.
*
* @param <T> type of elements
* @param array the array, assumed to be unmodified during use
* @return a spliterator for the array elements
* @since 1.8
*/
public static <T> Spliterator<T> spliterator(T[] array) {
return Spliterators.spliterator(array,
Spliterator.ORDERED | Spliterator.IMMUTABLE);
}
41. Stream
将数组作为一个连续的流使用,使用过程中不允许修改数组。其他重载方法一致。带范围的流使用呆范围的spliterator获得其范围之内的流
/**
* 将数组作为一个连续的流使用,使用过程中不允许修改数组。
*
* @param <T> The type of the array elements
* @param array The array, assumed to be unmodified during use
* @return a {@code Stream} for the array
* @since 1.8
*/
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
上一篇: C#中的数据结构
下一篇: c语言实现数据结构中的栈