Skip to content

Commit b3960d9

Browse files
authored
Unsynchronized ByteArrayInputStream implementation (#109)
* Add a non-synchronized ByteArrayInputStream implementation - FastByteArrayInputStream * Address review comments by @garydgregory
1 parent ca3c823 commit b3960d9

5 files changed

Lines changed: 560 additions & 6 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.InputStream;
20+
import java.util.Objects;
21+
22+
import static java.lang.Math.min;
23+
24+
/**
25+
* This is an alternative to {@link java.io.ByteArrayInputStream}
26+
* which removes the synchronization overhead for non-concurrent
27+
* access; as such this class is not thread-safe.
28+
*
29+
* @since 2.7
30+
*/
31+
//@NotThreadSafe
32+
public class UnsynchronizedByteArrayInputStream extends InputStream {
33+
34+
public static final int END_OF_STREAM = -1;
35+
36+
/**
37+
* The underlying data buffer.
38+
*/
39+
private final byte[] data;
40+
41+
/**
42+
* End Of Data.
43+
*
44+
* Similar to data.length,
45+
* i.e. the last readable offset + 1.
46+
*/
47+
private final int eod;
48+
49+
/**
50+
* Current offset in the data buffer.
51+
*/
52+
private int offset;
53+
54+
/**
55+
* The current mark (if any).
56+
*/
57+
private int markedOffset;
58+
59+
/**
60+
* Creates a new byte array input stream.
61+
*
62+
* @param data the buffer
63+
*/
64+
public UnsynchronizedByteArrayInputStream(final byte[] data) {
65+
Objects.requireNonNull(data);
66+
this.data = data;
67+
this.offset = 0;
68+
this.eod = data.length;
69+
this.markedOffset = this.offset;
70+
}
71+
72+
/**
73+
* Creates a new byte array input stream.
74+
*
75+
* @param data the buffer
76+
* @param offset the offset into the buffer
77+
*
78+
* @throws IllegalArgumentException if the offset is less than zero
79+
*/
80+
public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset) {
81+
Objects.requireNonNull(data);
82+
if (offset < 0) {
83+
throw new IllegalArgumentException("offset cannot be negative");
84+
}
85+
this.data = data;
86+
this.offset = min(offset, data.length > 0 ? data.length: offset);
87+
this.eod = data.length;
88+
this.markedOffset = this.offset;
89+
}
90+
91+
92+
/**
93+
* Creates a new byte array input stream.
94+
*
95+
* @param data the buffer
96+
* @param offset the offset into the buffer
97+
* @param length the length of the buffer
98+
*
99+
* @throws IllegalArgumentException if the offset or length less than zero
100+
*/
101+
public UnsynchronizedByteArrayInputStream(final byte[] data, final int offset, final int length) {
102+
Objects.requireNonNull(data);
103+
if (offset < 0) {
104+
throw new IllegalArgumentException("offset cannot be negative");
105+
}
106+
if (length < 0) {
107+
throw new IllegalArgumentException("length cannot be negative");
108+
}
109+
this.data = data;
110+
this.offset = min(offset, data.length > 0 ? data.length : offset);
111+
this.eod = min(this.offset + length, data.length);
112+
this.markedOffset = this.offset;
113+
}
114+
115+
@Override
116+
public int available() {
117+
return offset < eod ? eod - offset : 0;
118+
}
119+
120+
@Override
121+
public int read() {
122+
return offset < eod ? data[offset++] & 0xff : END_OF_STREAM;
123+
}
124+
125+
@Override
126+
public int read(final byte[] b) {
127+
Objects.requireNonNull(b);
128+
return read(b, 0, b.length);
129+
}
130+
131+
@Override
132+
public int read(final byte[] b, final int off, final int len) {
133+
Objects.requireNonNull(b);
134+
if (off < 0 || len < 0 || off + len > b.length) {
135+
throw new IndexOutOfBoundsException();
136+
}
137+
138+
if (offset >= eod) {
139+
return END_OF_STREAM;
140+
}
141+
142+
int actualLen = eod - offset;
143+
if (len < actualLen) {
144+
actualLen = len;
145+
}
146+
if (actualLen <= 0) {
147+
return 0;
148+
}
149+
System.arraycopy(data, offset, b, off, actualLen);
150+
offset += actualLen;
151+
return actualLen;
152+
}
153+
154+
@Override
155+
public long skip(final long n) {
156+
if(n < 0) {
157+
throw new IllegalArgumentException("Skipping backward is not supported");
158+
}
159+
160+
long actualSkip = eod - offset;
161+
if (n < actualSkip) {
162+
actualSkip = n;
163+
}
164+
165+
offset += actualSkip;
166+
return actualSkip;
167+
}
168+
169+
@Override
170+
public boolean markSupported() {
171+
return true;
172+
}
173+
174+
@Override
175+
public void mark(final int readlimit) {
176+
this.markedOffset = this.offset;
177+
}
178+
179+
@Override
180+
public void reset() {
181+
this.offset = this.markedOffset;
182+
}
183+
}

src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import org.apache.commons.io.input.ClosedInputStream;
2020

21-
import java.io.ByteArrayInputStream;
2221
import java.io.InputStream;
2322
import java.io.IOException;
2423
import java.io.OutputStream;
@@ -294,20 +293,26 @@ protected void writeToImpl(final OutputStream out) throws IOException {
294293
* returned stream is backed by buffers of <code>this</code> stream,
295294
* avoiding memory allocation and copy, thus saving space and time.<br>
296295
*
296+
* @param <T> the type of the InputStream which makes up
297+
* the {@link SequenceInputStream}.
298+
* @param isConstructor A constructor for an InputStream which makes
299+
* up the {@link SequenceInputStream}.
300+
*
297301
* @return the current contents of this output stream.
298302
* @see java.io.ByteArrayOutputStream#toByteArray()
299303
* @see #reset()
300304
* @since 2.7
301305
*/
302-
protected InputStream toInputStreamImpl() {
306+
protected <T extends InputStream> InputStream toInputStreamImpl(
307+
final InputStreamConstructor<T> isConstructor) {
303308
int remaining = count;
304309
if (remaining == 0) {
305310
return new ClosedInputStream();
306311
}
307-
final List<ByteArrayInputStream> list = new ArrayList<>(buffers.size());
312+
final List<T> list = new ArrayList<>(buffers.size());
308313
for (final byte[] buf : buffers) {
309314
final int c = Math.min(buf.length, remaining);
310-
list.add(new ByteArrayInputStream(buf, 0, c));
315+
list.add(isConstructor.construct(buf, 0, c));
311316
remaining -= c;
312317
if (remaining == 0) {
313318
break;
@@ -317,6 +322,26 @@ protected InputStream toInputStreamImpl() {
317322
return new SequenceInputStream(Collections.enumeration(list));
318323
}
319324

325+
/**
326+
* Constructor for an InputStream subclass.
327+
*
328+
* @param <T> the type of the InputStream.
329+
*/
330+
@FunctionalInterface
331+
protected interface InputStreamConstructor<T extends InputStream> {
332+
333+
/**
334+
* Construct an InputStream subclass.
335+
*
336+
* @param buf the buffer
337+
* @param offset the offset into the buffer
338+
* @param length the length of the buffer
339+
*
340+
* @return the InputStream subclass.
341+
*/
342+
T construct(final byte buf[], final int offset, final int length);
343+
}
344+
320345
/**
321346
* Gets the current contents of this byte stream as a byte array.
322347
* The result is independent of this stream.

src/main/java/org/apache/commons/io/output/ByteArrayOutputStream.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public static InputStream toBufferedInputStream(final InputStream input, final i
158158

159159
@Override
160160
public synchronized InputStream toInputStream() {
161-
return toInputStreamImpl();
161+
return toInputStreamImpl(java.io.ByteArrayInputStream::new);
162162
}
163163

164164
@Override

src/main/java/org/apache/commons/io/output/UnsynchronizedByteArrayOutputStream.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.apache.commons.io.output;
1818

19+
import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
20+
1921
import java.io.IOException;
2022
import java.io.InputStream;
2123
import java.io.OutputStream;
@@ -153,7 +155,7 @@ public static InputStream toBufferedInputStream(final InputStream input, final i
153155

154156
@Override
155157
public InputStream toInputStream() {
156-
return toInputStreamImpl();
158+
return toInputStreamImpl(UnsynchronizedByteArrayInputStream::new);
157159
}
158160

159161
@Override

0 commit comments

Comments
 (0)