You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2023/10/08 13:08:33 UTC

[commons-io] branch master updated (43594ce6 -> dd93554b)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


    from 43594ce6 Revert "[IO-414] don't write a BOM on every (or any) line (#492)"
     new 67bc02cc RandomAccessFileMode.create(Path) provides a better NullPointerException message.
     new dd93554b Improve performance of PathUtils.fileContentEquals(Path, Path)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |   8 +-
 src/changes/changes.xml                            |  23 +++-
 .../apache/commons/io/RandomAccessFileMode.java    |   3 +-
 .../org/apache/commons/io/RandomAccessFiles.java   |  55 +++++++++-
 .../apache/commons/io/channels/FileChannels.java   |  87 +++++++++++++++
 .../io/{build => channels}/package-info.java       |   6 +-
 .../java/org/apache/commons/io/file/PathUtils.java |  16 +--
 .../apache/commons/io/RandomAccessFilesTest.java   |  90 ++++++++++++++--
 .../io/jmh/PathUtilsContentEqualsBenchmark.java    | 119 +++++++++++++++++++++
 .../org/apache/commons/io/test-file-empty2.bin}    |   0
 10 files changed, 382 insertions(+), 25 deletions(-)
 create mode 100644 src/main/java/org/apache/commons/io/channels/FileChannels.java
 copy src/main/java/org/apache/commons/io/{build => channels}/package-info.java (87%)
 create mode 100644 src/test/java/org/apache/commons/io/jmh/PathUtilsContentEqualsBenchmark.java
 copy src/{site/resources/profile.jacoco => test/resources/org/apache/commons/io/test-file-empty2.bin} (100%)


[commons-io] 02/02: Improve performance of PathUtils.fileContentEquals(Path, Path)

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git

commit dd93554b692cabe884e7a097e431f475397a3b48
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Oct 8 09:08:28 2023 -0400

    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 +++-
 .../org/apache/commons/io/RandomAccessFiles.java   |  55 +++++++++-
 .../apache/commons/io/channels/FileChannels.java   |  87 +++++++++++++++
 .../apache/commons/io/channels/package-info.java   |  23 ++++
 .../java/org/apache/commons/io/file/PathUtils.java |  16 +--
 .../apache/commons/io/RandomAccessFilesTest.java   |  90 ++++++++++++++--
 .../io/jmh/PathUtilsContentEqualsBenchmark.java    | 119 +++++++++++++++++++++
 .../org/apache/commons/io/test-file-empty2.bin     |   0
 9 files changed, 400 insertions(+), 21 deletions(-)

diff --git a/pom.xml b/pom.xml
index d21b0e89..c1549901 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
-  <version>2.14.1-SNAPSHOT</version>
+  <version>2.15.0-SNAPSHOT</version>
   <name>Apache Commons IO</name>
 
   <inceptionYear>2002</inceptionYear>
@@ -52,7 +52,7 @@ file comparators, endian transformation classes, and much more.
     <connection>scm:git:https://gitbox.apache.org/repos/asf/commons-io.git</connection>
     <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-io.git</developerConnection>
     <url>https://gitbox.apache.org/repos/asf?p=commons-io.git</url>
-    <tag>rel/commons-io-2.13.0</tag>
+    <tag>rel/commons-io-2.15.0</tag>
   </scm>
 
   <developers>
@@ -294,8 +294,8 @@ file comparators, endian transformation classes, and much more.
     <commons.componentid>io</commons.componentid>
     <commons.module.name>org.apache.commons.io</commons.module.name>
     <commons.rc.version>RC1</commons.rc.version>
-    <commons.bc.version>2.13.0</commons.bc.version>
-    <commons.release.version>2.14.0</commons.release.version>
+    <commons.bc.version>2.14.0</commons.bc.version>
+    <commons.release.version>2.15.0</commons.release.version>
     <commons.release.desc>(requires Java 8)</commons.release.desc>
     <commons.jira.id>IO</commons.jira.id>
     <commons.jira.pid>12310477</commons.jira.pid>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 2cff287e..1e1eee0c 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -47,7 +47,7 @@ The <action> type attribute can be add,update,fix,remove.
   </properties>
 
   <body>
-    <release version="2.14.1" date="202Y-MM-DD" description="Java 8 is required.">
+    <release version="2.15.0" date="202Y-MM-DD" description="Java 8 is required.">
       <!-- FIX -->
       <action dev="sebb" type="fix" issue="IO-810" due-to="Laurence Gonsalves">
         XmlStreamReader encoding match RE is too strict.
@@ -73,7 +73,28 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="fix" issue="IO-814" due-to="Elliotte Rusty Harold, Gary Gregory">
         Don't throw UncheckedIOException #491. 
       </action>
+      <action dev="ggregory" type="fix" issue="IO-814" due-to="Gary Gregory">
+        RandomAccessFileMode.create(Path) provides a better NullPointerException message.         
+      </action>
+      <action dev="ggregory" type="fix" due-to="Gary Gregory">
+        Improve performance of PathUtils.fileContentEquals(Path, Path, LinkOption[], OpenOption[]) by about 60%, see PathUtilsContentEqualsBenchmark.
+      </action>
+      <action dev="ggregory" type="fix" due-to="Gary Gregory">
+        Improve performance of PathUtils.fileContentEquals(Path, Path) by about 60%, see PathUtilsContentEqualsBenchmark.
+      </action>
       <!-- ADD -->
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add org.apache.commons.io.channels.FileChannels.
+      </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add RandomAccessFiles#contentEquals(RandomAccessFile, RandomAccessFile).
+      </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add RandomAccessFiles#reset(RandomAccessFile).
+      </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add PathUtilsContentEqualsBenchmark.
+      </action>
       <!-- UPDATE -->
     </release>
     <release version="2.14.0" date="2023-09-24" description="Java 8 is required.">
diff --git a/src/main/java/org/apache/commons/io/RandomAccessFiles.java b/src/main/java/org/apache/commons/io/RandomAccessFiles.java
index 2fc071fc..0e0140b6 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 @@ package org.apache.commons.io;
 
 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 class RandomAccessFiles {
         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 00000000..b6e2811e
--- /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 00000000..51a7ca28
--- /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 ba553df2..71ab166e 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.File;
 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 java.util.stream.Stream;
 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 final class PathUtils {
     /**
      * Compares the file contents of two Paths to determine if they are equal or not.
      * <p>
-     * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
+     * File content is accessed through {@link RandomAccessFileMode#create(Path)}.
      * </p>
      *
      * @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 final class PathUtils {
             // 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 d3873599..ce7fa8fd 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 @@ package org.apache.commons.io;
 
 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 @@ import org.junit.jupiter.api.Test;
  */
 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 00000000..b45016ae
--- /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)}.
+ *
+ * <pre>
+ * 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
+ * </pre>
+ */
+@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 00000000..e69de29b


[commons-io] 01/02: RandomAccessFileMode.create(Path) provides a better NullPointerException message.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git

commit 67bc02cc74ffc3ac6f50afb66ab02c99b8ab29d8
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Oct 8 08:25:09 2023 -0400

    RandomAccessFileMode.create(Path) provides a better NullPointerException
    message.
---
 src/main/java/org/apache/commons/io/RandomAccessFileMode.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/commons/io/RandomAccessFileMode.java b/src/main/java/org/apache/commons/io/RandomAccessFileMode.java
index 54061588..8b51e439 100644
--- a/src/main/java/org/apache/commons/io/RandomAccessFileMode.java
+++ b/src/main/java/org/apache/commons/io/RandomAccessFileMode.java
@@ -20,6 +20,7 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.RandomAccessFile;
 import java.nio.file.Path;
+import java.util.Objects;
 
 /**
  * Access modes and factory methods for {@link RandomAccessFile}.
@@ -77,7 +78,7 @@ public enum RandomAccessFileMode {
      * @throws FileNotFoundException See {@link RandomAccessFile#RandomAccessFile(File, String)}.
      */
     public RandomAccessFile create(final Path file) throws FileNotFoundException {
-        return create(file.toFile());
+        return create(Objects.requireNonNull(file.toFile(), "file"));
     }
 
     /**