From dd93554b692cabe884e7a097e431f475397a3b48 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Sun, 8 Oct 2023 09:08:28 -0400 Subject: [PATCH] Improve performance of PathUtils.fileContentEquals(Path, Path) - Add org.apache.commons.io.channels.FileChannels. - Add RandomAccessFiles#contentEquals(RandomAccessFile, RandomAccessFile). - Add RandomAccessFiles#reset(RandomAccessFile). - Add PathUtilsContentEqualsBenchmark. --- pom.xml | 8 +- src/changes/changes.xml | 23 +++- .../apache/commons/io/RandomAccessFiles.java | 55 +++++++- .../commons/io/channels/FileChannels.java | 87 +++++++++++++ .../commons/io/channels/package-info.java | 23 ++++ .../org/apache/commons/io/file/PathUtils.java | 16 +-- .../commons/io/RandomAccessFilesTest.java | 90 +++++++++++-- .../jmh/PathUtilsContentEqualsBenchmark.java | 119 ++++++++++++++++++ .../apache/commons/io/test-file-empty2.bin | 0 9 files changed, 400 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/apache/commons/io/channels/FileChannels.java create mode 100644 src/main/java/org/apache/commons/io/channels/package-info.java create mode 100644 src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java create mode 100644 src/test/resources/org/apache/commons/io/test-file-empty2.bin diff --git a/pom.xml b/pom.xml index d21b0e89fd7..c1549901bf1 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 4.0.0 commons-io commons-io - 2.14.1-SNAPSHOT + 2.15.0-SNAPSHOT Apache Commons IO 2002 @@ -52,7 +52,7 @@ file comparators, endian transformation classes, and much more. scm:git:https://gitbox.apache.org/repos/asf/commons-io.git scm:git:https://gitbox.apache.org/repos/asf/commons-io.git https://gitbox.apache.org/repos/asf?p=commons-io.git - rel/commons-io-2.13.0 + rel/commons-io-2.15.0 @@ -294,8 +294,8 @@ file comparators, endian transformation classes, and much more. io org.apache.commons.io RC1 - 2.13.0 - 2.14.0 + 2.14.0 + 2.15.0 (requires Java 8) IO 12310477 diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 2cff287e37a..1e1eee0c18c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -47,7 +47,7 @@ The type attribute can be add,update,fix,remove. - + XmlStreamReader encoding match RE is too strict. @@ -73,7 +73,28 @@ The type attribute can be add,update,fix,remove. Don't throw UncheckedIOException #491. + + RandomAccessFileMode.create(Path) provides a better NullPointerException message. + + + Improve performance of PathUtils.fileContentEquals(Path, Path, LinkOption[], OpenOption[]) by about 60%, see PathUtilsContentEqualsBenchmark. + + + Improve performance of PathUtils.fileContentEquals(Path, Path) by about 60%, see PathUtilsContentEqualsBenchmark. + + + Add org.apache.commons.io.channels.FileChannels. + + + Add RandomAccessFiles#contentEquals(RandomAccessFile, RandomAccessFile). + + + Add RandomAccessFiles#reset(RandomAccessFile). + + + Add PathUtilsContentEqualsBenchmark. + diff --git a/src/main/java/org/apache/commons/io/RandomAccessFiles.java b/src/main/java/org/apache/commons/io/RandomAccessFiles.java index 2fc071fc201..0e0140b6535 100644 --- a/src/main/java/org/apache/commons/io/RandomAccessFiles.java +++ b/src/main/java/org/apache/commons/io/RandomAccessFiles.java @@ -19,14 +19,54 @@ import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.Objects; + +import org.apache.commons.io.channels.FileChannels; /** - * Works on RandomAccessFile. + * Works with {@link RandomAccessFile}. * * @since 2.13.0 */ public class RandomAccessFiles { + /** + * Tests if two RandomAccessFile contents are equal. + * + * @param raf1 A RandomAccessFile. + * @param raf2 Another RandomAccessFile. + * @return true if the contents of both RandomAccessFiles are equal, false otherwise. + * @throws IOException if an I/O error occurs. + * @since 2.15.0 + */ + @SuppressWarnings("resource") // See comments + public static boolean contentEquals(final RandomAccessFile raf1, final RandomAccessFile raf2) throws IOException { + // Short-circuit test + if (Objects.equals(raf1, raf2)) { + return true; + } + // Short-circuit test + final long length1 = length(raf1); + final long length2 = length(raf2); + if (length1 != length2) { + return false; + } + if (length1 == 0 && length2 == 0) { + return true; + } + // Dig in and to the work + // We do not close FileChannels because that closes the owning RandomAccessFile. + // Instead, the caller is assumed to manage the given RandomAccessFile objects. + final FileChannel channel1 = raf1.getChannel(); + final FileChannel channel2 = raf2.getChannel(); + return FileChannels.contentEquals(channel1, channel2, IOUtils.DEFAULT_BUFFER_SIZE); + } + + private static long length(final RandomAccessFile raf) throws IOException { + return raf != null ? raf.length() : 0; + } + /** * Reads a byte array starting at "position" for "length" bytes. * @@ -42,4 +82,17 @@ public static byte[] read(final RandomAccessFile input, final long position, fin return IOUtils.toByteArray(input::read, length); } + /** + * Resets the given file to position 0. + * + * @param raf The RandomAccessFile to reset. + * @return The given RandomAccessFile. + * @throws IOException If {@code pos} is less than {@code 0} or if an I/O error occurs. + * @since 2.15.0 + */ + public static RandomAccessFile reset(final RandomAccessFile raf) throws IOException { + raf.seek(0); + return raf; + } + } diff --git a/src/main/java/org/apache/commons/io/channels/FileChannels.java b/src/main/java/org/apache/commons/io/channels/FileChannels.java new file mode 100644 index 00000000000..b6e2811e6b3 --- /dev/null +++ b/src/main/java/org/apache/commons/io/channels/FileChannels.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.channels; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; + +/** + * Works with {@link FileChannel}. + * + * @since 2.15.0 + */ +public final class FileChannels { + + /** + * Don't instantiate. + */ + private FileChannels() { + // no-op + } + + /** + * Tests if two RandomAccessFiles contents are equal. + * + * @param channel1 A FileChannel. + * @param channel2 Another FileChannel. + * @param byteBufferSize The two internal buffer capacities, in bytes. + * @return true if the contents of both RandomAccessFiles are equal, false otherwise. + * @throws IOException if an I/O error occurs. + */ + public static boolean contentEquals(final FileChannel channel1, final FileChannel channel2, final int byteBufferSize) throws IOException { + // Short-circuit test + if (Objects.equals(channel1, channel2)) { + return true; + } + // Short-circuit test + final long size1 = size(channel1); + final long size2 = size(channel2); + if (size1 != size2) { + return false; + } + if (size1 == 0 && size2 == 0) { + return true; + } + // Dig in and do the work + final ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(byteBufferSize); + final ByteBuffer byteBuffer2 = ByteBuffer.allocateDirect(byteBufferSize); + while (true) { + final int read1 = channel1.read(byteBuffer1); + final int read2 = channel2.read(byteBuffer2); + if (read1 == IOUtils.EOF && read2 == IOUtils.EOF) { + return byteBuffer1.equals(byteBuffer2); + } + if (read1 != read2) { + return false; + } + if (!byteBuffer1.equals(byteBuffer2)) { + return false; + } + byteBuffer1.clear(); + byteBuffer2.clear(); + } + } + + private static long size(final FileChannel channel) throws IOException { + return channel != null ? channel.size() : 0; + } +} diff --git a/src/main/java/org/apache/commons/io/channels/package-info.java b/src/main/java/org/apache/commons/io/channels/package-info.java new file mode 100644 index 00000000000..51a7ca28e39 --- /dev/null +++ b/src/main/java/org/apache/commons/io/channels/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provides classes to work with {@link java.nio.channels}. + * + * @since 2.15.0 + */ +package org.apache.commons.io.channels; diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java b/src/main/java/org/apache/commons/io/file/PathUtils.java index ba553df225f..71ab166e2fb 100644 --- a/src/main/java/org/apache/commons/io/file/PathUtils.java +++ b/src/main/java/org/apache/commons/io/file/PathUtils.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.RandomAccessFile; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; @@ -69,7 +70,8 @@ import org.apache.commons.io.Charsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; +import org.apache.commons.io.RandomAccessFileMode; +import org.apache.commons.io.RandomAccessFiles; import org.apache.commons.io.ThreadUtils; import org.apache.commons.io.file.Counters.PathCounters; import org.apache.commons.io.file.attribute.FileTimes; @@ -717,20 +719,20 @@ public static boolean fileContentEquals(final Path path1, final Path path2) thro /** * Compares the file contents of two Paths to determine if they are equal or not. *

- * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}. + * File content is accessed through {@link RandomAccessFileMode#create(Path)}. *

* * @param path1 the first stream. * @param path2 the second stream. * @param linkOptions options specifying how files are followed. - * @param openOptions options specifying how files are opened. + * @param openOptions ignored. * @return true if the content of the streams are equal or they both don't exist, false otherwise. * @throws NullPointerException if openOptions is null. * @throws IOException if an I/O error occurs. * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File) */ public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions) - throws IOException { + throws IOException { if (path1 == null && path2 == null) { return true; } @@ -764,9 +766,9 @@ public static boolean fileContentEquals(final Path path1, final Path path2, fina // same file return true; } - try (InputStream inputStream1 = Files.newInputStream(nPath1, openOptions); - InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) { - return IOUtils.contentEquals(inputStream1, inputStream2); + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(path1.toRealPath(linkOptions)); + RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(path2.toRealPath(linkOptions))) { + return RandomAccessFiles.contentEquals(raf1, raf2); } } diff --git a/src/test/java/org/apache/commons/io/RandomAccessFilesTest.java b/src/test/java/org/apache/commons/io/RandomAccessFilesTest.java index d3873599555..ce7fa8fdf44 100644 --- a/src/test/java/org/apache/commons/io/RandomAccessFilesTest.java +++ b/src/test/java/org/apache/commons/io/RandomAccessFilesTest.java @@ -19,10 +19,15 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; import org.junit.jupiter.api.Test; @@ -31,32 +36,101 @@ */ public class RandomAccessFilesTest { - protected static final String FILE_RES_RO = "/org/apache/commons/io/test-file-20byteslength.bin"; - protected static final String FILE_NAME_RO = "src/test/resources" + FILE_RES_RO; + private static final String FILE_NAME_RO_20 = "src/test/resources/org/apache/commons/io/test-file-20byteslength.bin"; + private static final String FILE_NAME_RO_0 = "src/test/resources/org/apache/commons/io/test-file-empty.bin"; + private static final String FILE_NAME_RO_0_BIS = "src/test/resources/org/apache/commons/io/test-file-empty2.bin"; + + @Test + public void testContentEquals() throws IOException { + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { + assertEquals(raf1, raf1); + assertTrue(RandomAccessFiles.contentEquals(raf1, raf1)); + } + // as above, to make sure resources are OK + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { + assertEquals(raf1, raf1); + assertTrue(RandomAccessFiles.contentEquals(raf1, raf1)); + } + // same 20 bytes + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20); + RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { + assertTrue(RandomAccessFiles.contentEquals(raf1, raf2)); + } + // same empty file + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_0); + RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_0)) { + assertTrue(RandomAccessFiles.contentEquals(raf1, raf2)); + assertTrue(RandomAccessFiles.contentEquals(RandomAccessFiles.reset(raf2), RandomAccessFiles.reset(raf1))); + } + // diff empty file + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_0); + RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_0_BIS)) { + assertTrue(RandomAccessFiles.contentEquals(raf1, raf2)); + assertTrue(RandomAccessFiles.contentEquals(RandomAccessFiles.reset(raf2), RandomAccessFiles.reset(raf1))); + } + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_0); + RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { + assertFalse(RandomAccessFiles.contentEquals(raf1, raf2)); + assertFalse(RandomAccessFiles.contentEquals(RandomAccessFiles.reset(raf2), RandomAccessFiles.reset(raf1))); + } + // + final Path bigFile1 = Files.createTempFile(getClass().getSimpleName(), "-1.bin"); + final Path bigFile2 = Files.createTempFile(getClass().getSimpleName(), "-2.bin"); + final Path bigFile3 = Files.createTempFile(getClass().getSimpleName(), "-3.bin"); + try { + final int newLength = 1_000_000; + final byte[] bytes1 = new byte[newLength]; + final byte[] bytes2 = new byte[newLength]; + Arrays.fill(bytes1, (byte) 1); + Arrays.fill(bytes2, (byte) 2); + Files.write(bigFile1, bytes1); + Files.write(bigFile2, bytes2); + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(bigFile1); + RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(bigFile2)) { + assertFalse(RandomAccessFiles.contentEquals(raf1, raf2)); + assertFalse(RandomAccessFiles.contentEquals(RandomAccessFiles.reset(raf2), RandomAccessFiles.reset(raf1))); + assertTrue(RandomAccessFiles.contentEquals(RandomAccessFiles.reset(raf1), RandomAccessFiles.reset(raf1))); + } + // Make the last byte different + final byte[] bytes3 = bytes1.clone(); + bytes3[bytes3.length - 1] = 9; + Files.write(bigFile3, bytes3); + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(bigFile1); + RandomAccessFile raf3 = RandomAccessFileMode.READ_ONLY.create(bigFile3)) { + assertFalse(RandomAccessFiles.contentEquals(raf1, raf3)); + assertFalse(RandomAccessFiles.contentEquals(RandomAccessFiles.reset(raf3), RandomAccessFiles.reset(raf1))); + } + } finally { + // Delete ASAP + Files.deleteIfExists(bigFile1); + Files.deleteIfExists(bigFile2); + Files.deleteIfExists(bigFile3); + } + } @Test public void testRead() throws IOException { - try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) { + try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { final byte[] buffer = RandomAccessFiles.read(raf, 0, 0); assertArrayEquals(new byte[] {}, buffer); } - try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) { + try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { final byte[] buffer = RandomAccessFiles.read(raf, 1, 0); assertArrayEquals(new byte[] {}, buffer); } - try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) { + try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { final byte[] buffer = RandomAccessFiles.read(raf, 0, 1); assertArrayEquals(new byte[] { '1' }, buffer); } - try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) { + try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { final byte[] buffer = RandomAccessFiles.read(raf, 1, 1); assertArrayEquals(new byte[] { '2' }, buffer); } - try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) { + try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { final byte[] buffer = RandomAccessFiles.read(raf, 0, 20); assertEquals(20, buffer.length); } - try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) { + try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO_20)) { assertThrows(IOException.class, () -> RandomAccessFiles.read(raf, 0, 21)); } } diff --git a/src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java b/src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java new file mode 100644 index 00000000000..b45016ae2d6 --- /dev/null +++ b/src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.io.jmh; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.RandomAccessFileMode; +import org.apache.commons.io.RandomAccessFiles; +import org.apache.commons.io.file.PathUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * Test different implementations of {@link PathUtils#fileContentEquals(Path, Path)}. + * + *
+ * Benchmark                                                                Mode  Cnt    Score   Error  Units
+ * PathUtilsContentEqualsBenchmark.testCurrent_fileContentEquals            avgt    5    4.538 ▒  1.010  ms/op
+ * PathUtilsContentEqualsBenchmark.testCurrent_fileContentEquals_Blackhole  avgt    5  110.627 ▒ 30.317  ms/op
+ * PathUtilsContentEqualsBenchmark.testProposal_contentEquals               avgt    5    1.812 ▒  0.634  ms/op
+ * PathUtilsContentEqualsBenchmark.testProposal_contentEquals_Blackhole     avgt    5   43.521 ▒  6.762  ms/op
+ * 
+ */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +@Warmup(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) +@Fork(value = 1, jvmArgs = { "-server" }) +public class PathUtilsContentEqualsBenchmark { + + private static final Path bigFile1; + private static final Path bigFile2; + + static { + // Set up test fixtures + try { + bigFile1 = Files.createTempFile(PathUtilsContentEqualsBenchmark.class.getSimpleName(), "-1.bin"); + bigFile2 = Files.createTempFile(PathUtilsContentEqualsBenchmark.class.getSimpleName(), "-2.bin"); + final int newLength = 1_000_000; + final byte[] bytes1 = new byte[newLength]; + Arrays.fill(bytes1, (byte) 1); + Files.write(bigFile1, bytes1); + Files.copy(bigFile1, bigFile2, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static boolean newFileContentEquals(final Path path1, final Path path2) throws IOException { + try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(path1); + RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(path2)) { + return RandomAccessFiles.contentEquals(raf1, raf2); + } + } + + @Benchmark + public boolean[] testCurrent_fileContentEquals() throws IOException { + final boolean[] res = new boolean[1]; + res[0] = PathUtils.fileContentEquals(bigFile1, bigFile2); + return res; + } + + @Benchmark + public boolean[] testProposal_contentEquals() throws IOException { + final boolean[] res = new boolean[1]; + res[0] = newFileContentEquals(bigFile1, bigFile2); + return res; + } + + @Benchmark + public void testCurrent_fileContentEquals_Blackhole(final Blackhole blackhole) throws IOException { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + blackhole.consume(PathUtils.fileContentEquals(bigFile1, bigFile2)); + } + } + } + + @Benchmark + public void testProposal_contentEquals_Blackhole(final Blackhole blackhole) throws IOException { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + blackhole.consume(newFileContentEquals(bigFile1, bigFile2)); + } + } + } + +} diff --git a/src/test/resources/org/apache/commons/io/test-file-empty2.bin b/src/test/resources/org/apache/commons/io/test-file-empty2.bin new file mode 100644 index 00000000000..e69de29bb2d