java.util.Arraysクラスのガイド
1. 前書き
このチュートリアルでは、Java 1.2以降Javaの一部となっているユーティリティクラスであるjava.util.Arraysを見ていきます。
Arrays, weを使用すると、配列を作成、比較、並べ替え、検索、ストリーミング、変換できます。
2. 作成
配列を作成する方法のいくつかを見てみましょう:copyOf、copyOfRange、およびfill.
2.1. copyOfおよびcopyOfRange
copyOfRangeを使用するには、コピーする元の配列と開始インデックス(包括的)および終了インデックス(排他的)が必要です。
String[] intro = new String[] { "once", "upon", "a", "time" };
String[] abridgement = Arrays.copyOfRange(storyIntro, 0, 3);
assertArrayEquals(new String[] { "once", "upon", "a" }, abridgement);
assertFalse(Arrays.equals(intro, abridgement));
また、copyOfを使用するには、intro とターゲット配列サイズを取得し、その長さの新しい配列を取得します。
String[] revised = Arrays.copyOf(intro, 3);
String[] expanded = Arrays.copyOf(intro, 5);
assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised);
assertNull(expanded[4]);
copyOf pads the array with nulls if our target size is bigger than the original size.に注意してください
2.2. fill
別の方法として、固定長の配列を作成できます。これは、すべての要素が同じである配列が必要な場合に便利なfill, です。
String[] stutter = new String[3];
Arrays.fill(stutter, "once");
assertTrue(Stream.of(stutter)
.allMatch(el -> "once".equals(el));
setAll をチェックして、要素が異なる配列を作成します。
この機能はジェネリックスが言語で利用可能になる前に導入されたため、String[] filled = Arrays.fill(“once”、3);のようなものではなく、事前に配列をインスタンス化する必要があることに注意してください。
3. 比較する
それでは、配列を比較する方法に切り替えましょう。
3.1. equals およびdeepEquals
サイズと内容による単純な配列比較には、equalsを使用できます。 要素の1つとしてnullを追加すると、コンテンツチェックは失敗します。
assertTrue(
Arrays.equals(new String[] { "once", "upon", "a", "time" }, intro));
assertFalse(
Arrays.equals(new String[] { "once", "upon", "a", null }, intro));
ネストされた配列または多次元配列がある場合、deepEqualsを使用して、最上位の要素をチェックするだけでなく、再帰的にチェックを実行することもできます。
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は合格するが、equals は不合格になることに注意してください_._
This is because deepEquals ultimately calls itself each time it encounters an arrayは、equals は単にサブ配列の参照を比較します。
また、これにより、自己参照を使用して配列を呼び出すことが危険になります。
3.2. hashCode およびdeepHashCode
hashCodeを実装すると、Javaオブジェクトに推奨されるequals /hashCodeコントラクトの他の部分が得られます。 hashCodeを使用して、配列の内容に基づいて整数を計算します。
Object[] looping = new Object[]{ intro, intro };
int hashBefore = Arrays.hashCode(looping);
int deepHashBefore = Arrays.deepHashCode(looping);
次に、元の配列の要素をnullに設定し、ハッシュ値を再計算します。
intro[3] = null;
int hashAfter = Arrays.hashCode(looping);
または、deepHashCodeは、ネストされた配列をチェックして、要素とコンテンツの数が一致していないかどうかを確認します。 deepHashCodeで再計算する場合:
int deepHashAfter = Arrays.deepHashCode(looping);
これで、2つの方法の違いがわかります。
assertEquals(hashAfter, hashBefore);
assertNotEquals(deepHashAfter, deepHashBefore);
deepHashCode is the underlying calculation used when we are working with data structures like HashMap and HashSet on arrays。
4. 並べ替えと検索
次に、配列の並べ替えと検索を見てみましょう。
4.1. sort
要素がプリミティブであるか、Comparableを実装している場合は、sort を使用してインラインソートを実行できます。
String[] sorted = Arrays.copyOf(intro, 4);
Arrays.sort(sorted);
assertArrayEquals(
new String[]{ "a", "once", "time", "upon" },
sorted);
Take care that sort mutates the original reference、これがここでコピーを実行する理由です。
sort は、配列要素タイプごとに異なるアルゴリズムを使用します。 Primitive types use a dual-pivot quicksortおよびObject types use Timsort。 どちらも、ランダムに並べ替えられた配列の平均ケースはO(n log(n)) です。
Java 8以降、parallelSort isは並列ソートマージで使用できます。 複数のArrays.sort タスクを使用する同時ソート方法を提供します。
4.2. binarySearch
並べ替えられていない配列での検索は線形ですが、並べ替えられた配列がある場合は、O(log n)で実行できます。これは、binarySearch:で実行できることです。
int exact = Arrays.binarySearch(sorted, "time");
int caseInsensitive = Arrays.binarySearch(sorted, "TiMe", String::compareToIgnoreCase);
assertEquals("time", sorted[exact]);
assertEquals(2, exact);
assertEquals(exact, caseInsensitive);
3番目のパラメータとしてComparatorを指定しない場合、binarySearchは要素タイプがタイプComparableであると見なします。
また、if our array isn’t first sorted, then binarySearch won’t work as we expect!に注意してください
5. ストリーミング
前に見たように、Arrays はJava 8で更新され、parallelSort(上記)、stream 、setAll.などのStreamAPIを使用するメソッドが含まれるようになりました。
5.1. stream
streamは、配列のStreamAPIへのフルアクセスを提供します。
Assert.assertEquals(Arrays.stream(intro).count(), 4);
exception.expect(ArrayIndexOutOfBoundsException.class);
Arrays.stream(intro, 2, 1).count();
ストリームに包括的および排他的なインデックスを提供できますが、インデックスが順不同、負、または範囲外の場合は、ArrayIndexOutOfBoundsExceptionを期待する必要があります。
6. 変身
最後に、toString,asList,とsetAll は、配列を変換するいくつかの異なる方法を提供します。
6.1. toStringおよびdeepToString
元の配列の読み取り可能なバージョンを取得するための優れた方法は、toString:を使用することです。
assertEquals("[once, upon, a, time]", Arrays.toString(storyIntro));
再びwe must use the deep version to print the contents of nested arrays:
assertEquals(
"[[once, upon, a, time], [chapter one, chapter two], [the, end]]",
Arrays.deepToString(story));
6.2. asList
使用するすべてのArraysメソッドの中で最も便利なのは、asList.です。配列をリストに変換する簡単な方法があります。
List rets = Arrays.asList(storyIntro);
assertTrue(rets.contains("upon"));
assertTrue(rets.contains("time"));
assertEquals(rets.size(), 4);
ただし、the returned List will be a fixed length so we won’t be able to add or remove elements。
不思議なことに、java.util.Arrays has its own ArrayList subclass, which asList returnsにも注意してください。 これはデバッグ時に非常に誤解を招く可能性があります!
6.3. setAll
setAllを使用すると、機能インターフェイスを使用して配列のすべての要素を設定できます。 ジェネレーターの実装は、位置インデックスをパラメーターとして受け取ります。
String[] longAgo = new String[4];
Arrays.setAll(longAgo, i -> this.getWord(i));
assertArrayEquals(longAgo, new String[]{"a","long","time","ago"});
そして、もちろん、例外処理は、ラムダを使用する際の危険な部分の1つです。 ここで、if the lambda throws an exception, then Java doesn’t define the final state of the array.を覚えておいてください
7. 並列プレフィックス
Java 8以降に導入されたArraysのもう1つの新しいメソッドは、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[] arri = new int[] { 1, 2, 3, 4, 5 };
Arrays.parallelPrefix(arri, 1, 4, (left, right) -> left + right);
assertThat(arri, is(new int[] { 1, 2, 5, 9, 5 }));
このメソッドは並列で実行されるため、the cumulative operation should be side-effect-free and associativeであることに注意してください。
非連想関数の場合:
int nonassociativeFunc(int left, int right) {
return left + right*left;
}
parallelPrefixを使用すると、一貫性のない結果が生成されます。
@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. パフォーマンス
特に大規模な配列の場合、並列プレフィックス計算は通常、順次ループよりも効率的です。 JMHを使用してIntel Xeonマシン(6コア)でマイクロベンチマークを実行すると、パフォーマンスが大幅に向上することがわかります。
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);
}
7. 結論
この記事では、java.util.Arraysクラスを使用して配列を作成、検索、並べ替え、および変換するためのいくつかのメソッドについて学習しました。
この記事のソースは、いつものように、over on Githubです。