java.util.Arrays
類就是為陣列而生的專用工具類,基本上常見的對陣列的操作,Arrays 類都考慮到了,這讓我由衷地覺得,是時候給該類的作者 Josh Bloch、Neal Gafter、John Rose 點個讚了。
(我是怎麼知道作者名的?看原始碼就可以,小夥伴們,裝逼吧)
Arrays 都可以幹嘛呢?常見的有:
- 建立陣列
- 比較陣列
- 陣列排序
- 陣列檢索
- 陣列轉流
- 列印陣列
- 陣列轉 List
- setAll(沒想好中文名)
- parallelPrefix(沒想好中文名)
那接下來,小夥伴們是不是已經迫不及待想要和二哥一起來打怪進階了。走你。
01、建立陣列
使用 Arrays 類建立陣列可以通過以下三個方法:
- copyOf,複製指定的陣列,擷取或用 null 填充
- copyOfRange,複製指定範圍內的陣列到一個新的陣列
- fill,對陣列進行填充
1)copyOf,直接來看例子:
String[] intro = new String[] { "沉", "默", "王", "二" };
String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);
System.out.println(Arrays.toString(revised));
System.out.println(Arrays.toString(expanded));
revised 和 expanded 是複製後的新陣列,長度分別是 3 和 5,指定的陣列長度是 4。來看一下輸出結果:
[沉, 默, 王]
[沉, 默, 王, 二, null]
看到沒?revised 擷取了最後一位,因為長度是 3 嘛;expanded 用 null 填充了一位,因為長度是 5。
2)copyOfRange,直接來看例子:
String[] intro = new String[] { "沉", "默", "王", "二" };
String[] abridgement = Arrays.copyOfRange(intro, 0, 3);
System.out.println(Arrays.toString(abridgement));
copyOfRange()
方法需要三個引數,第一個是指定的陣列,第二個是起始位置(包含),第三個是截止位置(不包含)。來看一下輸出結果:
[沉, 默, 王]
0 的位置是“沉”,3 的位置是“二”,也就是說擷取了從 0 位(包含)到 3 位(不包含)的陣列元素。那假如說下標超出了陣列的長度,會發生什麼呢?
String[] abridgementExpanded = Arrays.copyOfRange(intro, 0, 6);
System.out.println(Arrays.toString(abridgementExpanded));
結束位置此時為 6,超出了指定陣列的長度 4,來看一下輸出結果:
[沉, 默, 王, 二, null, null]
仍然使用了 null 進行填充。為什麼要這麼做呢?小夥伴們思考一下,我想是作者考慮到了陣列越界的問題,不然每次呼叫 Arrays 類就要先判斷很多次長度,很麻煩。
3)fill,直接來看例子:
String[] stutter = new String[4];
Arrays.fill(stutter, "沉默王二");
System.out.println(Arrays.toString(stutter));
使用 new 關鍵字建立了一個長度為 4 的陣列,然後使用 fill()
方法將 4 個位置填充為“沉默王二”,來看一下輸出結果:
[沉默王二, 沉默王二, 沉默王二, 沉默王二]
如果想要一個元素完全相同的陣列時, fill()
方法就派上用場了。
02、比較陣列
Arrays 類的 equals()
方法用來判斷兩個陣列是否相等,來看下面這個例子:
String[] intro = new String[] { "沉", "默", "王", "二" };
boolean result = Arrays.equals(new String[] { "沉", "默", "王", "二" }, intro);
System.out.println(result);
boolean result1 = Arrays.equals(new String[] { "沉", "默", "王", "三" }, intro);
System.out.println(result1);
輸出結果如下所示:
true
false
指定的陣列為沉默王二四個字,比較的陣列一個是沉默王二,一個是沉默王三,所以 result 為 true,result1 為 false。
簡單看一下 equals()
方法的原始碼:
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;
}
因為陣列是一個物件,所以先使用“==”操作符進行判斷,如果不相等,再判斷是否為 null,兩個都為 null,返回 false;緊接著判斷 length,不等的話,返回 false;否則的話,依次呼叫 Objects.equals()
比較相同位置上的元素是否相等。
感覺非常嚴謹,這也就是學習原始碼的意義,鑑賞的同時,學習。
除了 equals()
方法,還有另外一個訣竅可以判斷兩個陣列是否相等,儘管可能會出現誤差(概率非常小)。那就是 Arrays.hashCode()
方法,先來看一下該方法的原始碼:
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;
}
雜湊演算法本身是非常嚴謹的,所以如果兩個陣列的雜湊值相等,那幾乎可以判斷兩個陣列是相等的。
String[] intro = new String[] { "沉", "默", "王", "二" };
System.out.println(Arrays.hashCode(intro));
System.out.println(Arrays.hashCode(new String[] { "沉", "默", "王", "二" }));
來看一下輸出結果:
868681617
868681617
兩個陣列的雜湊值相等,畢竟元素是一樣的。但這樣確實不夠嚴謹,優先使用 Objects.equals()
方法。
03、陣列排序
Arrays 類的 sort()
方法用來判斷兩個陣列是否相等,來看下面這個例子:
String[] intro1 = new String[] { "chen", "mo", "wang", "er" };
String[] sorted = Arrays.copyOf(intro1, 4);
Arrays.sort(sorted);
System.out.println(Arrays.toString(sorted));
由於排序會改變原有的陣列,所以我們使用了 copyOf()
方法重新複製了一份。來看一下輸出結果:
[chen, er, mo, wang]
可以看得出,按照的是首字母的升序進行排列的。基本資料型別是按照雙軸快速排序的,引用資料型別是按照 TimSort 排序的,使用了 Peter McIlroy 的“樂觀排序和資訊理論複雜性”中的技術。
04、陣列檢索
陣列排序後就可以使用 Arrays 類的 binarySearch()
方法進行二分查詢了。否則的話,只能線性檢索,效率就會低很多。
String[] intro1 = new String[] { "chen", "mo", "wang", "er" };
String[] sorted = Arrays.copyOf(intro1, 4);
Arrays.sort(sorted);
int exact = Arrays.binarySearch(sorted, "wang");
System.out.println(exact);
int caseInsensitive = Arrays.binarySearch(sorted, "Wang", String::compareToIgnoreCase);
System.out.println(caseInsensitive);
binarySearch()
方法既可以精確檢索,也可以模糊檢索,比如說忽略大小寫。來看一下輸出結果:
3
3
排序後的結果是 [chen, er, mo, wang]
,所以檢索出來的下標是 3。
05、陣列轉流
Stream 流非常強大,需要入門的小夥伴可以檢視我之前寫的一篇文章:
Arrays 類的 stream()
方法可以將陣列轉換成流:
String[] intro = new String[] { "沉", "默", "王", "二" };
System.out.println(Arrays.stream(intro).count());
還可以為 stream()
方法指定起始下標和結束下標:
System.out.println(Arrays.stream(intro, 1, 2).count());
如果下標的範圍有誤的時候,比如說從 2 到 1 結束,則程式會丟擲 ArrayIndexOutOfBoundsException 異常:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: origin(2) > fence(1)
at java.base/java.util.Spliterators.checkFromToBounds(Spliterators.java:387)
06、列印陣列
關於陣列的列印方式,我之前單獨寫過一篇文章:
裡面談了很多種陣列列印的方式,因為陣列是一個物件,直接 System.out.println
的話,結果是這樣的:
[Ljava.lang.String;@3d075dc0
那最優雅的方式,其實文章裡面已經出現過很多次了,就是 Arrays.toString()
:
public static String toString(Object[] 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(String.valueOf(a[i]));
if (i == iMax)
return b.append(']').toString();
b.append(", ");
}
}
小夥伴可以好好欣賞一下這段原始碼,感覺考慮得非常周到。
07、陣列轉 List
儘管陣列非常強大,但它自身可以操作的工具方法很少,比如說判斷陣列中是否包含某個值。轉成 List 的話,就簡便多了。
String[] intro = new String[] { "沉", "默", "王", "二" };
List<String> rets = Arrays.asList(intro);
System.out.println(rets.contains("二"));
不過需要注意的是,Arrays.asList()
返回的是 java.util.Arrays.ArrayList
,並不是 java.util.ArrayList
,它的長度是固定的,無法進行元素的刪除或者新增。
rets.add("三");
rets.remove("二");
執行這兩個方法的時候,會丟擲異常:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.AbstractList.add(AbstractList.java:153)
at java.base/java.util.AbstractList.add(AbstractList.java:111)
要想操作元素的話,需要多一步轉化:
List<String> rets1 = new ArrayList<>(Arrays.asList(intro));
rets1.add("三");
rets1.remove("二");
08、setAll
Java 8 新增了 setAll()
方法,它提供了一個函數語言程式設計的入口,可以對陣列的元素進行填充:
int[] array = new int[10];
Arrays.setAll(array, i -> i * 10);
System.out.println(Arrays.toString(array));
這段程式碼什麼意思呢?i 就相當於是陣列的下標,值從 0 開始,到 9 結束,那麼 i * 10
就意味著 0 * 10 開始,到 9 * 10 結束,來看一下輸出結果:
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
比之前的 fill()
方法強大多了,對吧?不再填充的是相同的元素。
09、parallelPrefix
parallelPrefix()
方法和 setAll()
方法一樣,也是 Java 8 之後提供的,提供了一個函數語言程式設計的入口,通過遍歷陣列中的元素,將當前下標位置上的元素與它之前下標的元素進行操作,然後將操作後的結果覆蓋當前下標位置上的元素。
int[] arr = new int[] { 1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> left + right);
System.out.println(Arrays.toString(arr));
上面程式碼中有一個 Lambda 表示式((left, right) -> left + right
),需要入門的小夥伴可以檢視我之前寫的一篇文章:
那為了讓小夥伴看得更明白一些,我們把上面這段程式碼稍微改造一下:
int[] arr = new int[]{1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> {
System.out.println(left + "," + right);
return left + right;
});
System.out.println(Arrays.toString(arr));
先來看一下輸出結果:
1,2
3,3
6,4
[1, 3, 6, 10]
也就是說, Lambda 表示式執行了三次:
- 第一次是 1 和 2 相加,結果是 3,替換下標為 1 的位置
- 第二次是 3 和 3 相加,結果是 6,也就是第一次的結果和下標為 2 的元素相加的結果
- 第三次是 6 和 4 相加,結果是 10,也就是第二次的結果和下標為 3 的元素相加的結果
有點強大,對吧?
10、總結
好了,我親愛的小夥伴們,以上就是本文的全部內容了,能看到這裡的都是最優秀的程式設計師,二哥必須要為你點個贊。
我是沉默王二,一枚有趣的程式設計師。如果覺得文章對你有點幫助,請微信搜尋「 沉默王二 」第一時間閱讀,回覆【666】更有我為你精心準備的 500G 高清教學視訊(已分門別類)。
本文 GitHub 已經收錄,有大廠面試完整考點,歡迎 Star。
原創不易,莫要白票,請你為本文點個贊吧,這將是我寫作更多優質文章的最強動力。