21

I am using following way to write InputStream to File:

private void writeToFile(InputStream stream) throws IOException {
    String filePath = "C:\\Test.jpg";
    FileChannel outChannel = new FileOutputStream(filePath).getChannel();       
    ReadableByteChannel inChannel = Channels.newChannel(stream);
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    
    while(true) {
        if(inChannel.read(buffer) == -1) {
            break;
        }
        
        buffer.flip();
        outChannel.write(buffer);
        buffer.clear();
    }
    
    inChannel.close();
    outChannel.close();
}

I was wondering if this is the right way to use NIO. I have read a method FileChannel.transferFrom, which takes three parameter:

  1. ReadableByteChannel src
  2. long position
  3. long count

In my case I only have src, I don't have the position and count, is there any way I can use this method to create the file?

Also for Image is there any better way to create image only from InputStream and NIO?

Any information would be very useful to me. There are similar questions here, in SO, but I cannot find any particular solution which suites my case.

Community
  • 1
  • 1
Tapas Bose
  • 28,796
  • 74
  • 215
  • 331
  • 5
    Why do it in such a complicated way? You can do the same in one line: `Files.copy(stream, new File("C:\\Test.jpg").toPath());` – Jesper May 16 '13 at 07:51

2 Answers2

52

I would use Files.copy

Files.copy(is, Paths.get(filePath));

as for your version

  1. ByteBuffer.allocateDirect is faster - Java will make a best effort to perform native I/O operations directly upon it.

  2. Closing is unreliable, if first fails second will never execute. Use try-with-resources instead, Channels are AutoCloseable too.

gustavohenke
  • 40,997
  • 14
  • 121
  • 129
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • 1
    `transferFrom()` and `transferTo()` must be called in a loop. There is no guarantee that they transfer the requested count. That's why they return a count. – user207421 May 16 '13 at 08:23
  • 1
    Use Files.copy(fileInputStream, filePath, StandardCopyOption.REPLACE_EXISTING); if file already exists. – Justinas Jakavonis Jul 27 '17 at 12:49
10

No it's not correct. You run the risk of losing data. The canonical NIO copy loop is as follows:

while (in.read(buffer) >= 0 || buffer.position() > 0)
{
  buffer.flip();
  out.write(buffer);
  buffer.compact();
}

Note the changed loop conditions, which take care of flushing the output at EOS, and the use of compact() instead of clear(), which takes care of the possibility of short writes.

Similarly the canonical transferTo()/transferFrom() loop is as follows:

long offset = 0;
long quantum = 1024*1024; // or however much you want to transfer at a time
long count;
while ((count = out.transferFrom(in, offset, quantum)) > 0)
{
    offset += count;
}

It must be called in a loop, as it isn't guaranteed to transfer the entire quantum.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • If transferFrom returns 0 it does not mean that all bytes are actually transferred. To be 100% correct we need to know in advance the expected count from InputStream and loop till we transfer them all. Do you agree? – Evgeniy Dorofeev May 16 '13 at 09:04
  • Yes I do. It's a strange thing that there is no proper EOS indication from these APIs. – user207421 May 16 '13 at 10:14