使用 Java 編譯項目時產生的報錯 java: Compilation failed: internal java compiler error 錯誤原因 導致這個錯誤的原因主要有三點: 編譯版本不匹配 JDK 版本不支持 記憶體不足 解決辦法 對於編譯版本不匹配,我們要先查看 IDEA 的編譯的 ...
一、介紹
本文我們將瞭解java.util.Arrays。這是一個自Java1.2以來就存在的Java工具類。
使用Arrays,我們可以創建、比較、排序、搜索、流操作以及其他轉換操作。
二、創建
讓我們看一下通過Arrays創建數組的方法:copyOf、copyOfRange和fill。
2.1. copyOf and copyOfRange
使用copyOfRange,我們需要一個源數組和開始這索引與結束索引,前閉後開:
String[] intro = new String[]{"once", "upon", "a", "time"};
String[] abridgement = Arrays.copyOfRange(intro, 0, 3);
assertArrayEquals(new String[]{"once", "upon", "a"}, abridgement);
assertFalse(Arrays.equals(intro, abridgement));
使用copyOf,需要傳遞一個源數組和目標數組長度並且返回一個新數組,新數組長度為所傳參數長度:
String[] intro = new String[]{"once", "upon", "a", "time"};
String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);
assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised);
assertNull(expanded[4]);
註意:copyOf後,如果返回數組長度大於源數組長度會用null值來填充剩餘空間。
2.2. fill
另一種方式,我們可以使用fill創建一個固定長度的數組,當所有數組元素都一樣的時候非常有用:
String[] stutter = new String[3];
Arrays.fill(stutter, "once");
assertTrue(Stream.of(stutter).allMatch(el -> "once".equals(el));
setAll是創建不同元素的數組。註意:我們需要提前初始化數組在調用fill前。
三、比較
現在讓我們來看一下使用Arrays進行比較的方法。
3.1. equals and deepEquals
我們可以使用equals對數組進行大小和內容的簡單比較。如果我們向數組中添加了一個空元素,兩個數組就不相等了。
String[] intro = new String[]{"once", "upon", "a", "time"};
assertTrue(Arrays.equals(new String[]{"once", "upon", "a", "time"}, intro));
assertFalse(Arrays.equals(new String[]{"once", "upon", "a", "null"}, intro));
如果我們比較嵌套或多維數組,我們可以使用deepEquals不僅比較第一層元素,而且還會遞歸比較對象元素:
String[] end = new String[]{"end"};
String[] intro = new String[]{"once", "upon", "a", "time"};
Object[] story =
new Object[]{intro, new String[]{"chapter one", "chapter two"}, end};
Object[] copy =
new Object[]{intro, new String[]{"chapter one", "chapter two"}, end};
assertTrue(Arrays.deepEquals(story, copy));
assertFalse(Arrays.equals(story, copy));
上述代碼,deepEquals返回true, equals返回false。
這是因為deepEquals每次比較時都會調用自己,而equals只會比較子數組的引用。
因此,使用自引用數組的調用非常危險。
3.2. _hashCode _and deepHashCode
我們用hashCode方法來基於對象的內容計算出一個整數:
Object[] looping = new Object[]{intro, intro};
int hashBefore = Arrays.hasCode(looping);
int deepHashBefore = Arrays.deepHashCode(looping);
現在,我們將數組中一個元素置為空並重新獲取它的hash值
intro[3] = null;
int hashAfter = Arrays.hashCode(looping);
我們再試試deepHashCode,它檢查數組的每個元素的內容。我們重新來調用一次:
int deepHashAfter = Arrays.deepHashCode(looping);
現在,我們來看一些這兩個方法有什麼不同:
assertEquals(hashAfter, hashBefore);
assertNotEquals(deepHashAfter, deepHashBefore);
deepHashCode是我們在HashMap和HashSet等數據結構做為數組元素時做低層運算的。
四、排序和搜索
下麵讓我們來看一下數據的排序和搜索
4.1. sort
如果我們的元素是基本元素或實現了Compareable的對象,我們可以用sort來進行排序:
String[] sorted = Arrays.copyOf(intro, 4);
Arrays.sort(sorted);
assertArrayEquals(new String[]{"a", "once", "time", "upon"}, sorted);
sort根據數組不同的元素類型使用了不同的演算法。基本類型使用了dual-pivot quicksort,對象類型的數組使用了Timsort。兩者的時間複雜度都是O(nlog(n))。
4.2. binarySearch
在未排序的數組中搜索是線性的,但是如果是排序的數組,我們可以用二分查找在O(logn)完成:
int exact = Arrays.binarySearch(sorted, "time");
int caseInsensitive =
Arrays.binarySearch(sorted, "TiMe", String::compareToIgnoreCase);
assertEquals("time", sorted[exact]);
assertEquals(2, exact);
assertEquals(exact, caseInsensitive);
如果我們不提供比較器作為第三個參數,那麼binarySearch將依賴於我們元素類型的Comparable。再次註意,使用binarySearch的前提是已排序數組。
五、流操作
之前提到過,Arrays在Java8中進行了更新,提供了Stream API的方法,例如parallelSort、stream和setAll。
5.1. stream
stream方法讓我們像流一樣調用Stream API來使用我們的數組:
Assert.assertEquals(Arrays.stream(intro).count(), 4);
exception.expect(ArrayIndexOutOfBoundsException.class);
Arrays.stream(intro, 2, 1).count();
我們可以傳入在範圍內的索引或走出範圍的索引,超出範圍會拋出ArrayindexOutOfBoundsException。
六、轉換
最後,toString、asList和setAll為我們提供了幾種不同方法來轉換數組。
6.1. toString and deepToString
一個好的可讀性很好的將基礎類型數組轉為字元串的方法:
assertEquals("[once, upon, a time]", Arrays.toString(storyIntro));
同樣我們也可以用deepToSTring來列印對象類型的數組:
assertEquals(
"[[once, upon, a, time], [chapter one, chapter tow], [the, end]]",
Arrays.deepToString(story));
6.2. asList
Arrays方法中最方便的一個是asList,它可以將數組轉換為列表:
List<String> rets = Arrays.asList(intro);
assertTrue(rets.contains("upon"));
assertTrue(rets.contains("time"));
assertEquals(rets.size(), 4);
但是,返回的List將是固定長度,因此我們無法添加或刪除元素。註意:java.util.Arrays的方法asList返回的是它自己的ArrayList子類。
6.3. setAll
使用setAll, 設置一個生成器可以為數組初始化元素:
String[] longAgo = new String[4];
Arrays.setAll(longAgo, i -> intro[i]);
assertArrayEquals(longAgo, new String[]{"a", "long", "time", "ago"});
使用lambda表達式也是極易出異常的。
7. Parallel Prefix
Arrays在Java8引入的另一個新方法是parallelPrefix。使用parallelPrefix ,我們可以以累積的方式對輸入數組的每個元素進行操作。
7.1. parallelPrefix
做一個累加操作像[1, 2, 3, 4]輸出結果[1, 3, 6, 10]:
int[] arr = new int[]{1, 2, 3, 4};
Arrays.parallelPrefix(arr, (left, right) -> left + right);
assertThat(arr, is(new int[]{1, 3, 6, 10}));
我們也可以指定一個範圍:
int[] arr = new int[]{1, 2, 3, 4, 5};
Arrays.parallelPrefix(arr, 1, 4, (left, right) -> left + right);
assertThat(arr, is(new int[]{1, 2, 5, 9, 5}));
註意:因為該方法是並行執行的,因此累積操作應該是無狀態的。
對於非關聯函數:
int nonassociativeFunc(int left, int right) {
return left + right * left;
}
如下會產生不一致的情況:
@Test
public void whenPrefixNonAssociative_thenError() {
boolean consistent = true;
Random r = new Random();
for (int k = 0; k < 100_000; k++) {
int[] arrA = r.ints(100, 1, 5).toArray();
int[] arrB = Arrays.copyOf(arrA, arrA.length);
Arrays.parallelPrefix(arrA, this::nonassociativeFunc);
for (int i = 1; i < arrB.length; i++) {
arrB[i] = nonassociativeFunc(arrB[i - 1], arrB[i]);
}
consistent = Arrays.equals(arrA, arrB);
if(!consistent) break;
}
assertFalse(consistent);
}
7.2. Performance
並行首碼計算通過比順序迴圈更高效,尤其對於大型數組。下麵是一個基準測試結果:
Benchmark Mode Cnt Score Error Units
largeArrayLoopSum thrpt 5 9.428 ± 0.075 ops/s
largeArrayParallelPrefixSum thrpt 5 15.235 ± 0.075 ops/s
Benchmark Mode Cnt Score Error Units
largeArrayLoopSum avgt 5 105.825 ± 0.846 ops/s
largeArrayParallelPrefixSum avgt 5 65.676 ± 0.828 ops/s
下麵是基準測試代碼:
@Benchmark
public void largeArrayLoopSum(BigArray bigArray, Blackhole blackhole) {
for (int i = 0; i < ARRAY_SIZE - 1; i++) {
bigArray.data[i + 1] += bigArray.data[i];
}
blackhole.consume(bigArray.data);
}
@Benchmark
public void largeArrayParallelPrefixSum(BigArray bigArray,
Blackhole blackhole) {
Arrays.parallelPrefix(bigArray.data, (left, right) -> left + right);
blackhole.consume(bigArray.data);
}
八、總結
本文中我們瞭解瞭如何使用java.util.Arrays類創建、搜索、排序和轉換數組的一些方法。
該類在最新的Java版本中進行了擴展,包括Java8中引入了流和Java9中的加強。