@@ -132,6 +132,94 @@ public class IOUtils {
132132 // Writer. Each method should take at least one of these as a parameter,
133133 // or return one of them.
134134
135+ /**
136+ * Holder for per-thread internal scratch buffers.
137+ *
138+ * <p>Buffers are created lazily and reused within the same thread to reduce allocation overhead. In the rare case of reentrant access, a temporary buffer
139+ * is allocated to avoid data corruption.</p>
140+ *
141+ * <p>Typical usage:</p>
142+ *
143+ * <pre>{@code
144+ * final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
145+ * try {
146+ * // use the buffer
147+ * } finally {
148+ * ScratchBufferHolder.releaseScratchByteArray(buffer);
149+ * }
150+ * }</pre>
151+ */
152+ static final class ScratchBufferHolder {
153+
154+ /**
155+ * Holder for internal byte array buffer.
156+ */
157+ private static final ThreadLocal <Object []> SCRATCH_BYTE_BUFFER_HOLDER = ThreadLocal .withInitial (() -> new Object [] { false , byteArray () });
158+
159+ /**
160+ * Holder for internal char array buffer.
161+ */
162+ private static final ThreadLocal <Object []> SCRATCH_CHAR_BUFFER_HOLDER = ThreadLocal .withInitial (() -> new Object [] { false , charArray () });
163+
164+
165+ /**
166+ * Gets the internal byte array buffer.
167+ *
168+ * @return the internal byte array buffer.
169+ */
170+ static byte [] getScratchByteArray () {
171+ final Object [] holder = SCRATCH_BYTE_BUFFER_HOLDER .get ();
172+ // If already in use, return a new array
173+ if ((boolean ) holder [0 ]) {
174+ return byteArray ();
175+ }
176+ holder [0 ] = true ;
177+ return (byte []) holder [1 ];
178+ }
179+
180+ /**
181+ * Gets the char byte array buffer.
182+ *
183+ * @return the char byte array buffer.
184+ */
185+ static char [] getScratchCharArray () {
186+ final Object [] holder = SCRATCH_CHAR_BUFFER_HOLDER .get ();
187+ // If already in use, return a new array
188+ if ((boolean ) holder [0 ]) {
189+ return charArray ();
190+ }
191+ holder [0 ] = true ;
192+ return (char []) holder [1 ];
193+ }
194+
195+
196+ /**
197+ * If the argument is the internal byte array, release it for reuse.
198+ *
199+ * @param array the byte array to release.
200+ */
201+ static void releaseScratchByteArray (byte [] array ) {
202+ final Object [] holder = SCRATCH_BYTE_BUFFER_HOLDER .get ();
203+ if (array == holder [1 ]) {
204+ Arrays .fill (array , (byte ) 0 );
205+ holder [0 ] = false ;
206+ }
207+ }
208+
209+ /**
210+ * If the argument is the internal char array, release it for reuse.
211+ *
212+ * @param array the byte array to release.
213+ */
214+ static void releaseScratchCharArray (char [] array ) {
215+ final Object [] holder = SCRATCH_CHAR_BUFFER_HOLDER .get ();
216+ if (array == holder [1 ]) {
217+ Arrays .fill (array , (char ) 0 );
218+ holder [0 ] = false ;
219+ }
220+ }
221+ }
222+
135223 /**
136224 * CR char '{@value}'.
137225 *
@@ -201,16 +289,6 @@ public class IOUtils {
201289 */
202290 public static final String LINE_SEPARATOR_WINDOWS = StandardLineSeparator .CRLF .getString ();
203291
204- /**
205- * Internal byte array buffer, intended for both reading and writing.
206- */
207- private static final ThreadLocal <byte []> SCRATCH_BYTE_BUFFER = ThreadLocal .withInitial (IOUtils ::byteArray );
208-
209- /**
210- * Internal char array buffer, intended for both reading and writing.
211- */
212- private static final ThreadLocal <char []> SCRATCH_CHAR_BUFFER = ThreadLocal .withInitial (IOUtils ::charArray );
213-
214292 /**
215293 * The maximum size of an array in many Java VMs.
216294 * <p>
@@ -582,8 +660,8 @@ static void checkFromToIndex(final int fromIndex, final int toIndex, final int l
582660 * @see IO#clear()
583661 */
584662 static void clear () {
585- SCRATCH_BYTE_BUFFER .remove ();
586- SCRATCH_CHAR_BUFFER .remove ();
663+ ScratchBufferHolder . SCRATCH_BYTE_BUFFER_HOLDER .remove ();
664+ ScratchBufferHolder . SCRATCH_CHAR_BUFFER_HOLDER .remove ();
587665 }
588666
589667 /**
@@ -1127,39 +1205,43 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th
11271205 }
11281206
11291207 // reuse one
1130- final char [] array1 = getScratchCharArray ();
1208+ final char [] array1 = ScratchBufferHolder . getScratchCharArray ();
11311209 // but allocate another
11321210 final char [] array2 = charArray ();
11331211 int pos1 ;
11341212 int pos2 ;
11351213 int count1 ;
11361214 int count2 ;
1137- while (true ) {
1138- pos1 = 0 ;
1139- pos2 = 0 ;
1140- for (int index = 0 ; index < DEFAULT_BUFFER_SIZE ; index ++) {
1141- if (pos1 == index ) {
1142- do {
1143- count1 = input1 .read (array1 , pos1 , DEFAULT_BUFFER_SIZE - pos1 );
1144- } while (count1 == 0 );
1145- if (count1 == EOF ) {
1146- return pos2 == index && input2 .read () == EOF ;
1215+ try {
1216+ while (true ) {
1217+ pos1 = 0 ;
1218+ pos2 = 0 ;
1219+ for (int index = 0 ; index < DEFAULT_BUFFER_SIZE ; index ++) {
1220+ if (pos1 == index ) {
1221+ do {
1222+ count1 = input1 .read (array1 , pos1 , DEFAULT_BUFFER_SIZE - pos1 );
1223+ } while (count1 == 0 );
1224+ if (count1 == EOF ) {
1225+ return pos2 == index && input2 .read () == EOF ;
1226+ }
1227+ pos1 += count1 ;
11471228 }
1148- pos1 += count1 ;
1149- }
1150- if (pos2 == index ) {
1151- do {
1152- count2 = input2 .read (array2 , pos2 , DEFAULT_BUFFER_SIZE - pos2 );
1153- } while (count2 == 0 );
1154- if (count2 == EOF ) {
1155- return pos1 == index && input1 .read () == EOF ;
1229+ if (pos2 == index ) {
1230+ do {
1231+ count2 = input2 .read (array2 , pos2 , DEFAULT_BUFFER_SIZE - pos2 );
1232+ } while (count2 == 0 );
1233+ if (count2 == EOF ) {
1234+ return pos1 == index && input1 .read () == EOF ;
1235+ }
1236+ pos2 += count2 ;
1237+ }
1238+ if (array1 [index ] != array2 [index ]) {
1239+ return false ;
11561240 }
1157- pos2 += count2 ;
1158- }
1159- if (array1 [index ] != array2 [index ]) {
1160- return false ;
11611241 }
11621242 }
1243+ } finally {
1244+ ScratchBufferHolder .releaseScratchCharArray (array1 );
11631245 }
11641246 }
11651247
@@ -1639,9 +1721,13 @@ public static long copyLarge(final InputStream inputStream, final OutputStream o
16391721 * @throws IOException if an I/O error occurs.
16401722 * @since 2.2
16411723 */
1642- public static long copyLarge (final InputStream input , final OutputStream output , final long inputOffset ,
1643- final long length ) throws IOException {
1644- return copyLarge (input , output , inputOffset , length , getScratchByteArray ());
1724+ public static long copyLarge (final InputStream input , final OutputStream output , final long inputOffset , final long length ) throws IOException {
1725+ final byte [] buffer = ScratchBufferHolder .getScratchByteArray ();
1726+ try {
1727+ return copyLarge (input , output , inputOffset , length , buffer );
1728+ } finally {
1729+ ScratchBufferHolder .releaseScratchByteArray (buffer );
1730+ }
16451731 }
16461732
16471733 /**
@@ -1711,7 +1797,12 @@ public static long copyLarge(final InputStream input, final OutputStream output,
17111797 * @since 1.3
17121798 */
17131799 public static long copyLarge (final Reader reader , final Writer writer ) throws IOException {
1714- return copyLarge (reader , writer , getScratchCharArray ());
1800+ final char [] buffer = ScratchBufferHolder .getScratchCharArray ();
1801+ try {
1802+ return copyLarge (reader , writer , buffer );
1803+ } finally {
1804+ ScratchBufferHolder .releaseScratchCharArray (buffer );
1805+ }
17151806 }
17161807
17171808 /**
@@ -1758,7 +1849,12 @@ public static long copyLarge(final Reader reader, final Writer writer, final cha
17581849 * @since 2.2
17591850 */
17601851 public static long copyLarge (final Reader reader , final Writer writer , final long inputOffset , final long length ) throws IOException {
1761- return copyLarge (reader , writer , inputOffset , length , getScratchCharArray ());
1852+ final char [] buffer = ScratchBufferHolder .getScratchCharArray ();
1853+ try {
1854+ return copyLarge (reader , writer , inputOffset , length , buffer );
1855+ } finally {
1856+ ScratchBufferHolder .releaseScratchCharArray (buffer );
1857+ }
17621858 }
17631859
17641860 /**
@@ -1825,46 +1921,6 @@ static UnsynchronizedByteArrayOutputStream copyToOutputStream(
18251921 }
18261922 }
18271923
1828- /**
1829- * Fills the given array with 0s.
1830- *
1831- * @param arr The non-null array to fill.
1832- * @return The given array.
1833- */
1834- private static byte [] fill0 (final byte [] arr ) {
1835- Arrays .fill (arr , (byte ) 0 );
1836- return arr ;
1837- }
1838-
1839- /**
1840- * Fills the given array with 0s.
1841- *
1842- * @param arr The non-null array to fill.
1843- * @return The given array.
1844- */
1845- private static char [] fill0 (final char [] arr ) {
1846- Arrays .fill (arr , (char ) 0 );
1847- return arr ;
1848- }
1849-
1850- /**
1851- * Gets the internal byte array buffer, intended for both reading and writing.
1852- *
1853- * @return the internal byte array buffer, intended for both reading and writing.
1854- */
1855- static byte [] getScratchByteArray () {
1856- return fill0 (SCRATCH_BYTE_BUFFER .get ());
1857- }
1858-
1859- /**
1860- * Gets the char byte array buffer, intended for both reading and writing.
1861- *
1862- * @return the char byte array buffer, intended for both reading and writing.
1863- */
1864- static char [] getScratchCharArray () {
1865- return fill0 (SCRATCH_CHAR_BUFFER .get ());
1866- }
1867-
18681924 /**
18691925 * Returns the length of the given array in a null-safe manner.
18701926 *
@@ -2497,7 +2553,12 @@ public static URL resourceToURL(final String name, final ClassLoader classLoader
24972553 * @since 2.0
24982554 */
24992555 public static long skip (final InputStream input , final long skip ) throws IOException {
2500- return skip (input , skip , IOUtils ::getScratchByteArray );
2556+ final byte [] buffer = ScratchBufferHolder .getScratchByteArray ();
2557+ try {
2558+ return skip (input , skip , () -> buffer );
2559+ } finally {
2560+ ScratchBufferHolder .releaseScratchByteArray (buffer );
2561+ }
25012562 }
25022563
25032564 /**
@@ -2604,14 +2665,18 @@ public static long skip(final Reader reader, final long toSkip) throws IOExcepti
26042665 throw new IllegalArgumentException ("Skip count must be non-negative, actual: " + toSkip );
26052666 }
26062667 long remain = toSkip ;
2607- while (remain > 0 ) {
2608- // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip()
2609- final char [] charArray = getScratchCharArray ();
2610- final long n = reader .read (charArray , 0 , (int ) Math .min (remain , charArray .length ));
2611- if (n < 0 ) { // EOF
2612- break ;
2668+ final char [] charArray = ScratchBufferHolder .getScratchCharArray ();
2669+ try {
2670+ while (remain > 0 ) {
2671+ // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip()
2672+ final long n = reader .read (charArray , 0 , (int ) Math .min (remain , charArray .length ));
2673+ if (n < 0 ) { // EOF
2674+ break ;
2675+ }
2676+ remain -= n ;
26132677 }
2614- remain -= n ;
2678+ } finally {
2679+ ScratchBufferHolder .releaseScratchCharArray (charArray );
26152680 }
26162681 return toSkip - remain ;
26172682 }
0 commit comments