# HG changeset patch # User chegar # Date 1433410026 -3600 # Node ID 3d58028e8e7ff13a888b9710ebfe2852621336b2 # Parent bc7fd31687bd67f9ae38ff44d7ef7ef3e3438686 8080835: Add blocking bulk read to java.io.InputStream Reviewed-by: alanb, rriggs, prappo diff -r bc7fd31687bd -r 3d58028e8e7f jdk/src/java.base/share/classes/java/io/InputStream.java --- a/jdk/src/java.base/share/classes/java/io/InputStream.java Thu Jun 04 10:24:31 2015 +0100 +++ b/jdk/src/java.base/share/classes/java/io/InputStream.java Thu Jun 04 10:27:06 2015 +0100 @@ -25,6 +25,7 @@ package java.io; +import java.util.Arrays; import java.util.Objects; /** @@ -50,7 +51,7 @@ // use when skipping. private static final int MAX_SKIP_BUFFER_SIZE = 2048; - private static final int TRANSFER_BUFFER_SIZE = 8192; + private static final int DEFAULT_BUFFER_SIZE = 8192; /** * Reads the next byte of data from the input stream. The value byte is @@ -192,6 +193,128 @@ } /** + * The maximum size of array to allocate. + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + */ + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + + /** + * Reads all remaining bytes from the input stream. This method blocks until + * all remaining bytes have been read and end of stream is detected, or an + * exception is thrown. This method does not close the input stream. + * + *
When this stream reaches end of stream, further invocations of this + * method will return an empty byte array. + * + *
Note that this method is intended for simple cases where it is + * convenient to read all bytes into a byte array. It is not intended for + * reading input streams with large amounts of data. + * + *
The behavior for the case where the input stream is asynchronously + * closed, or the thread interrupted during the read, is highly input + * stream specific, and therefore not specified. + * + *
If an I/O error occurs reading from the input stream, then it may do + * so after some, but not all, bytes have been read. Consequently the input + * stream may not be at end of stream and may be in an inconsistent state. + * It is strongly recommended that the stream be promptly closed if an I/O + * error occurs. + * + * @return a byte array containing the bytes read from this input stream + * @throws IOException if an I/O error occurs + * @throws OutOfMemoryError if an array of the required size cannot be + * allocated. For example, if an array larger than {@code 2GB} would + * be required to store the bytes. + * + * @since 1.9 + */ + public byte[] readAllBytes() throws IOException { + byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; + int capacity = buf.length; + int nread = 0; + int n; + for (;;) { + // read to EOF which may read more or less than initial buffer size + while ((n = read(buf, nread, capacity - nread)) > 0) + nread += n; + + // if the last call to read returned -1, then we're done + if (n < 0) + break; + + // need to allocate a larger buffer + if (capacity <= MAX_BUFFER_SIZE - capacity) { + capacity = capacity << 1; + } else { + if (capacity == MAX_BUFFER_SIZE) + throw new OutOfMemoryError("Required array size too large"); + capacity = MAX_BUFFER_SIZE; + } + buf = Arrays.copyOf(buf, capacity); + } + return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); + } + + /** + * Reads the requested number of bytes from the input stream into the given + * byte array. This method blocks until {@code len} bytes of input data have + * been read, end of stream is detected, or an exception is thrown. The + * number of bytes actually read, possibly zero, is returned. This method + * does not close the input stream. + * + *
In the case where end of stream is reached before {@code len} bytes + * have been read, then the actual number of bytes read will be returned. + * When this stream reaches end of stream, further invocations of this + * method will return zero. + * + *
If {@code len} is zero, then no bytes are read and {@code 0} is + * returned; otherwise, there is an attempt to read up to {@code len} bytes. + * + *
The first byte read is stored into element {@code b[off]}, the next + * one in to {@code b[off+1]}, and so on. The number of bytes read is, at + * most, equal to {@code len}. Let k be the number of bytes actually + * read; these bytes will be stored in elements {@code b[off]} through + * {@code b[off+}k{@code -1]}, leaving elements {@code b[off+}k + * {@code ]} through {@code b[off+len-1]} unaffected. + * + *
The behavior for the case where the input stream is asynchronously + * closed, or the thread interrupted during the read, is highly input + * stream specific, and therefore not specified. + * + *
If an I/O error occurs reading from the input stream, then it may do
+ * so after some, but not all, bytes of {@code b} have been updated with
+ * data from the input stream. Consequently the input stream and {@code b}
+ * may be in an inconsistent state. It is strongly recommended that the
+ * stream be promptly closed if an I/O error occurs.
+ *
+ * @param b the byte array into which the data is read
+ * @param off the start offset in {@code b} at which the data is written
+ * @param len the maximum number of bytes to read
+ * @return the actual number of bytes read into the buffer
+ * @throws IOException if an I/O error occurs
+ * @throws NullPointerException if {@code b} is {@code null}
+ * @throws IndexOutOfBoundsException If {@code off} is negative, {@code len}
+ * is negative, or {@code len} is greater than {@code b.length - off}
+ *
+ * @since 1.9
+ */
+ public int readNBytes(byte[] b, int off, int len) throws IOException {
+ Objects.requireNonNull(b);
+ if (off < 0 || len < 0 || len > b.length - off)
+ throw new IndexOutOfBoundsException();
+ int n = 0;
+ while (n < len) {
+ int count = read(b, off + n, len - n);
+ if (count < 0)
+ break;
+ n += count;
+ }
+ return n;
+ }
+
+ /**
* Skips over and discards n
bytes of data from this input
* stream. The skip
method may, for a variety of reasons, end
* up skipping over some smaller number of bytes, possibly 0
.
@@ -396,9 +519,9 @@
public long transferTo(OutputStream out) throws IOException {
Objects.requireNonNull(out, "out");
long transferred = 0;
- byte[] buffer = new byte[TRANSFER_BUFFER_SIZE];
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int read;
- while ((read = this.read(buffer, 0, TRANSFER_BUFFER_SIZE)) >= 0) {
+ while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) {
out.write(buffer, 0, read);
transferred += read;
}
diff -r bc7fd31687bd -r 3d58028e8e7f jdk/test/java/io/InputStream/ReadAllBytes.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/InputStream/ReadAllBytes.java Thu Jun 04 10:27:06 2015 +0100
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Random;
+import jdk.testlibrary.RandomFactory;
+
+/*
+ * @test
+ * @bug 8080835
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.*
+ * @run main ReadAllBytes
+ * @summary Basic test for InputStream.readAllBytes
+ * @key randomness
+ */
+
+public class ReadAllBytes {
+
+ private static Random generator = RandomFactory.getRandom();
+
+ public static void main(String[] args) throws IOException {
+ test(new byte[]{});
+ test(new byte[]{1, 2, 3});
+ test(createRandomBytes(1024));
+ test(createRandomBytes((1 << 13) - 1));
+ test(createRandomBytes((1 << 13)));
+ test(createRandomBytes((1 << 13) + 1));
+ test(createRandomBytes((1 << 15) - 1));
+ test(createRandomBytes((1 << 15)));
+ test(createRandomBytes((1 << 15) + 1));
+ test(createRandomBytes((1 << 17) - 1));
+ test(createRandomBytes((1 << 17)));
+ test(createRandomBytes((1 << 17) + 1));
+ }
+
+ static void test(byte[] expectedBytes) throws IOException {
+ int expectedLength = expectedBytes.length;
+ WrapperInputStream in = new WrapperInputStream(new ByteArrayInputStream(expectedBytes));
+ byte[] readBytes = in.readAllBytes();
+
+ int x;
+ byte[] tmp = new byte[10];
+ check((x = in.read()) == -1,
+ "Expected end of stream from read(), got " + x);
+ check((x = in.read(tmp)) == -1,
+ "Expected end of stream from read(byte[]), got " + x);
+ check((x = in.read(tmp, 0, tmp.length)) == -1,
+ "Expected end of stream from read(byte[], int, int), got " + x);
+ check(in.readAllBytes().length == 0,
+ "Expected readAllBytes to return empty byte array");
+ check(expectedLength == readBytes.length,
+ "Expected length " + expectedLength + ", got " + readBytes.length);
+ check(Arrays.equals(expectedBytes, readBytes),
+ "Expected[" + expectedBytes + "], got:[" + readBytes + "]");
+ check(!in.isClosed(), "Stream unexpectedly closed");
+ }
+
+ static byte[] createRandomBytes(int size) {
+ byte[] bytes = new byte[size];
+ generator.nextBytes(bytes);
+ return bytes;
+ }
+
+ static void check(boolean cond, Object ... failedArgs) {
+ if (cond)
+ return;
+ StringBuilder sb = new StringBuilder();
+ for (Object o : failedArgs)
+ sb.append(o);
+ throw new RuntimeException(sb.toString());
+ }
+
+ static class WrapperInputStream extends FilterInputStream {
+ private boolean closed;
+ WrapperInputStream(InputStream in) { super(in); }
+ @Override public void close() throws IOException { closed = true; in.close(); }
+ boolean isClosed() { return closed; }
+ }
+}
diff -r bc7fd31687bd -r 3d58028e8e7f jdk/test/java/io/InputStream/ReadNBytes.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/InputStream/ReadNBytes.java Thu Jun 04 10:27:06 2015 +0100
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Random;
+import jdk.testlibrary.RandomFactory;
+
+/*
+ * @test
+ * @bug 8080835
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.*
+ * @run main ReadNBytes
+ * @summary Basic test for InputStream.readNBytes
+ * @key randomness
+ */
+
+public class ReadNBytes {
+
+ private static Random generator = RandomFactory.getRandom();
+
+ public static void main(String[] args) throws IOException {
+ test(new byte[]{1, 2, 3});
+ test(createRandomBytes(1024));
+ test(createRandomBytes((1 << 13) - 1));
+ test(createRandomBytes((1 << 13)));
+ test(createRandomBytes((1 << 13) + 1));
+ test(createRandomBytes((1 << 15) - 1));
+ test(createRandomBytes((1 << 15)));
+ test(createRandomBytes((1 << 15) + 1));
+ test(createRandomBytes((1 << 17) - 1));
+ test(createRandomBytes((1 << 17)));
+ test(createRandomBytes((1 << 17) + 1));
+ }
+
+ static void test(byte[] inputBytes) throws IOException {
+ int length = inputBytes.length;
+ WrapperInputStream in = new WrapperInputStream(new ByteArrayInputStream(inputBytes));
+ byte[] readBytes = new byte[(length / 2) + 1];
+ int nread = in.readNBytes(readBytes, 0, readBytes.length);
+
+ int x;
+ byte[] tmp;
+ check(nread == readBytes.length,
+ "Expected number of bytes read: " + readBytes.length + ", got: " + nread);
+ check(Arrays.equals((tmp = Arrays.copyOf(inputBytes, nread)), readBytes),
+ "Expected[" + tmp + "], got:[" + readBytes + "]");
+ check(!in.isClosed(), "Stream unexpectedly closed");
+
+ // Read again
+ nread = in.readNBytes(readBytes, 0, readBytes.length);
+
+ check(nread == length - readBytes.length,
+ "Expected number of bytes read: " + (length - readBytes.length) + ", got: " + nread);
+ check(Arrays.equals((tmp = Arrays.copyOfRange(inputBytes, readBytes.length, length)),
+ Arrays.copyOf(readBytes, nread)),
+ "Expected[" + tmp + "], got:[" + readBytes + "]");
+ // Expect end of stream
+ check((x = in.read()) == -1,
+ "Expected end of stream from read(), got " + x);
+ check((x = in.read(tmp)) == -1,
+ "Expected end of stream from read(byte[]), got " + x);
+ check((x = in.read(tmp, 0, tmp.length)) == -1,
+ "Expected end of stream from read(byte[], int, int), got " + x);
+ check((x = in.readNBytes(tmp, 0, tmp.length)) == 0,
+ "Expected end of stream, 0, from readNBytes(byte[], int, int), got " + x);
+ check(!in.isClosed(), "Stream unexpectedly closed");
+ }
+
+ static byte[] createRandomBytes(int size) {
+ byte[] bytes = new byte[size];
+ generator.nextBytes(bytes);
+ return bytes;
+ }
+
+ static void check(boolean cond, Object ... failedArgs) {
+ if (cond)
+ return;
+ StringBuilder sb = new StringBuilder();
+ for (Object o : failedArgs)
+ sb.append(o);
+ throw new RuntimeException(sb.toString());
+ }
+
+
+ static class WrapperInputStream extends FilterInputStream {
+ private boolean closed;
+ WrapperInputStream(InputStream in) { super(in); }
+ @Override public void close() throws IOException { closed = true; in.close(); }
+ boolean isClosed() { return closed; }
+ }
+}