Search code examples
javafile-iofilechannel

Java: Writing with FileChannel to a file make the file shrink?


I tried to write specific bytes to certain position of a file using FileChannel. But actually the file shrink to the last position where I write change. I do it like this:

    Path path = Paths.get("I://music - Copy.mp3");

    System.out.println(Files.size(path)/1024 + "KB");

    try (FileChannel chan = new FileOutputStream(path.toFile()).getChannel()) {
        chan.position(1024 * 1024);

        ByteBuffer b = ByteBuffer.allocate(1024);
        chan.write(b);

        System.out.println("Write 1KB of data");
    }

    System.out.println(Files.size(path)/1024 + "KB");

and this is the output I get:

3670KB
Write 1KB of data
1025KB

Can anybody tell me where it goes wrong??


Solution

  • FileOutputStream truncates the file to zero length when not in append mode. It does not overwrite a file's contents so much as it throws away the contents and starts over. You can verify this by calling chan.size() after creating the channel, which will give you 0. [1]

    FileChannels can be advanced past the end of a file and told to write there; this causes the file size to increase to position + bytes_written (emphasis mine):

    Setting the position to a value that is greater than the current size is legal but does not change the size of the entity. [..] A later attempt to write bytes at such a position will cause the entity to grow to accommodate the new bytes; the values of any bytes between the previous end-of-file and the newly-written bytes are unspecified.

    So while it looks like the FileChannel is cutting off your file after writing, it is the FileOutputStream truncating to 0 length, and then the FileChannel expanding it again.

    To prevent this from happening, avoid using a FileOutputStream to create a channel. You have a Path, so you can call Files.newByteChannel or FileChannel.open :

    Path path = Paths.get("I://music - Copy.mp3");
    
    System.out.println(Files.size(path)/1024 + "KB");
    
    // pick either:
    try (FileChannel chan = FileChannel.open(path, StandardOpenOption.WRITE)) {
    try (SeekableByteChannel chan = Files.newByteChannel(path, StandardOpenOption.WRITE)) {
        chan.position(1024 * 1024);
    
        ByteBuffer b = ByteBuffer.allocate(1024);
        chan.write(b);
    
        System.out.println("Write 1KB of data");
    }
    
    System.out.println(Files.size(path)/1024 + "KB");
    

    [1] Note that programs outside the JVM, such as a file explorer, may not indicate this until you flush or close the stream.