Skip to content
Permalink
Browse files
8264777: Overload optimized FileInputStream::readAllBytes
Reviewed-by: dfuchs, alanb
  • Loading branch information
Brian Burkhalter committed May 17, 2021
1 parent 3b11d81 commit da4dfde71a176d2b8401782178e854d4c924eba1
Showing 4 changed files with 243 additions and 6 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1994, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1994, 2021, 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
@@ -26,9 +26,10 @@
package java.io;

import java.nio.channels.FileChannel;
import java.util.Arrays;
import jdk.internal.util.ArraysSupport;
import sun.nio.ch.FileChannelImpl;


/**
* A {@code FileInputStream} obtains input bytes
* from a file in a file system. What files
@@ -62,6 +63,8 @@
*/
public class FileInputStream extends InputStream
{
private static final int DEFAULT_BUFFER_SIZE = 8192;

/* File Descriptor - handle to the open file */
private final FileDescriptor fd;

@@ -271,6 +274,94 @@ public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}

public byte[] readAllBytes() throws IOException {
long length = length();
long position = position();
long size = length - position;

if (length <= 0 || size <= 0)
return super.readAllBytes();

if (size > (long) Integer.MAX_VALUE) {
String msg =
String.format("Required array size too large for %s: %d = %d - %d",
path, size, length, position);
throw new OutOfMemoryError(msg);
}

int capacity = (int)size;
byte[] buf = new byte[capacity];

int nread = 0;
int n;
for (;;) {
// read to EOF which may read more or less than initial size, e.g.,
// file is truncated while we are reading
while ((n = read(buf, nread, capacity - nread)) > 0)
nread += n;

// if last call to read() returned -1, we are done; otherwise,
// try to read one more byte and if that fails we're done too
if (n < 0 || (n = read()) < 0)
break;

// one more byte was read; need to allocate a larger buffer
capacity = Math.max(ArraysSupport.newLength(capacity,
1, // min growth
capacity), // pref growth
DEFAULT_BUFFER_SIZE);
buf = Arrays.copyOf(buf, capacity);
buf[nread++] = (byte)n;
}
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}

public byte[] readNBytes(int len) throws IOException {
if (len < 0)
throw new IllegalArgumentException("len < 0");
if (len == 0)
return new byte[0];

long length = length();
long position = position();
long size = length - position;

if (length <= 0 || size <= 0)
return super.readNBytes(len);

int capacity = (int)Math.min(len, size);
byte[] buf = new byte[capacity];

int remaining = capacity;
int nread = 0;
int n;
do {
n = read(buf, nread, remaining);
if (n > 0 ) {
nread += n;
remaining -= n;
} else if (n == 0) {
// Block until a byte is read or EOF is detected
byte b = (byte)read();
if (b == -1 )
break;
buf[nread++] = b;
remaining--;
}
} while (n >= 0 && remaining > 0);
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}

private long length() throws IOException {
return length0();
}
private native long length0() throws IOException;

private long position() throws IOException {
return position0();
}
private native long position0() throws IOException;

/**
* Skips over and discards {@code n} bytes of data from the
* input stream.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1994, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1994, 2021, 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
@@ -348,7 +348,7 @@ public byte[] readAllBytes() throws IOException {

/**
* Reads up to a specified number of bytes from the input stream. This
* method blocks until the requested number of bytes have been read, end
* method blocks until the requested number of bytes has been read, end
* of stream is detected, or an exception is thrown. This method does not
* close the input stream.
*
@@ -560,7 +560,7 @@ public long skip(long n) throws IOException {
* If {@code n} is negative, then no bytes are skipped.
* Subclasses may handle the negative value differently.
*
* <p> This method blocks until the requested number of bytes have been
* <p> This method blocks until the requested number of bytes has been
* skipped, end of file is reached, or an exception is thrown.
*
* <p> If end of stream is reached before the stream is at the desired
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2021, 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
@@ -72,6 +72,39 @@ Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
return readBytes(env, this, bytes, off, len, fis_fd);
}

JNIEXPORT jlong JNICALL
Java_java_io_FileInputStream_length0(JNIEnv *env, jobject this) {

FD fd;
jlong length = jlong_zero;

fd = getFD(env, this, fis_fd);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return -1;
}
if ((length = IO_GetLength(fd)) == -1) {
JNU_ThrowIOExceptionWithLastError(env, "GetLength failed");
}
return length;
}

JNIEXPORT jlong JNICALL
Java_java_io_FileInputStream_position0(JNIEnv *env, jobject this) {
FD fd;
jlong ret;

fd = getFD(env, this, fis_fd);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
return -1;
}
if ((ret = IO_Lseek(fd, 0L, SEEK_CUR)) == -1) {
JNU_ThrowIOExceptionWithLastError(env, "Seek failed");
}
return ret;
}

JNIEXPORT jlong JNICALL
Java_java_io_FileInputStream_skip0(JNIEnv *env, jobject this, jlong toSkip) {
jlong cur = jlong_zero;
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2021, 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.
*/

/*
* @test
* @library /test/lib
* @build jdk.test.lib.RandomFactory
* @run main ReadXBytes
* @bug 8264777
* @summary Test read{All,N}Bytes overrides (use -Dseed=X to set PRNG seed)
* @key randomness
*/

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Random;
import jdk.test.lib.RandomFactory;

public class ReadXBytes {
private static final int ITERATIONS = 10;
private static final int MAX_FILE_SIZE = 1_000_000;
private static final Random RND = RandomFactory.getRandom();

public static void main(String args[]) throws IOException {
File dir = new File(System.getProperty("test.src", "."));
dir.deleteOnExit();

File empty = File.createTempFile("foo", "bar", dir);
empty.deleteOnExit();
try (FileInputStream fis = new FileInputStream(empty)) {
try {
fis.readNBytes(-1);
throw new RuntimeException("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
}
byte[] nbytes = fis.readNBytes(0);
if (nbytes.length != 0)
throw new RuntimeException("readNBytes() zero length for empty");

byte[] b = fis.readNBytes(1);
if (b.length != 0)
throw new RuntimeException("readNBytes: zero-length byte[] expected");

b = fis.readAllBytes();
if (b.length != 0)
throw new RuntimeException("readAllBytes: zero-length byte[] expected");
}

for (int i = 0; i < ITERATIONS; i++) {
File file = File.createTempFile("foo", "bar", dir);
file.deleteOnExit();

int size = 1 + RND.nextInt(MAX_FILE_SIZE);
System.out.printf("size %d%n", size);
byte[] bytes = new byte[size];
RND.nextBytes(bytes);
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.write(bytes);
}

try (FileInputStream fis = new FileInputStream(file)) {
int pos = RND.nextInt(size);
int len = RND.nextInt(size - pos);
fis.getChannel().position(pos);
byte[] nbytes = fis.readNBytes(0);
if (nbytes.length != 0)
throw new RuntimeException("readNBytes() zero length");
nbytes = fis.readNBytes(len);
if (nbytes.length != len)
throw new RuntimeException("readNBytes() length");
if (!Arrays.equals(nbytes, 0, len, bytes, pos, pos + len))
throw new RuntimeException("readNBytes() content");
}

try (FileInputStream fis = new FileInputStream(file)) {
int pos = RND.nextInt(size);
fis.getChannel().position(pos);
byte[] allbytes = fis.readAllBytes();
if (allbytes.length != size - pos)
throw new RuntimeException("readAllBytes() length");
if (!Arrays.equals(allbytes, 0, allbytes.length,
bytes, pos, pos + allbytes.length))
throw new RuntimeException("readAllBytes() content");
}

file.delete();
}
dir.delete();
}
}

1 comment on commit da4dfde

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on da4dfde May 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.