Arrays
位于java.util包內(nèi)的Arrays類是Java提供的一個操作數(shù)組的工具類彤叉,其內(nèi)部定義了一些常見的用于操作數(shù)組的靜態(tài)方法,下面就按照以下幾個常用類型村怪,梳理一下秽浇。
- 數(shù)組轉(zhuǎn)List
- 排序
- 查找
- 元素填充
- 數(shù)組復制
- toString
- 相等性判斷
Arrays 數(shù)組操作集
數(shù)組轉(zhuǎn)List ---asList
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
這個被“普遍”稱為數(shù)組轉(zhuǎn)List的方法,可能是Arrays內(nèi)大家使用頻率最高的一個靜態(tài)方法了甚负。使用起來也很簡單,下面就很容易的實現(xiàn)了將數(shù)組轉(zhuǎn)為List柬焕。
String[] b = new String[]{"5", "6", "7", "8"};
List<String> datas = Arrays.asList(b);
當然還有另一種使用方法:
List<String> datas = Arrays.asList("5", "6", "7", "8");
其實,個人感覺“數(shù)組轉(zhuǎn)List”這種說法是沒有意義的梭域;我們都知道在List是一個接口斑举,而真正實現(xiàn)了這個接口的類只有ArrayList,LinkedList,Vector。其中ArrayList和Vector內(nèi)部都是使用“動態(tài)數(shù)組”實現(xiàn)病涨,LinkedList采用鏈表結構實現(xiàn)富玷。根據(jù)上一篇數(shù)據(jù)接口-線性表我們知道,數(shù)據(jù)的物理結構只用順序存儲接口和鏈式存儲結構没宾,List也不出其右凌彬。因此說這個方法實現(xiàn)了數(shù)組拷貝更確切一點沸柔。
下面循衰,根據(jù)他的具體實現(xiàn),我們更能體會到拷貝的意義褐澎。
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {@link Collection#toArray}. The returned list is
* serializable and implements {@link RandomAccess}.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
@SafeVarargs
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
/**
* @serial include
*/
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
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;
}
……
}
這里需要注意的是会钝,asList內(nèi)部的ArrayList并不是我們常用的那個ArrayList,而是在Arrays類內(nèi)部的一個私有靜態(tài)類舟扎。從代碼可以看出侦铜,這個內(nèi)部的ArrayList券膀,和常規(guī)的ArrayList相比,并沒有實現(xiàn)List接口奸鬓,而是直接繼承了AbstractList。
以下所說的ArrayList 統(tǒng)一指此處的靜態(tài)類
asList的實現(xiàn)很簡單掸读,返回了一個ArrayList的實例串远,參數(shù)為所要拷貝的數(shù)組名。
可以看到asList() 是接受一個泛型的變長參數(shù)的儿惫,而基本數(shù)據(jù)類型是無法被泛型化的澡罚。而對于泛型而言,基本數(shù)據(jù)類型肾请,實際上會被人為是一個 [x 的類型留搔。 [ 表示這是一個數(shù)組,x 為當前數(shù)組的類型铛铁。
因此隔显,這個方法不能直接“轉(zhuǎn)換”基礎數(shù)據(jù)類型的數(shù)組却妨。
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
可以看到,正常情況下荣月,ArrayList的構造函數(shù)完成的工作就是一個賦值操作管呵,把我們傳遞進來的數(shù)組賦給a,而a就是一個數(shù)組哺窄。說白了捐下,這就是一個數(shù)組拷貝的過程。再看ArrayList內(nèi)部實現(xiàn)萌业,get,set 都是根據(jù)數(shù)組下標實現(xiàn)簡單的數(shù)組賦值操作坷襟。這里省略了ArrayList內(nèi)部幾個方法,總之都是對數(shù)組的操作生年,有興趣的同學婴程,可以自行查看源碼。注意抱婉,這個類內(nèi)部档叔,并沒有add方法的具體實現(xiàn),也就是說AbstractList內(nèi)部的add方法并沒有被覆蓋
AbstractList#add
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
因此蒸绩,通過asList返回的List衙四,一定不能進行add 操作,否則會拋出異常患亿。
通過以上分析传蹈,我們可以得出以下結論:
- asList 不接受基本數(shù)據(jù)類型的數(shù)組名,作為參數(shù)直接傳遞步藕。
- asList 通過backed(拷貝)的方式惦界,返回的是一個固定長度的List,這點從方法注釋也可以看到
- 鑒于第二條咙冗,不能對這個返回的List執(zhí)行add 方法沾歪,可以調(diào)用set方法。
- 同理不能調(diào)用remove方法雾消,但可以調(diào)用get方法獲取元素灾搏。
這里關于第一個結論,還需要解釋一下仪或,不接受基本數(shù)據(jù)類型的數(shù)組名作為參數(shù)确镊,但是以下實現(xiàn)是可以的。
List ds = Arrays.asList(1, 2, 4, 4, 5);
好了范删,Arrays.asList的用法就說到這里了蕾域。既然都到這里了,順便多說一句,List轉(zhuǎn)數(shù)組的實現(xiàn)旨巷,Collection接口定義了統(tǒng)一的方法toArray巨缘。對于不同的List實現(xiàn),統(tǒng)一調(diào)用即可采呐。
List<String> datas;
.....
String[] result= (String[]) datas.toArray();
排序
sort
sort() 方法顧名思義若锁,主要是實現(xiàn)數(shù)組的排序,默認按升序進行排列斧吐。
Arrays 內(nèi)部關于sort的實現(xiàn)又固,可以大體分為兩類,一類是基本數(shù)據(jù)類型的排序煤率,一類是Object類型的排序仰冠。
- 基本數(shù)據(jù)類型的排序
//對數(shù)組排序
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
//對數(shù)組從指定位置排序
public static void sort(int[] a, int fromIndex, int toIndex) {
//檢測參數(shù)是否越界
rangeCheck(a.length, fromIndex, toIndex);
DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
}
sort靜態(tài)方法的實現(xiàn),按參數(shù)主要有兩種實現(xiàn)方法蝶糯,一種是數(shù)組整體進行排序洋只;一種是在數(shù)組內(nèi)指定一段起始位置進行排序,之后的對象數(shù)組排序也只是按指定起始位置排序昼捍,不再重復描述识虚。其內(nèi)部具體實現(xiàn)是DualPivotQuicksort(雙軸快速排序)。
這里可以接收的參數(shù)類型除了int數(shù)組妒茬,還可以是long担锤,short,char郊闯,byte妻献,float蛛株,double類型數(shù)組团赁。
- Object 類型數(shù)組排序
實現(xiàn)Comparable接口的對象數(shù)組排序
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
注意,使用這個方法時谨履,提供的“數(shù)組中的對象”必須是實現(xiàn)了Comparable接口的欢摄,也就是說必須告知明確告知,對數(shù)組中的對象是按什么規(guī)則排序笋粟。
實現(xiàn)Comparator接口的排序
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
使用這個方法是泛型為T的數(shù)組怀挠,需要提供一個實現(xiàn)了Comparator接口的實例,同理也是必須明確告知排序規(guī)則害捕,如果同時實現(xiàn)了Comparable接口和Comparator接口绿淋,Comparator接口的實現(xiàn)將覆蓋Comparable接口的排序規(guī)則。
對象數(shù)組排序內(nèi)部實現(xiàn)采用了LegacyMergeSort(歸并排序)和TimSort排序尝盼。
parallelSort
parallelSort 是Java8新增的排序方式吞滞,和sort方法不同的是,他采用多線程并行的方式進行排序,當數(shù)據(jù)規(guī)模較大時和sort相比有明顯優(yōu)勢;具體可見arrays-sort-versus-arrays-parallelsort.
public static final int MIN_ARRAY_SORT_GRAN = 1 << 13;
public static void parallelSort(int[] a) {
int n = a.length, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
else
new ArraysParallelSortHelpers.FJInt.Sorter
(null, a, new int[n], 0, n, 0,
((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g).invoke();
}
parallelSort 使用方式及可接受參數(shù)類型和sort方法基礎數(shù)據(jù)類型時的參數(shù)完全一致裁赠。
查找
Arrays內(nèi)部的查找殿漠,主要是binarySearch(二分查找法)∨謇蹋可以說绞幌,關于查找到實現(xiàn)分類和排序完全一樣。首先從數(shù)據(jù)類型上也是分為基礎數(shù)據(jù)類型構成的數(shù)組和對象數(shù)組一忱。都支持按特定范圍進行排序莲蜘;對于對象數(shù)組的排序,對象數(shù)組需要實現(xiàn)Comparable接口或者是提供Comparator接口的實例帘营。
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
//可以在指定范圍排序
public static int binarySearch(int[] a, int fromIndex, int toIndex,
int key) {
rangeCheck(a.length, fromIndex, toIndex);
return binarySearch0(a, fromIndex, toIndex, key);
}
//Object[]數(shù)組中的Object必須實現(xiàn)了Comparable接口
public static int binarySearch(Object[] a, int fromIndex, int toIndex,
Object key) {
rangeCheck(a.length, fromIndex, toIndex);
return binarySearch0(a, fromIndex, toIndex, key);
}
//提供Comparator實例
public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c) {
return binarySearch0(a, 0, a.length, key, c);
}
元素填充
public static void fill(Object[] a, int fromIndex, int toIndex, Object val) {
rangeCheck(a.length, fromIndex, toIndex);
for (int i = fromIndex; i < toIndex; i++)
a[i] = val;
}
fill()方法,使用很簡單仪吧,也很好理解庄新,將數(shù)組用特定的元素val 填滿即可,也可以是特定位置薯鼠。
復制
這個方法择诈,就是實現(xiàn)兩個將原數(shù)組按指定長度復制到目標數(shù)組內(nèi)返回。
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;
}
其內(nèi)部使用System.arraycopy方法出皇,這是一個Java提供的native方法羞芍,因此效率會高一些。Java 內(nèi)部關于數(shù)組復制的實現(xiàn)郊艘,都用到了這個方法荷科。
同樣,也包括一個copyOfRange的方法纱注,這個按名字就可以理解畏浆,就是按照范圍進行復制。
toString
這個toString的靜態(tài)方法狞贱,其實也很實用刻获;從下面的代碼的實現(xiàn),可以看出瞎嬉,他的作用就是將我們定義的數(shù)組蝎毡,按照 "[a0,a1,....]"的格式轉(zhuǎn)成字符串,方便我們直接打印整個數(shù)組氧枣,打印出來的日志也會看起來更直觀沐兵,更方便。
public static String toString(double[] 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(", ");
}
}
它除了支持8種基礎數(shù)據(jù)類型的數(shù)組外便监,還支持Object類型的數(shù)組扎谎。
相等性
equals
Arrays內(nèi)部關于兩個數(shù)組相等的判斷可以首先看下下面的代碼:
基礎數(shù)據(jù)類型數(shù)組,以long類型為例
public static boolean equals(float[] a, float[] 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 (Float.floatToIntBits(a[i])!=Float.floatToIntBits(a2[i]))
return false;
return true;
}
Object 類型數(shù)組
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++) {
Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return true;
}
我們知道,數(shù)組名代表數(shù)組首地址簿透;因此移袍,從以上代碼可以得出結論,
當兩個數(shù)組不是同一數(shù)組時老充,也就是a==a2 不成立葡盗。
當滿足以下任一條件時:
- 兩個數(shù)組中有一個為空時
- 兩個數(shù)組長度不等時
- 兩個數(shù)組中包含任意不相等的元素時
就認為兩個數(shù)組不相等,反之則認為相等啡浊。對于對象數(shù)組觅够,相同位置的對象均為null是,認為是兩個相同的元素巷嚣。
deepEquals
關于deepEquals和equals的區(qū)別喘先,可以看看這篇文章Java中Arrays類的兩個方法:deepEquals和equals
其他
數(shù)組交換
/**
* Swaps x[a] with x[b].
*/
private static void swap(Object[] x, int a, int b) {
Object t = x[a];
x[a] = x[b];
x[b] = t;
}
這個方法其實挺實用的,以后如果懶得寫了廷粒,可以直接一行代碼搞定窘拯。
最后
以上分析是基于Java jdk1.8 版本,在Java中由于lamdba表達式坝茎,函數(shù)式編程思想的引入涤姊,Arrays內(nèi)部新增了許多相關的類如Stream 等,考慮到使用頻率嗤放,暫時不展開討論了思喊。
參考內(nèi)容: