Ръководство за клас java.util.Arrays

1. Въведение

В този урок ще разгледаме java.util.Arrays , полезен клас, който е част от Java от Java 1.2.

Използвайки масиви, можем да създаваме, сравняваме, сортираме, търсим, предаваме и трансформираме масиви.

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 , щяхме да направим въведение и размер на целевия масив и да получим нов масив с тази дължина:

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

Имайте предвид, че copyOf поставя масива с null s, ако целевият ни размер е по-голям от оригиналния размер.

2.2. запълване

Друг начин е да създадем масив с фиксирана дължина е fill, което е полезно, когато искаме масив, където всички елементи са еднакви:

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

Разгледайте setAll, за да създадете масив, в който елементите са различни.

Обърнете внимание, че трябва да инстанцираме масива сами предварително - за разлика от нещо като String [] fill = Arrays.fill („веднъж“ , 3) ; - откакто тази функция беше въведена преди генеричните лекарства да са налични на езика.

3. Сравняване

Сега нека преминем към методи за сравняване на масиви.

3.1. equals и deepEquals

Можем да използваме равни за просто сравнение на масиви по размер и съдържание. Ако добавим 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));

Обърнете внимание как deepE quals преминава, но равно се проваля .

Това е така, защото deepEquals в крайна сметка се извиква всеки път, когато срещне масив , докато equals просто ще сравнява препратките на под-масивите.

Също така, това прави опасно извикването на масив със самопрепратка!

3.2. hashCode и deepHashCode

Внедряването на hashCode ще ни даде другата част от договора equals / hashCode, която се препоръчва за Java обекти. Използваме hashCode за изчисляване на цяло число въз основа на съдържанието на масива:

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

Сега задаваме елемент на оригиналния масив на нула и преизчисляваме хеш стойностите:

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

Като алтернатива deepHashCode проверява вложените масиви за съвпадение на броя на елементите и съдържанието. Ако преизчислим с deepHashCode :

int deepHashAfter = Arrays.deepHashCode(looping);

Сега можем да видим разликата в двата метода:

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

deepHashCode е основното изчисление, използвано, когато работим със структури от данни като HashMap и HashSet върху масиви .

4. Сортиране и търсене

След това нека да разгледаме сортирането и търсенето на масиви.

4.1. вид

Ако нашите елементи са или примитиви, или те изпълняват Сравнимо , можем да използваме сортиране, за да извършим вътрешно сортиране:

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

Внимавайте, че сортирането мутира оригиналната препратка , поради което правим копие тук.

sort ще използва различен алгоритъм за различни типове елементи на масив. Примитивните типове използват двоен пивот, а типовете обекти използват Timsort. И двете имат средния случай на O (n log (n)) за произволно сортиран масив.

Към Java 8, parallelSort е на разположение за паралелен вид Разделяне. Той предлага едновременен метод за сортиране, използващ няколко задачи на 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);

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.

Този клас е разширен в по-новите версии на Java с включване на методи за производство и консумация на потоци в Java 8 и методи за несъответствие в Java 9.

Източникът на тази статия е, както винаги, в Github.