概述
集合框架的工具類除了Collecions之外锦针,另一個(gè)就是Arrays了赫舒,上文已經(jīng)說(shuō)過(guò)脚曾,Java中Arrays工具類是針對(duì)數(shù)組進(jìn)行操作的,這個(gè)類中包含了許多操作數(shù)組的靜態(tài)方法割以。我們來(lái)大致看一下它的源碼實(shí)現(xiàn)金度。
構(gòu)造方法與屬性
private Arrays() {}
// 進(jìn)行并行排序的最小數(shù)組長(zhǎng)度
private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
// 在使用歸并排序中優(yōu)先使用插入排序的閾值(后續(xù)會(huì)移除掉)
private static final int INSERTIONSORT_THRESHOLD = 7;
和Collections一樣,構(gòu)造方法私有严沥,不對(duì)外提供猜极。我們只需調(diào)用Arrays的靜態(tài)方法來(lái)操作數(shù)據(jù)即可。
sort方法
Arrays提供了一系列重載的sort方法消玄,大體上可以分為兩種跟伏,一種是針對(duì)基本數(shù)據(jù)類型來(lái)進(jìn)行排序丢胚,包括int,long受扳,byte携龟,float,double等類型勘高,另一種是針對(duì)Object數(shù)組類型來(lái)進(jìn)行排序峡蟋。默認(rèn)都是升序排列的。我們來(lái)簡(jiǎn)單看一下int數(shù)組的排序:
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
// 對(duì)數(shù)組的一部分進(jìn)行排序
public static void sort(int[] a, int fromIndex, int toIndex) {
// 檢測(cè)數(shù)組的下標(biāo)范圍
rangeCheck(a.length, fromIndex, toIndex);
DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
}
??可以看到华望,底層都是通過(guò)調(diào)用DualPivotQuicksort該類的sort方法來(lái)實(shí)現(xiàn)的蕊蝗。DualPivotQuicksort這個(gè)類是JAVA1.7之后專門(mén)提供給Java內(nèi)部排序使用的專用類,被稱為雙樞軸快速排序赖舟,用來(lái)優(yōu)化原先的快速排序蓬戚,該算法一般能提供O(nlog(n))
的時(shí)間復(fù)雜度,大家有興趣的可以查看它的源碼來(lái)學(xué)習(xí)一下宾抓。
??至于其他類型的排序碌更,和int類型排序都是類似的,不多說(shuō)了洞慎。另外簡(jiǎn)單說(shuō)一點(diǎn),就是我們使用的Collections的sort方法就是借助Arrays.sort方法來(lái)實(shí)現(xiàn)的嘿棘。
另外一種重載的sort方法:
public static void sort(Object[] a)
public static void sort(Object[] a, int fromIndex, int toIndex)
// 帶比較器的排序算法
public static <T> void sort(T[] a, Comparator<? super T> c)
// 帶比較器的 數(shù)組的部分排序
public static <T> void sort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> c)
該方法要求傳入的參數(shù)必須實(shí)現(xiàn)了Comparable
劲腿,這個(gè)排序算法是一種穩(wěn)定的算法。由于實(shí)現(xiàn)大致相似鸟妙,我們來(lái)簡(jiǎn)單看下sort(Object[] a)
焦人。
public static void sort(Object[] a) {
// 使用歸并排序
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
// 使用TimSort排序
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
該算法的實(shí)現(xiàn)又分為兩種,一種通過(guò)歸并排序算法來(lái)實(shí)現(xiàn)重父,另一種通過(guò)使用TimSort排序算法來(lái)實(shí)現(xiàn)花椭。TimSort算法是一種插入與傳統(tǒng)歸并算法結(jié)合的一種算法,是對(duì)歸并算法的一種優(yōu)化房午。至于使用哪一種算法矿辽,需要設(shè)置系統(tǒng)變量:java.util.Arrays.useLegacyMergeSort
,通過(guò)設(shè)置為true郭厌,來(lái)使用歸并算法袋倔,否則使用TimSort算法。默認(rèn)情況下我們是不會(huì)用到歸并算法的折柠,并且在JDK文檔中有說(shuō)明宾娜,在后續(xù)的版本中,legacyMergeSort歸并算法會(huì)被移除掉扇售。所以前塔,如果有興趣嚣艇,大家可以去看下TimSort算法的實(shí)現(xiàn)。這里參考自:
Compile in Java 6, run in 7 - how to specify useLegacyMergeSort?
/**
* Old merge sort implementation can be selected (for
* compatibility with broken comparators) using a system property.
* Cannot be a static boolean in the enclosing class due to
* circular dependencies. To be removed in a future release.
*/
static final class LegacyMergeSort {
private static final boolean userRequested =
java.security.AccessController.doPrivileged(
new sun.security.action.GetBooleanAction(
"java.util.Arrays.useLegacyMergeSort")).booleanValue();
}
/** To be removed in a future release. */
private static void legacyMergeSort(Object[] a) {
Object[] aux = a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
parallelSort方法
??Arrays同樣提供了一系列重載的parallelSort方法华弓,用于數(shù)字類型的并行排序食零,同樣默認(rèn)也是升序排列的。這一系列算法是JAVA1.8之后引入的该抒,基于JAVA的并行處理框架fork/join框架慌洪,而fork/join框架,是Java1.7引入凑保,目的是為了充分利用多核處理器冈爹,編寫(xiě)并行程序,提高程序性能的框架欧引。
public static void parallelSort(byte[] a) {
int n = a.length, p, g;
// 如果數(shù)組長(zhǎng)度小于并行排序的最小長(zhǎng)度或者并行線程池的大小是1
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
DualPivotQuicksort.sort(a, 0, n - 1);
else
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();
}
我們可以看到频伤,程序里進(jìn)行了判斷,如果數(shù)組長(zhǎng)度太小芝此,小于并行排序的最小長(zhǎng)度或者并行線程池的大小是1憋肖,這時(shí)候就不再使用并行處理,還是使用DualPivotQuicksort的sort方法來(lái)進(jìn)行排序婚苹。這里有兩點(diǎn)簡(jiǎn)單說(shuō)明下:
- Arrays的常量
MIN_ARRAY_SORT_GRAN
岸更,如果數(shù)組容量太小,而使用并行處理的話膊升,通常會(huì)導(dǎo)致跨任務(wù)的內(nèi)存競(jìng)爭(zhēng)怎炊,這樣的話并行的速度就沒(méi)有預(yù)想中的那么令人滿意。- ForkJoinPool是fork/join框架中的一個(gè)核心類廓译,通過(guò)ForkJoinPool的getCommonPoolParallelism方法我們可以獲取到并行線程池的大小评肆,如果是1的話,也就是單核非区,就不需要使用并行處理了瓜挽。因?yàn)槟J(rèn)情況下,并行線程池的大小等于CPU的核數(shù)征绸。
parallelPrefix方法
public static void parallelPrefix(int[] array, IntBinaryOperator op)
public static <T> void parallelPrefix(T[] array, int fromIndex,int toIndex, BinaryOperator<T> op)
Arrays提供了一系列parallelPrefix方法久橙,用于對(duì)數(shù)組進(jìn)行并行的二元迭代操作,其中方法的最后一個(gè)參數(shù)是二元操作符的意思管怠,該運(yùn)算符其實(shí)是一個(gè)函數(shù)表達(dá)式剥汤。說(shuō)起來(lái)有點(diǎn)繞口,先看個(gè)例子:
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
System.out.println("old array: ");
Arrays.stream(numbers).forEach(n -> System.out.print(n + " "));
// 二元迭代:累加
System.out.println("\noperator: + ");
IntBinaryOperator binaryOperator = (x, y) -> (x + y);
Arrays.parallelPrefix(numbers, binaryOperator);
Arrays.stream(numbers).forEach(n -> System.out.print(n + " "));
// 二元迭代:累乘
System.out.println("\noperator: * ");
Arrays.parallelPrefix(numbers, (x, y) -> x * y);
Arrays.stream(numbers).forEach(n -> System.out.print(n + " "));
}
output:
old array:
1 2 3 4 5
operator: +
1 3 6 10 15
operator: *
1 3 18 180 2700
例子參考自:Java 8 Arrays parallelPrefix method Example
也就是說(shuō)排惨,該方法是一種累計(jì)的操作吭敢,比如累加,數(shù)組中的每個(gè)元素依次與前面的所有元素累加暮芭,然后替換原來(lái)的元素鹿驼。對(duì)于大型數(shù)組欲低,并行迭代操作通常比順序循環(huán)性能更好。同樣畜晰,該方法也有多種重載方式砾莱,支持int,long凄鼻,double等類型的操作腊瑟。
該方法是JDK1.8之后引入的,底層是通過(guò)JDK內(nèi)部提供的ArrayPrefixHelpers類來(lái)實(shí)現(xiàn)的块蚌,當(dāng)然最終還是通過(guò)fork/join框架來(lái)實(shí)現(xiàn)的闰非。
源碼如下:
public static void parallelPrefix(int[] array, IntBinaryOperator op) {
Objects.requireNonNull(op);
if (array.length > 0)
new ArrayPrefixHelpers.IntCumulateTask
(null, op, array, 0, array.length).invoke();
}
binarySearch方法
public static int binarySearch(int[] a, int key)
public static int binarySearch(int[] a, int fromIndex, int toIndex,int key)
顧名思義,這個(gè)方法是二分查找的意思峭范,用來(lái)在數(shù)組中查找元素财松。當(dāng)然,使用該方法之前纱控,必須保證該數(shù)組已經(jīng)排序完成辆毡,并且是升序完成。否則甜害,查找將沒(méi)有什么意義舶掖。如果數(shù)組包含多個(gè)指定查找值的元素,則無(wú)法保證找到具體哪一個(gè)值尔店。
源碼如下:
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
/**
* 其中該方法和binarySearch的public方法一樣眨攘,就是沒(méi)有索引校驗(yàn)
*/
// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
// 開(kāi)始下標(biāo)
int low = fromIndex;
// 結(jié)束下標(biāo)
int high = toIndex - 1;
while (low <= high) {
// 通過(guò)位移運(yùn)算符計(jì)算中間下標(biāo)值
int mid = (low + high) >>> 1;
// 保存數(shù)組中間值
int midVal = a[mid];
// 中間值比要查找的值小,往數(shù)組高位進(jìn)行查詢
if (midVal < key)
low = mid + 1;
// 中間值比要查找的值大闹获,往數(shù)組低位進(jìn)行查詢
else if (midVal > key)
high = mid - 1;
else
// 如果相等,直接返回
return mid; // key found
}
// 查找不到河哑,返回負(fù)值
return -(low + 1); // key not found.
}
該方法比較簡(jiǎn)單避诽,當(dāng)然也是提供了一系列重載的方法,其中也可以自定義查找時(shí)的比較規(guī)則:
private static <T> int binarySearch0(T[] a, int fromIndex, int toIndex, T key, Comparator<? super T> c)
大家等用到的時(shí)候可自行查看璃谨。
equals方法
public static boolean equals(Object[] a, Object[] a2)
這個(gè)方法很簡(jiǎn)單沙庐,就是判斷兩個(gè)數(shù)組是否相等。比較的類型如果重寫(xiě)了equals方法佳吞,則按照對(duì)象的equals方法進(jìn)行比較拱雏。該方法也有許多重載方法,實(shí)現(xiàn)類似底扳,我們來(lái)查看下object數(shù)組作為參數(shù)的源碼:
public static boolean equals(Object[] a, Object[] a2) {
// 如果是同一個(gè)數(shù)組引用铸抑,則相等
if (a==a2)
return true;
// 如果有一個(gè)數(shù)組為null,則不相等
if (a==null || a2==null)
return false;
int length = a.length;
// 如果長(zhǎng)度不相等衷模,則不相等
if (a2.length != length)
return false;
// 如果對(duì)應(yīng)位置上的值不相等鹊汛,則不相等
for (int i=0; i<length; i++) {
Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
如果以上都不滿足蒲赂,則相等
return true;
}
fill方法
public static void fill(long[] a, long val)
public static void fill(long[] a, int fromIndex, int toIndex, long val)
這個(gè)方法也很簡(jiǎn)單,就是將指定值填充到數(shù)組的每個(gè)元素刁憋。底層實(shí)現(xiàn)就是循環(huán)遍歷填充即可滥嘴。一般我們批量初始化數(shù)組的時(shí)候可以使用該方法。
public static void fill(int[] a, int val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
copyOf和copyOfRange方法
public static int[] copyOf(int[] original, int newLength)
public static <T> T[] copyOfRange(T[] original, int from, int to)
復(fù)制數(shù)組至耻,newLength是新數(shù)組的長(zhǎng)度若皱。如果新數(shù)組長(zhǎng)度大于原數(shù)組長(zhǎng)度,則新值填充null或相關(guān)類型默認(rèn)值(比如int尘颓,默認(rèn)填充0走触,double類型默認(rèn)為0.0)。先來(lái)看下泛型的源碼:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
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;
}
可以看到泥耀,底層是通過(guò)System的native方法arraycopy來(lái)實(shí)現(xiàn)的饺汹,這個(gè)方法在操作集合的時(shí)候也經(jīng)常用到。而對(duì)于類型固定的則和這類似痰催,看一下int類型:
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;
}
而copyOfRange底層實(shí)現(xiàn)類似兜辞,就多了步對(duì)索引的判斷,就不多說(shuō)了夸溶。
asList
將數(shù)組轉(zhuǎn)為L(zhǎng)ist逸吵,該方法與集合的toArray方法一起充當(dāng)了基于數(shù)組和集合之間的橋梁。并且該方法還提供了一種很便捷的方法來(lái)創(chuàng)建一個(gè)初始化大小的列表缝裁,該列表初始化包含幾個(gè)元素:
List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
該方法源碼如下:
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
這里返回的ArrayList不是我們常用的java.util.ArrayList扫皱,而是Arrays的一個(gè)靜態(tài)內(nèi)部類,并且該內(nèi)部類中沒(méi)有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);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
...
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
...
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
...
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
...
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
hashCode方法
??獲取數(shù)組的hashCode值,該值是基于數(shù)組的每一個(gè)元素的hashCode來(lái)實(shí)現(xiàn)的粹污。
??如果數(shù)組中有嵌套數(shù)組段多,我們還想根據(jù)里層數(shù)組的每個(gè)元素的hashCode來(lái)獲取hash的話,我們可以通過(guò)deepHashCode方法壮吩,該方法通過(guò)遞歸的方式來(lái)實(shí)現(xiàn)进苍。
通俗的來(lái)講,hashCode方法只計(jì)算到數(shù)組的第一層鸭叙,而deepHashCode方法則會(huì)一直遞歸調(diào)用到數(shù)組無(wú)法再拆分為止觉啊。
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
// 遍歷每個(gè)元素,調(diào)用每個(gè)元素的hashCode方法
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
大概看一下deepHashCode的方法:
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;
}
deepEquals方法
同樣沈贝,對(duì)于數(shù)組的equals方法杠人,也是只比較第一層,如果有多層嵌套的話,我們可以使用deepEquals方法來(lái)比較搜吧。
toString方法
這個(gè)方法我們經(jīng)常用到市俊,這里就是返回?cái)?shù)組的字符串表示形式,我們可以更直觀的看到數(shù)組里保存的都是什么滤奈。
public static String toString(Object[] a) {
if (a == null)
return "null";
int iMax = a.length - 1;
if (iMax == -1)
return "[]";
// 內(nèi)部使用StringBuilder來(lái)表示
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; ; i++) {
b.append(String.valueOf(a[i]));
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
同理摆昧,還有一個(gè)deepToString方法,用來(lái)返回嵌套數(shù)組的字符串表示形式蜒程。這里就不多說(shuō)了绅你。
setAll方法
public static <T> void setAll(T[] array, IntFunction<? extends T> generator)
使用生成器函數(shù)更新數(shù)組的每個(gè)元素,其中最后一個(gè)參數(shù)是JDK1.8之后的一個(gè)一元操作符昭躺,也是函數(shù)式接口忌锯。我們先看一個(gè)例子:
public static void main(String[] args) {
int[] number = {1, 2, 3, 4, 5, 6};
Arrays.setAll(number, x -> x * 3);
System.out.println(Arrays.toString(number));
}
output:
[0, 3, 6, 9, 12, 15]
上述方法是為數(shù)組的每個(gè)元素乘以3,重新賦值給數(shù)組领炫。
源碼如下:
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);
}
通過(guò)調(diào)用函數(shù)式接口generator的apply方法來(lái)實(shí)現(xiàn)一元運(yùn)算符操作偶垮。
parallelSetAll方法
同樣,parallelSetAll方法是setAll的并行方式帝洪,通過(guò)借助JDK1.8的IntStream對(duì)象來(lái)實(shí)現(xiàn)似舵。
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); });
}
spliterator方法
JDK8之后引入的并行迭代器,目的就是并行遍歷數(shù)組中的元素葱峡⊙饣可以與原先的Iterator迭代器相比較。Spliterator是很有意思的一個(gè)類砰奕,如果有時(shí)間蛛芥,再單獨(dú)分析一下,現(xiàn)在先簡(jiǎn)單看一下源碼即可:
public static <T> Spliterator<T> spliterator(T[] array) {
return Spliterators.spliterator(array,Spliterator.ORDERED | Spliterator.IMMUTABLE);
}
stream方法
Stream也是JDK8之后引入的用于處理集合或者數(shù)組等類型的流军援,一般Stream配合lambda表達(dá)式使用仅淑,可以極大的提高我們編程的效率和程序的可讀性。對(duì)于Stream胸哥,我們也是等有時(shí)間了單獨(dú)分析一下涯竟,現(xiàn)在也是先簡(jiǎn)單看一下源碼,順便看一下簡(jiǎn)單的實(shí)現(xiàn):
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}
public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}
看一下簡(jiǎn)單的實(shí)現(xiàn):
public static void main(String[] args) {
int[] number = {1, 2, 3, 4, 5, 6};
Arrays.stream(number).forEach(n -> System.out.print(n + " "));
}
output:
1 2 3 4 5 6
總結(jié)
Arrays的源碼到現(xiàn)在基本上已經(jīng)看完了烘嘱,我們可以做個(gè)大致的總結(jié):
- Arrays的源碼大致分為三種:parallel開(kāi)頭的都是并行處理的昆禽,deep開(kāi)頭的都是用于數(shù)組嵌套相關(guān)的操作蝗蛙,另一種就是我們常用的簡(jiǎn)單操作蝇庭;
- Arrays中基本上每個(gè)方法都有一系列的重載方法用于滿足不同類型的操作,我們可以根據(jù)需要選擇性的調(diào)用捡硅。
本文參考的鏈接如下:
https://docs.oracle.com/javase/8/docs/api/
讀 Java Arrays 源碼 筆記
快速排序?qū)崿F(xiàn)及優(yōu)化 | DualPivotQuicksort
Java8里面的java.util.Spliterator接口有什么用哮内?
參考網(wǎng)站主要是:stackoverflow,搜索引擎:Google。