@@ -133,88 +133,128 @@ public class IOUtils {
133133 // or return one of them.
134134
135135 /**
136- * Holder for per-thread internal scratch buffers .
136+ * Holder for per-thread internal scratch buffer .
137137 *
138138 * <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
139139 * is allocated to avoid data corruption.</p>
140140 *
141141 * <p>Typical usage:</p>
142142 *
143143 * <pre>{@code
144- * final byte[] buffer = ScratchBufferHolder.getScratchByteArray();
145- * try {
144+ * try (ScratchBytes scratch = ScratchBytes.get()) {
146145 * // use the buffer
147- * } finally {
148- * ScratchBufferHolder.releaseScratchByteArray(buffer);
146+ * byte[] bytes = scratch.array();
147+ * // ...
149148 * }
150149 * }</pre>
151150 */
152- static final class ScratchBufferHolder {
151+ static final class ScratchBytes implements AutoCloseable {
153152
154153 /**
155- * Holder for internal byte array buffer.
154+ * Wraps an internal byte array.
155+ *
156+ * [0] boolean in use.
157+ * [1] byte[] buffer.
156158 */
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-
159+ private static final ThreadLocal <Object []> LOCAL = ThreadLocal .withInitial (() -> new Object [] { false , new ScratchBytes (byteArray ()) });
164160
165161 /**
166162 * Gets the internal byte array buffer.
167163 *
168164 * @return the internal byte array buffer.
169165 */
170- static byte [] getScratchByteArray () {
171- final Object [] holder = SCRATCH_BYTE_BUFFER_HOLDER .get ();
166+ static ScratchBytes get () {
167+ final Object [] holder = LOCAL .get ();
172168 // If already in use, return a new array
173169 if ((boolean ) holder [0 ]) {
174- return byteArray ();
170+ return new ScratchBytes ( byteArray () );
175171 }
176172 holder [0 ] = true ;
177- return (byte []) holder [1 ];
173+ return (ScratchBytes ) holder [1 ];
174+ }
175+
176+ private final byte [] buffer ;
177+
178+ private ScratchBytes (final byte [] buffer ) {
179+ this .buffer = buffer ;
180+ }
181+
182+ byte [] array () {
183+ return buffer ;
178184 }
179185
180186 /**
181- * Gets the char array buffer.
182- *
183- * @return the char array buffer.
187+ * If the buffer is the internal array, clear and release it for reuse.
184188 */
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 ();
189+ @ Override
190+ public void close () {
191+ final Object [] holder = LOCAL .get ();
192+ if (buffer == ((ScratchBytes ) holder [1 ]).buffer ) {
193+ Arrays .fill (buffer , (byte ) 0 );
194+ holder [0 ] = false ;
190195 }
191- holder [0 ] = true ;
192- return (char []) holder [1 ];
193196 }
197+ }
198+
199+ /**
200+ * Holder for per-thread internal scratch buffer.
201+ *
202+ * <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
203+ * is allocated to avoid data corruption.</p>
204+ *
205+ * <p>Typical usage:</p>
206+ *
207+ * <pre>{@code
208+ * try (ScratchChars scratch = ScratchChars.get()) {
209+ * // use the buffer
210+ * char[] bytes = scratch.array();
211+ * // ...
212+ * }
213+ * }</pre>
214+ */
215+ static final class ScratchChars implements AutoCloseable {
194216
217+ /**
218+ * Wraps an internal char array.
219+ *
220+ * [0] boolean in use.
221+ * [1] char[] buffer.
222+ */
223+ private static final ThreadLocal <Object []> LOCAL = ThreadLocal .withInitial (() -> new Object [] { false , new ScratchChars (charArray ()) });
195224
196225 /**
197- * If the argument is the internal byte array, release it for reuse .
226+ * Gets the internal char array buffer .
198227 *
199- * @param array the byte array to release .
228+ * @return the internal char array buffer .
200229 */
201- static void releaseScratchByteArray ( final 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 ;
230+ static ScratchChars get ( ) {
231+ final Object [] holder = LOCAL .get ();
232+ // If already in use, return a new array
233+ if (( boolean ) holder [ 0 ]) {
234+ return new ScratchChars ( charArray ()) ;
206235 }
236+ holder [0 ] = true ;
237+ return (ScratchChars ) holder [1 ];
238+ }
239+
240+ private final char [] buffer ;
241+
242+ private ScratchChars (final char [] buffer ) {
243+ this .buffer = buffer ;
244+ }
245+
246+ char [] array () {
247+ return buffer ;
207248 }
208249
209250 /**
210- * If the argument is the internal char array, release it for reuse.
211- *
212- * @param array the char array to release.
251+ * If the buffer is the internal array, clear and release it for reuse.
213252 */
214- static void releaseScratchCharArray (final char [] array ) {
215- final Object [] holder = SCRATCH_CHAR_BUFFER_HOLDER .get ();
216- if (array == holder [1 ]) {
217- Arrays .fill (array , (char ) 0 );
253+ @ Override
254+ public void close () {
255+ final Object [] holder = LOCAL .get ();
256+ if (buffer == ((ScratchChars ) holder [1 ]).buffer ) {
257+ Arrays .fill (buffer , (char ) 0 );
218258 holder [0 ] = false ;
219259 }
220260 }
@@ -660,8 +700,8 @@ static void checkFromToIndex(final int fromIndex, final int toIndex, final int l
660700 * @see IO#clear()
661701 */
662702 static void clear () {
663- ScratchBufferHolder . SCRATCH_BYTE_BUFFER_HOLDER .remove ();
664- ScratchBufferHolder . SCRATCH_CHAR_BUFFER_HOLDER .remove ();
703+ ScratchBytes . LOCAL .remove ();
704+ ScratchChars . LOCAL .remove ();
665705 }
666706
667707 /**
@@ -1205,14 +1245,14 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th
12051245 }
12061246
12071247 // reuse one
1208- final char [] array1 = ScratchBufferHolder . getScratchCharArray ();
1209- // but allocate another
1210- final char [] array2 = charArray ();
1211- int pos1 ;
1212- int pos2 ;
1213- int count1 ;
1214- int count2 ;
1215- try {
1248+ try ( ScratchChars scratch = IOUtils . ScratchChars . get ()) {
1249+ final char [] array1 = scratch . array ();
1250+ // but allocate another
1251+ final char [] array2 = charArray () ;
1252+ int pos1 ;
1253+ int pos2 ;
1254+ int count1 ;
1255+ int count2 ;
12161256 while (true ) {
12171257 pos1 = 0 ;
12181258 pos2 = 0 ;
@@ -1240,8 +1280,6 @@ public static boolean contentEquals(final Reader input1, final Reader input2) th
12401280 }
12411281 }
12421282 }
1243- } finally {
1244- ScratchBufferHolder .releaseScratchCharArray (array1 );
12451283 }
12461284 }
12471285
@@ -1722,11 +1760,8 @@ public static long copyLarge(final InputStream inputStream, final OutputStream o
17221760 * @since 2.2
17231761 */
17241762 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 );
1763+ try (ScratchBytes scratch = ScratchBytes .get ()) {
1764+ return copyLarge (input , output , inputOffset , length , scratch .array ());
17301765 }
17311766 }
17321767
@@ -1797,11 +1832,8 @@ public static long copyLarge(final InputStream input, final OutputStream output,
17971832 * @since 1.3
17981833 */
17991834 public static long copyLarge (final Reader reader , final Writer writer ) throws IOException {
1800- final char [] buffer = ScratchBufferHolder .getScratchCharArray ();
1801- try {
1802- return copyLarge (reader , writer , buffer );
1803- } finally {
1804- ScratchBufferHolder .releaseScratchCharArray (buffer );
1835+ try (ScratchChars scratch = IOUtils .ScratchChars .get ()) {
1836+ return copyLarge (reader , writer , scratch .array ());
18051837 }
18061838 }
18071839
@@ -1849,11 +1881,8 @@ public static long copyLarge(final Reader reader, final Writer writer, final cha
18491881 * @since 2.2
18501882 */
18511883 public static long copyLarge (final Reader reader , final Writer writer , final long inputOffset , final long length ) throws IOException {
1852- final char [] buffer = ScratchBufferHolder .getScratchCharArray ();
1853- try {
1854- return copyLarge (reader , writer , inputOffset , length , buffer );
1855- } finally {
1856- ScratchBufferHolder .releaseScratchCharArray (buffer );
1884+ try (ScratchChars scratch = IOUtils .ScratchChars .get ()) {
1885+ return copyLarge (reader , writer , inputOffset , length , scratch .array ());
18571886 }
18581887 }
18591888
@@ -2553,11 +2582,8 @@ public static URL resourceToURL(final String name, final ClassLoader classLoader
25532582 * @since 2.0
25542583 */
25552584 public static long skip (final InputStream input , final long skip ) throws IOException {
2556- final byte [] buffer = ScratchBufferHolder .getScratchByteArray ();
2557- try {
2558- return skip (input , skip , () -> buffer );
2559- } finally {
2560- ScratchBufferHolder .releaseScratchByteArray (buffer );
2585+ try (ScratchBytes scratch = ScratchBytes .get ()) {
2586+ return skip (input , skip , scratch ::array );
25612587 }
25622588 }
25632589
@@ -2665,18 +2691,16 @@ public static long skip(final Reader reader, final long toSkip) throws IOExcepti
26652691 throw new IllegalArgumentException ("Skip count must be non-negative, actual: " + toSkip );
26662692 }
26672693 long remain = toSkip ;
2668- final char [] charArray = ScratchBufferHolder . getScratchCharArray ();
2669- try {
2694+ try ( ScratchChars scratch = IOUtils . ScratchChars . get ()) {
2695+ final char [] chars = scratch . array ();
26702696 while (remain > 0 ) {
26712697 // 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 ));
2698+ final long n = reader .read (chars , 0 , (int ) Math .min (remain , chars .length ));
26732699 if (n < 0 ) { // EOF
26742700 break ;
26752701 }
26762702 remain -= n ;
26772703 }
2678- } finally {
2679- ScratchBufferHolder .releaseScratchCharArray (charArray );
26802704 }
26812705 return toSkip - remain ;
26822706 }
0 commit comments