Panduan ke Kelas java.util.Arrays

1. Pengenalan

Dalam tutorial ini, kita akan melihat java.util.Arrays , kelas utiliti yang telah menjadi sebahagian daripada Java sejak Java 1.2.

Dengan menggunakan Array, kita dapat membuat, membandingkan, menyusun, mencari, menstrim, dan mengubah tatasusunan.

2. Membuat

Mari lihat beberapa cara untuk membuat tatasusunan: copyOf , copyOfRange , dan isi.

2.1. copyOf dan copyOfRange

Untuk menggunakan copyOfRange , kami memerlukan susunan asal kami dan indeks permulaan (inklusif) dan indeks akhir (eksklusif) yang ingin kami salin:

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));

Dan untuk menggunakan copyOf , kami akan mengambil intro dan ukuran array sasaran dan kami akan mendapatkan susunan baru yang panjangnya:

String[] revised = Arrays.copyOf(intro, 3); String[] expanded = Arrays.copyOf(intro, 5); assertArrayEquals(Arrays.copyOfRange(intro, 0, 3), revised); assertNull(expanded[4]);

Perhatikan bahawa copyOf melengkapkan array dengan null s jika ukuran sasaran kita lebih besar daripada ukuran asal.

2.2. isi

Cara lain, kita boleh membuat susunan panjang tetap, adalah isian, yang berguna apabila kita mahukan tatasusunan di mana semua elemennya sama:

String[] stutter = new String[3]; Arrays.fill(stutter, "once"); assertTrue(Stream.of(stutter) .allMatch(el -> "once".equals(el));

Lihat setAll untuk membuat susunan di mana elemennya berbeza.

Perhatikan bahawa kita perlu membuat susunan diri kita sendiri sebelumnya - berbanding dengan sesuatu seperti String [] filling = Arrays.fill ("sekali" , 3) ; - kerana ciri ini diperkenalkan sebelum generik tersedia dalam bahasa.

3. Membanding

Sekarang mari kita beralih kepada kaedah untuk membandingkan tatasusunan.

3.1. sama dan deepEquals

Kita boleh menggunakan sama untuk perbandingan susunan sederhana dengan ukuran dan isi. Sekiranya kita menambah nol sebagai salah satu elemen, pemeriksaan kandungan gagal:

assertTrue( Arrays.equals(new String[] { "once", "upon", "a", "time" }, intro)); assertFalse( Arrays.equals(new String[] { "once", "upon", "a", null }, intro));

Apabila kita mempunyai susunan bersarang atau multi-dimensi, kita dapat menggunakan deepEquals untuk tidak hanya memeriksa elemen tingkat atas tetapi juga melakukan pemeriksaan secara berulang:

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));

Perhatikan bagaimana qual deepE berlalu tetapi sama gagal .

Ini kerana deepEquals akhirnya memanggil dirinya sendiri setiap kali bertemu dengan array , sementara yang sama hanya akan membandingkan rujukan sub-array.

Juga, ini membahayakan untuk memanggil array dengan rujukan diri!

3.2. hashCode dan deepHashCode

Pelaksanaan hashCode akan memberi kita bahagian lain dari kontrak sama / hashCode yang disarankan untuk objek Java. Kami menggunakan hashCode untuk menghitung bilangan bulat berdasarkan kandungan array:

Object[] looping = new Object[]{ intro, intro }; int hashBefore = Arrays.hashCode(looping); int deepHashBefore = Arrays.deepHashCode(looping);

Sekarang, kami menetapkan elemen array asal untuk membatalkan dan mengira semula nilai hash:

intro[3] = null; int hashAfter = Arrays.hashCode(looping); 

Sebagai alternatif, deepHashCode memeriksa susunan bersarang untuk memadankan bilangan elemen dan kandungan. Sekiranya kita mengira semula dengan deepHashCode :

int deepHashAfter = Arrays.deepHashCode(looping);

Sekarang, kita dapat melihat perbezaan dalam dua kaedah:

assertEquals(hashAfter, hashBefore); assertNotEquals(deepHashAfter, deepHashBefore); 

deepHashCode adalah pengiraan asas yang digunakan ketika kita bekerja dengan struktur data seperti HashMap dan HashSet pada tatasusunan .

4. Menyusun dan Mencari

Seterusnya, mari kita lihat menyusun dan mencari susunan.

4.1. mengurut

Sekiranya unsur-unsur kita sama ada primitif atau menerapkannya Berbanding , kita dapat menggunakan sort untuk melakukan semacam in-line:

String[] sorted = Arrays.copyOf(intro, 4); Arrays.sort(sorted); assertArrayEquals( new String[]{ "a", "once", "time", "upon" }, sorted);

Menjaga bahawa jenis bermutasi rujukan asal , itulah sebabnya kami melakukan salinan di sini.

sort akan menggunakan algoritma yang berbeza untuk pelbagai jenis elemen array. Jenis primitif menggunakan pintasan dwi-pangsi dan jenis Objek menggunakan Timsort. Kedua-duanya mempunyai rata-rata kes O (n log (n)) untuk array yang disusun secara rawak.

Pada Java 8, parallelSort tersedia untuk penggabungan selari. Ia menawarkan kaedah menyusun serentak menggunakan beberapa tugas Susun atur .

4.2. carian binari

Mencari dalam susunan yang tidak disusun adalah linear, tetapi jika kita mempunyai susunan yang disusun, maka kita dapat melakukannya di O (log n) , itulah yang dapat kita lakukan dengan 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);

If we don't provide a Comparator as a third parameter, then binarySearch counts on our element type being of type Comparable.

And again, note that if our array isn't first sorted, then binarySearch won't work as we expect!

5. Streaming

As we saw earlier, Arrays was updated in Java 8 to include methods using the Stream API such as parallelSort (mentioned above), stream and setAll.

5.1. stream

stream gives us full access to the Stream API for our array:

Assert.assertEquals(Arrays.stream(intro).count(), 4); exception.expect(ArrayIndexOutOfBoundsException.class); Arrays.stream(intro, 2, 1).count();

We can provide inclusive and exclusive indices for the stream however we should expect an ArrayIndexOutOfBoundsException if the indices are out of order, negative, or out of range.

6. Transforming

Finally, toString,asList, and setAll give us a couple different ways to transform arrays.

6.1. toString and deepToString

A great way we can get a readable version of our original array is with toString:

assertEquals("[once, upon, a, time]", Arrays.toString(storyIntro)); 

Again 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

Most convenient of all the Arrays methods for us to use is the asList. We have an easy way to turn an array into a list:

List rets = Arrays.asList(storyIntro); assertTrue(rets.contains("upon")); assertTrue(rets.contains("time")); assertEquals(rets.size(), 4);

However, the returned List will be a fixed length so we won't be able to add or remove elements.

Note also that, curiously, java.util.Arrays has its own ArrayList subclass, which asList returns. This can be very deceptive when debugging!

6.3. setAll

With setAll, we can set all of the elements of an array with a functional interface. The generator implementation takes the positional index as a parameter:

String[] longAgo = new String[4]; Arrays.setAll(longAgo, i -> this.getWord(i)); assertArrayEquals(longAgo, new String[]{"a","long","time","ago"});

And, of course, exception handling is one of the more dicey parts of using lambdas. So remember that here, if the lambda throws an exception, then Java doesn't define the final state of the array.

7. Parallel Prefix

Another new method in Arrays introduced since Java 8 is parallelPrefix. With parallelPrefix, we can operate on each element of the input array in a cumulative fashion.

7.1. parallelPrefix

If the operator performs addition like in the following sample, [1, 2, 3, 4] will result in [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}));

Also, we can specify a subrange for the operation:

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 }));

Notice that the method is performed in parallel, so the cumulative operation should be side-effect-free and associative.

For a non-associative function:

int nonassociativeFunc(int left, int right) { return left + right*left; }

using parallelPrefix would yield inconsistent results:

@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

Parallel prefix computation is usually more efficient than sequential loops, especially for large arrays. When running micro-benchmark on an Intel Xeon machine(6 cores) with JMH, we can see a great performance improvement:

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

Here is the benchmark code:

@Benchmark public void largeArrayLoopSum(BigArray bigArray, Blackhole blackhole) { for (int i = 0; i  left + right); blackhole.consume(bigArray.data); }

7. Conclusion

In this article, we learned how some methods for creating, searching, sorting and transforming arrays using the java.util.Arrays class.

Kelas ini telah dikembangkan dalam rilis Java baru-baru ini dengan penyertaan kaedah penghasil dan penggunaan aliran di Java 8 dan kaedah tidak sesuai di Java 9.

Sumber untuk artikel ini adalah, seperti biasa, di Github.