Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8264777: Overload optimized FileInputStream::readAllBytes #3845

Closed
wants to merge 9 commits into from
@@ -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 {
bplb marked this conversation as resolved.
Show resolved Hide resolved
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,117 @@
/*
* 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("Zero-length byte[] expected");
}

try (FileInputStream fis = new FileInputStream(empty)) {
byte[] b = fis.readAllBytes();
bplb marked this conversation as resolved.
Show resolved Hide resolved
if (b.length != 0)
throw new RuntimeException("Zero-length byte[] expected");
} finally {
empty.delete();
}

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();
}
}