diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 022bb3fcae5..782791be0eb 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -195,7 +195,10 @@ The type attribute can be add,update,fix,remove. Bump jimfs from 1.1 to 1.2 #183. - Improved performance of IOUtils.contentEquals(InputStream, InputStream). + Improve performance of IOUtils.contentEquals(InputStream, InputStream). + + + Improve performance of IOUtils.contentEquals(Reader, Reader). diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index 2e688246e2c..313b2e9299e 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -16,6 +16,9 @@ */ package org.apache.commons.io; +import static org.apache.commons.io.IOUtils.DEFAULT_BUFFER_SIZE; +import static org.apache.commons.io.IOUtils.EOF; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -790,43 +793,60 @@ public static boolean contentEquals(final InputStream input1, final InputStream } /** - * Compares the contents of two Readers to determine if they are equal or - * not. + * Compares the contents of two Readers to determine if they are equal or not. *

- * This method buffers the input internally using - * {@code BufferedReader} if they are not already buffered. + * This method buffers the input internally using {@code BufferedReader} if they are not already buffered. *

* - * @param reader1 the first reader - * @param reader2 the second reader - * @return true if the content of the readers are equal or they both don't - * exist, false otherwise + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal or they both don't exist, false otherwise * @throws NullPointerException if either input is null - * @throws IOException if an I/O error occurs + * @throws IOException if an I/O error occurs * @since 1.1 */ @SuppressWarnings("resource") - public static boolean contentEquals(final Reader reader1, final Reader reader2) - throws IOException { - if (reader1 == reader2) { + public static boolean contentEquals(final Reader input1, final Reader input2) throws IOException { + if (input1 == input2) { return true; } - if (reader1 == null ^ reader2 == null) { + if (input1 == null || input2 == null) { return false; } - final BufferedReader bufferedInput1 = toBufferedReader(reader1); - final BufferedReader bufferedInput2 = toBufferedReader(reader2); - int ch = bufferedInput1.read(); - while (EOF != ch) { - final int ch2 = bufferedInput2.read(); - if (ch != ch2) { - return false; + final char[] array1 = new char[DEFAULT_BUFFER_SIZE]; + final char[] array2 = new char[DEFAULT_BUFFER_SIZE]; + int pos1; + int pos2; + int count1; + int count2; + while (true) { + pos1 = 0; + pos2 = 0; + for (int index = 0; index < DEFAULT_BUFFER_SIZE; index++) { + if (pos1 == index) { + do { + count1 = input1.read(array1, pos1, DEFAULT_BUFFER_SIZE - pos1); + } while (count1 == 0); + if (count1 == EOF) { + return pos2 == index && input2.read() == EOF; + } + pos1 += count1; + } + if (pos2 == index) { + do { + count2 = input2.read(array2, pos2, DEFAULT_BUFFER_SIZE - pos2); + } while (count2 == 0); + if (count2 == EOF) { + return pos1 == index && input1.read() == EOF; + } + pos2 += count2; + } + if (array1[index] != array2[index]) { + return false; + } } - ch = bufferedInput1.read(); } - - return bufferedInput2.read() == EOF; } /** diff --git a/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java index 763eab78b11..1d365503370 100644 --- a/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java +++ b/src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java @@ -45,12 +45,12 @@ * Test different implementations of {@link IOUtils#contentEquals(Reader, Reader)}. * *
- * IOUtilsContentEqualsReadersBenchmark.testFileCurrent               avgt    5      1984542.440 ▒     741983.929  ns/op
- * IOUtilsContentEqualsReadersBenchmark.testFilePr118                 avgt    5      1903047.996 ▒    1126067.279  ns/op
- * IOUtilsContentEqualsReadersBenchmark.testFileRelease_2_8_0         avgt    5      2000614.270 ▒     577200.820  ns/op
- * IOUtilsContentEqualsReadersBenchmark.testStringCurrent             avgt    5   4833065053.333 ▒  313253734.966  ns/op
- * IOUtilsContentEqualsReadersBenchmark.testStringPr118               avgt    5   1032292548.000 ▒   32968762.278  ns/op
- * IOUtilsContentEqualsReadersBenchmark.testStringRelease_2_8_0       avgt    5   4810962660.000 ▒  221405909.807  ns/op
+ * IOUtilsContentEqualsReadersBenchmark.testFileCurrent               avgt    5      1670968.050 ▒     67526.308  ns/op
+ * IOUtilsContentEqualsReadersBenchmark.testFilePr118                 avgt    5      1660143.543 ▒    733178.893  ns/op
+ * IOUtilsContentEqualsReadersBenchmark.testFileRelease_2_8_0         avgt    5      1785283.975 ▒    214177.764  ns/op
+ * IOUtilsContentEqualsReadersBenchmark.testStringCurrent             avgt    5   1144495273.333 ▒  50706166.907  ns/op
+ * IOUtilsContentEqualsReadersBenchmark.testStringPr118               avgt    5   1075059231.455 ▒ 275364676.487  ns/op
+ * IOUtilsContentEqualsReadersBenchmark.testStringRelease_2_8_0       avgt    5   4767157193.333 ▒ 139567775.251  ns/op
  * 
*/ @BenchmarkMode(Mode.AverageTime) @@ -61,6 +61,7 @@ @Fork(value = 1, jvmArgs = {"-server"}) public class IOUtilsContentEqualsReadersBenchmark { + private static final int STRING_LEN = 1 << 24; private static final String TEST_PATH_A = "/org/apache/commons/io/testfileBOM.xml"; private static final String TEST_PATH_16K_A = "/org/apache/commons/io/abitmorethan16k.txt"; private static final String TEST_PATH_16K_A_COPY = "/org/apache/commons/io/abitmorethan16kcopy.txt"; @@ -69,15 +70,15 @@ public class IOUtilsContentEqualsReadersBenchmark { static String[] STRINGS = new String[5]; static { - STRINGS[0] = StringUtils.repeat("ab", 1 << 24); + STRINGS[0] = StringUtils.repeat("ab", STRING_LEN); STRINGS[1] = STRINGS[0] + 'c'; STRINGS[2] = STRINGS[0] + 'd'; - STRINGS[3] = StringUtils.repeat("ab\rab\n", 1 << 24); - STRINGS[4] = StringUtils.repeat("ab\r\nab\r", 1 << 24); + STRINGS[3] = StringUtils.repeat("ab\rab\n", STRING_LEN); + STRINGS[4] = StringUtils.repeat("ab\r\nab\r", STRING_LEN); } - static String SPECIAL_CASE_STRING_0 = StringUtils.repeat(StringUtils.repeat("ab", 1 << 24) + '\n', 2); - static String SPECIAL_CASE_STRING_1 = StringUtils.repeat(StringUtils.repeat("cd", 1 << 24) + '\n', 2); + static String SPECIAL_CASE_STRING_0 = StringUtils.repeat(StringUtils.repeat("ab", STRING_LEN) + '\n', 2); + static String SPECIAL_CASE_STRING_1 = StringUtils.repeat(StringUtils.repeat("cd", STRING_LEN) + '\n', 2); @SuppressWarnings("resource") public static boolean contentEquals_release_2_8_0(final Reader input1, final Reader input2) throws IOException {