1. class 简介
Arrays类包含很多对数组进行操作的静态方法,可以对数组进行复制、填充、排序、搜索、比较、打印和转换成集合容器等操作,除非特别说明,所有方法都会因为传入一个null的数组引用而抛出NullPointerException。Arrays类的方法频繁地出现在JDK源码(尤其是Java中的数据结构)中,所以我觉得弄清楚Arrays类的内部实现是有必要的,虽然本身就很简单。
2. class内部原理及特点
- Arrays类中的暴露在外部的所有方法都是静态方法,Arrays类不能被实例化。
- CopyOf方法底层清一色地使用了System.arraycopy方法。
- 对于基本类型数据的排序使用的是DualPivotQuickSort双支点快排。
- 对于引用类型和泛型数据的排序使用的是TimSort。
- 搜索即为最常见的二分搜索
3. class源码细节分析
所谓射人先射马,擒贼先擒王,JDK中的方法一般都重载了很多个,但是大多数方法底层都是调用了同一个最复杂的最具有一般性的方法。所以这里只贴最具代表性的。
copyOf 和 copyOfRange
这类方法分两种,一种是指定长度从下标0开始复制,另一种是指定复制的起始位置。
/*
把original数组的下标0到下标newLength-1间的所有元素复制到一个新数组并返回
*/
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;
}
/* 这个方法使用Array类通过反射动态生成一个和original数组相同类型的数组,之后再将original数组的指定长度的元素复制过去 */
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
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;
}
/* 下面两个方法指定复制的起始点,除此之外和copyOf方法一样 */
public static int[] copyOfRange(int[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
int[] copy = new int[newLength];
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}
public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
return copy;
}
以上这些copy方法都相当于对System.arraycopy进行了封装,直接使用System.arraycopy可能会因为newLength过长而下标越界抛出异常,而Arrays.copyOf和Arrays.copyOfRange则没有这个问题,它会使用两个数组中较短的长度。
sort
同样分成两种,一种是对整个数组进行排序,另一种是对于一个指定区间进行排序。
/* 对于int,short,long,double,float,byte,char等基本数据类型采用双支点快排,效率比传统的快排要好 */
public static void sort(int[] a) {
DualPivotQuicksort.sort(a);
}
public static void sort(int[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);
DualPivotQuicksort.sort(a, fromIndex, toIndex - 1);
}
/* 对于引用类型和泛型采用TimSort */
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a);
}
public static void sort(Object[] a, int fromIndex, int toIndex) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex);
else
ComparableTimSort.sort(a, fromIndex, toIndex);
}
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, c);
}
public static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex, c);
else
TimSort.sort(a, fromIndex, toIndex, c);
}
binarySearch
/* 最常见的二分搜索,要求数组是排过序的,每种数据类型都重载了,但是核心是这个私有方法 */
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.
}
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;
}
fill
/* 填充数组,即循环赋值,重载的其它几个方法都一样 */
public static void fill(int[] a, int val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
public static void fill(int[] a, int fromIndex, int toIndex, int val) {
rangeCheck(a.length, fromIndex, toIndex);
for (int i = fromIndex; i < toIndex; i++)
a[i] = val;
}
toString
/* 浅层次的将数组字符串化(主要用于一维数组,二维以上用deepToString),两头加上中括号,中间以逗号间隔,其它重载的方法都一样 */
public static String toString(int[] 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(", ");
}
}
hashCode
/* 使用的是Times31哈希函数 */
public static int hashCode(int a[]) {
if (a == null)
return 0;
int result = 1;
for (int element : a)
result = 31 * result + element;
return result;
}
asList
这个方法是数组到集合容器之间的桥梁,反过来集合容器可以通过toArray方法转换成数组
/* 这个ArrayList类是Arrays的一个内部类,注意其内部数组是不可变的 */
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
这个方法要注意两点:
1、Arrays.asList接受一个可变长参数,可以接受多个值或者一个数组,但如果是基本类型数组如int[]则并不会得到想要的结果,因为基本类型不能作为泛型参数,这里传入int[]数组只会被当成是一个参数,这样产生的集合容器中只会有一个元素,想要将数组转换为集合,必须是引用类型如Integer[]。
2、方法中的ArrayList是Arrays类的一个内部类,其内部数组是final不可变的,所以试图改变其内部数组容量的操作都会抛UnsupportedOperationException,比如:
Arrays类的内部类ArrayList中的数组定义如下:
private final E[] a;
public static void main(String args[]){
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.set(1,10)//没有改变容量,没有问题
list.add(6);//异常,此时的list内部数组为final,扩容会生成一个新数组
//如果想对这个list进行操作最好是将其作为一个新的list的构造参数
List<Integer>list2 = new ArrayList<Integer>(list);
}
4. 总结
Arrays类的内部实现都非常的简单易懂,除了DualPivotQuicksort和TimSort可能要去查阅相关资料,其它的地方都没有什么的难点。在后面的集合类源码中,将会经常出现Arrays.copyOf和System.arraycopy方法。