Skip to content

Commit aa04779

Browse files
author
Gary Gregory
committed
Improve performance of IOUtils.contentEquals(Reader, Reader).
This is based on the PR #118 by XenoAmess but only for this one method.
1 parent 83febda commit aa04779

File tree

3 files changed

+59
-35
lines changed

3 files changed

+59
-35
lines changed

src/changes/changes.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,10 @@ The <action> type attribute can be add,update,fix,remove.
195195
Bump jimfs from 1.1 to 1.2 #183.
196196
</action>
197197
<action dev="ggregory" type="update" due-to="XenoAmess, Gary Gregory">
198-
Improved performance of IOUtils.contentEquals(InputStream, InputStream).
198+
Improve performance of IOUtils.contentEquals(InputStream, InputStream).
199+
</action>
200+
<action dev="ggregory" type="update" due-to="XenoAmess, Gary Gregory">
201+
Improve performance of IOUtils.contentEquals(Reader, Reader).
199202
</action>
200203
</release>
201204
<!-- The release date is the date RC is cut -->

src/main/java/org/apache/commons/io/IOUtils.java

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
*/
1717
package org.apache.commons.io;
1818

19+
import static org.apache.commons.io.IOUtils.DEFAULT_BUFFER_SIZE;
20+
import static org.apache.commons.io.IOUtils.EOF;
21+
1922
import java.io.BufferedInputStream;
2023
import java.io.BufferedOutputStream;
2124
import java.io.BufferedReader;
@@ -790,43 +793,60 @@ public static boolean contentEquals(final InputStream input1, final InputStream
790793
}
791794

792795
/**
793-
* Compares the contents of two Readers to determine if they are equal or
794-
* not.
796+
* Compares the contents of two Readers to determine if they are equal or not.
795797
* <p>
796-
* This method buffers the input internally using
797-
* {@code BufferedReader} if they are not already buffered.
798+
* This method buffers the input internally using {@code BufferedReader} if they are not already buffered.
798799
* </p>
799800
*
800-
* @param reader1 the first reader
801-
* @param reader2 the second reader
802-
* @return true if the content of the readers are equal or they both don't
803-
* exist, false otherwise
801+
* @param input1 the first reader
802+
* @param input2 the second reader
803+
* @return true if the content of the readers are equal or they both don't exist, false otherwise
804804
* @throws NullPointerException if either input is null
805-
* @throws IOException if an I/O error occurs
805+
* @throws IOException if an I/O error occurs
806806
* @since 1.1
807807
*/
808808
@SuppressWarnings("resource")
809-
public static boolean contentEquals(final Reader reader1, final Reader reader2)
810-
throws IOException {
811-
if (reader1 == reader2) {
809+
public static boolean contentEquals(final Reader input1, final Reader input2) throws IOException {
810+
if (input1 == input2) {
812811
return true;
813812
}
814-
if (reader1 == null ^ reader2 == null) {
813+
if (input1 == null || input2 == null) {
815814
return false;
816815
}
817-
final BufferedReader bufferedInput1 = toBufferedReader(reader1);
818-
final BufferedReader bufferedInput2 = toBufferedReader(reader2);
819816

820-
int ch = bufferedInput1.read();
821-
while (EOF != ch) {
822-
final int ch2 = bufferedInput2.read();
823-
if (ch != ch2) {
824-
return false;
817+
final char[] array1 = new char[DEFAULT_BUFFER_SIZE];
818+
final char[] array2 = new char[DEFAULT_BUFFER_SIZE];
819+
int pos1;
820+
int pos2;
821+
int count1;
822+
int count2;
823+
while (true) {
824+
pos1 = 0;
825+
pos2 = 0;
826+
for (int index = 0; index < DEFAULT_BUFFER_SIZE; index++) {
827+
if (pos1 == index) {
828+
do {
829+
count1 = input1.read(array1, pos1, DEFAULT_BUFFER_SIZE - pos1);
830+
} while (count1 == 0);
831+
if (count1 == EOF) {
832+
return pos2 == index && input2.read() == EOF;
833+
}
834+
pos1 += count1;
835+
}
836+
if (pos2 == index) {
837+
do {
838+
count2 = input2.read(array2, pos2, DEFAULT_BUFFER_SIZE - pos2);
839+
} while (count2 == 0);
840+
if (count2 == EOF) {
841+
return pos1 == index && input1.read() == EOF;
842+
}
843+
pos2 += count2;
844+
}
845+
if (array1[index] != array2[index]) {
846+
return false;
847+
}
825848
}
826-
ch = bufferedInput1.read();
827849
}
828-
829-
return bufferedInput2.read() == EOF;
830850
}
831851

832852
/**

src/test/java/org/apache/commons/io/jmh/IOUtilsContentEqualsReadersBenchmark.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@
4545
* Test different implementations of {@link IOUtils#contentEquals(Reader, Reader)}.
4646
*
4747
* <pre>
48-
* IOUtilsContentEqualsReadersBenchmark.testFileCurrent avgt 5 1984542.440741983.929 ns/op
49-
* IOUtilsContentEqualsReadersBenchmark.testFilePr118 avgt 5 1903047.9961126067.279 ns/op
50-
* IOUtilsContentEqualsReadersBenchmark.testFileRelease_2_8_0 avgt 5 2000614.270 577200.820 ns/op
51-
* IOUtilsContentEqualsReadersBenchmark.testStringCurrent avgt 5 4833065053.333 ▒ 313253734.966 ns/op
52-
* IOUtilsContentEqualsReadersBenchmark.testStringPr118 avgt 5 1032292548.000 32968762.278 ns/op
53-
* IOUtilsContentEqualsReadersBenchmark.testStringRelease_2_8_0 avgt 5 4810962660.000 221405909.807 ns/op
48+
* IOUtilsContentEqualsReadersBenchmark.testFileCurrent avgt 5 1670968.05067526.308 ns/op
49+
* IOUtilsContentEqualsReadersBenchmark.testFilePr118 avgt 5 1660143.543733178.893 ns/op
50+
* IOUtilsContentEqualsReadersBenchmark.testFileRelease_2_8_0 avgt 5 1785283.975214177.764 ns/op
51+
* IOUtilsContentEqualsReadersBenchmark.testStringCurrent avgt 5 1144495273.333 ▒ 50706166.907 ns/op
52+
* IOUtilsContentEqualsReadersBenchmark.testStringPr118 avgt 5 1075059231.455275364676.487 ns/op
53+
* IOUtilsContentEqualsReadersBenchmark.testStringRelease_2_8_0 avgt 5 4767157193.333139567775.251 ns/op
5454
* </pre>
5555
*/
5656
@BenchmarkMode(Mode.AverageTime)
@@ -61,6 +61,7 @@
6161
@Fork(value = 1, jvmArgs = {"-server"})
6262
public class IOUtilsContentEqualsReadersBenchmark {
6363

64+
private static final int STRING_LEN = 1 << 24;
6465
private static final String TEST_PATH_A = "/org/apache/commons/io/testfileBOM.xml";
6566
private static final String TEST_PATH_16K_A = "/org/apache/commons/io/abitmorethan16k.txt";
6667
private static final String TEST_PATH_16K_A_COPY = "/org/apache/commons/io/abitmorethan16kcopy.txt";
@@ -69,15 +70,15 @@ public class IOUtilsContentEqualsReadersBenchmark {
6970
static String[] STRINGS = new String[5];
7071

7172
static {
72-
STRINGS[0] = StringUtils.repeat("ab", 1 << 24);
73+
STRINGS[0] = StringUtils.repeat("ab", STRING_LEN);
7374
STRINGS[1] = STRINGS[0] + 'c';
7475
STRINGS[2] = STRINGS[0] + 'd';
75-
STRINGS[3] = StringUtils.repeat("ab\rab\n", 1 << 24);
76-
STRINGS[4] = StringUtils.repeat("ab\r\nab\r", 1 << 24);
76+
STRINGS[3] = StringUtils.repeat("ab\rab\n", STRING_LEN);
77+
STRINGS[4] = StringUtils.repeat("ab\r\nab\r", STRING_LEN);
7778
}
7879

79-
static String SPECIAL_CASE_STRING_0 = StringUtils.repeat(StringUtils.repeat("ab", 1 << 24) + '\n', 2);
80-
static String SPECIAL_CASE_STRING_1 = StringUtils.repeat(StringUtils.repeat("cd", 1 << 24) + '\n', 2);
80+
static String SPECIAL_CASE_STRING_0 = StringUtils.repeat(StringUtils.repeat("ab", STRING_LEN) + '\n', 2);
81+
static String SPECIAL_CASE_STRING_1 = StringUtils.repeat(StringUtils.repeat("cd", STRING_LEN) + '\n', 2);
8182

8283
@SuppressWarnings("resource")
8384
public static boolean contentEquals_release_2_8_0(final Reader input1, final Reader input2) throws IOException {

0 commit comments

Comments
 (0)