Skip to content

Commit

Permalink
Improve performance of IOUtils.contentEquals(Reader, Reader).
Browse files Browse the repository at this point in the history
This is based on the PR #118 by
XenoAmess but only for this one method.
  • Loading branch information
Gary Gregory committed Jan 25, 2021
1 parent 83febda commit aa04779
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 35 deletions.
5 changes: 4 additions & 1 deletion src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ The <action> type attribute can be add,update,fix,remove.
Bump jimfs from 1.1 to 1.2 #183.
</action>
<action dev="ggregory" type="update" due-to="XenoAmess, Gary Gregory">
Improved performance of IOUtils.contentEquals(InputStream, InputStream).
Improve performance of IOUtils.contentEquals(InputStream, InputStream).
</action>
<action dev="ggregory" type="update" due-to="XenoAmess, Gary Gregory">
Improve performance of IOUtils.contentEquals(Reader, Reader).
</action>
</release>
<!-- The release date is the date RC is cut -->
Expand Down
66 changes: 43 additions & 23 deletions src/main/java/org/apache/commons/io/IOUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p>
* 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.
* </p>
*
* @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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@
* Test different implementations of {@link IOUtils#contentEquals(Reader, Reader)}.
*
* <pre>
* IOUtilsContentEqualsReadersBenchmark.testFileCurrent avgt 5 1984542.440741983.929 ns/op
* IOUtilsContentEqualsReadersBenchmark.testFilePr118 avgt 5 1903047.9961126067.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.05067526.308 ns/op
* IOUtilsContentEqualsReadersBenchmark.testFilePr118 avgt 5 1660143.543733178.893 ns/op
* IOUtilsContentEqualsReadersBenchmark.testFileRelease_2_8_0 avgt 5 1785283.975214177.764 ns/op
* IOUtilsContentEqualsReadersBenchmark.testStringCurrent avgt 5 1144495273.333 ▒ 50706166.907 ns/op
* IOUtilsContentEqualsReadersBenchmark.testStringPr118 avgt 5 1075059231.455275364676.487 ns/op
* IOUtilsContentEqualsReadersBenchmark.testStringRelease_2_8_0 avgt 5 4767157193.333139567775.251 ns/op
* </pre>
*/
@BenchmarkMode(Mode.AverageTime)
Expand All @@ -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";
Expand All @@ -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 {
Expand Down

0 comments on commit aa04779

Please sign in to comment.