10

I have a Java class, where I'm reading data in via an InputStream

    byte[] b = null;
    try {
        b = new byte[in.available()];
        in.read(b);
    } catch (IOException e) {
        e.printStackTrace();
    }

It works perfectly when I run my app from the IDE (Eclipse).

But when I export my project and it's packed in a JAR, the read command doesn't read all the data. How could I fix it?

This problem mostly occurs when the InputStream is a File (~10kb).

Thanks!

2
  • 1
    So it seemed that the OS that you run eclipse returned the total size of a file when available() was called, but this didn't happen on your test box. So don't rely on the number returned by available() as said by the java doc. Commented Dec 9, 2016 at 3:20
  • 1
    Classic misuse of available(). There is a warning in the Javadoc specifically against using it in this way. Commented Feb 15, 2017 at 5:07

4 Answers 4

9

Usually I prefer using a fixed size buffer when reading from input stream. As evilone pointed out, using available() as buffer size might not be a good idea because, say, if you are reading a remote resource, then you might not know the available bytes in advance. You can read the javadoc of InputStream to get more insight.

Here is the code snippet I usually use for reading input stream:

byte[] buffer = new byte[BUFFER_SIZE];

int bytesRead = 0;
while ((bytesRead = in.read(buffer)) >= 0){
  for (int i = 0; i < bytesRead; i++){
     //Do whatever you need with the bytes here
  }
}

The version of read() I'm using here will fill the given buffer as much as possible and return number of bytes actually read. This means there is chance that your buffer may contain trailing garbage data, so it is very important to use bytes only up to bytesRead.

Note the line (bytesRead = in.read(buffer)) >= 0, there is nothing in the InputStream spec saying that read() cannot read 0 bytes. You may need to handle the case when read() reads 0 bytes as special case depending on your case. For local file I never experienced such case; however, when reading remote resources, I actually seen read() reads 0 bytes constantly resulting the above code into an infinite loop. I solved the infinite loop problem by counting the number of times I read 0 bytes, when the counter exceed a threshold I will throw exception. You may not encounter this problem, but just keep this in mind :)

I probably will stay away from creating new byte array for each read for performance reasons.

Sign up to request clarification or add additional context in comments.

3 Comments

The Javdoc for InputStream.read() clearly states that it blocks until at least one byte is transferred or end of stream or an exception occurs. The only way it can return zero is if you supply a zero length buffer or count.
@EJP Thanks for pointing that out, I totally missed that when I posted this answer. However, it's still important to check for read(...) returning 0 because according to actual event, some framework do return implementation of InputStream that have read(...) return 0 even when given a buffer of length bigger than 0.
@EJP But I have faced reading 0 bytes (while reading a zip file content) in practical scenario... So I it is always better to check for bytesRead > 0 I guess...
7

read() will return -1 when the InputStream is depleted. There is also a version of read which takes an array, this allows you to do chunked reads. It returns the number of bytes actually read or -1 when at the end of the InputStream. Combine this with a dynamic buffer such as ByteArrayOutputStream to get the following:

InputStream in = ...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int read;
byte[] input = new byte[4096];
while ( -1 != ( read = in.read( input ) ) ) {
    buffer.write( input, 0, read );
}
input = buffer.toByteArray()

This cuts down a lot on the number of methods you have to invoke and allows the ByteArrayOutputStream to grow its internal buffer faster.

Comments

4
File file = new File("/path/to/file");

try {
   InputStream is = new FileInputStream(file);
   byte[] bytes = IOUtils.toByteArray(is);

   System.out.println("Byte array size: " + bytes.length);
} catch (IOException e) {
   e.printStackTrace();
}

3 Comments

"Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not. It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream." But, what should I use instead of Available?
You can get a file's length directly, and you should always read() in a loop (probably not byte-by-byte as this answer has) and check how many bytes were returned. read(byte[]) is not guaranteed to read as many bytes as you'd like.
AFAIK toByteArray() reads from the input stream for you. There's no need for the code after the first line.
0

Below is a snippet of code that downloads a file (*. Png, *. Jpeg, *. Gif, ...) and write it in BufferedOutputStream that represents the HttpServletResponse.

BufferedInputStream inputStream = bo.getBufferedInputStream(imageFile);
try {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    int bytesRead = 0;
    byte[] input = new byte[DefaultBufferSizeIndicator.getDefaultBufferSize()];
    while (-1 != (bytesRead = inputStream.read(input))) {
        buffer.write(input, 0, bytesRead);
    }
    input = buffer.toByteArray();

    response.reset();
    response.setBufferSize(DefaultBufferSizeIndicator.getDefaultBufferSize());
    response.setContentType(mimeType);
    // Here's the secret. Content-Length should equal the number of bytes read.
    response.setHeader("Content-Length", String.valueOf(buffer.size()));
    response.setHeader("Content-Disposition", "inline; filename=\"" + imageFile.getName() + "\"");

    BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream(), DefaultBufferSizeIndicator.getDefaultBufferSize());
    try {
        outputStream.write(input, 0, buffer.size());
    } finally {
        ImageBO.close(outputStream);
    }
} finally {
    ImageBO.close(inputStream);
}

Hope this helps.

2 Comments

Thanks for answering, the content should be posted in English and not in Portuguese. I have updated your answer to remove the Portuguese version.
The ByteArrayOutputStream is a complete waste of time and space. The input should be written directly to the output. You shouldn't have to set the conttent-length at all.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.