Reading/Writing to/from Files using FileChannel and ByteBuffer in Java

In the past, I have talked about RandomAccessFile and how it can be used for doing faster IO in Java, and  in this Java NIO tutorial, we are going to see how to use read/write data from using FileChannel and ByteBuffer. Channel provides an alternate way to read data from a file, it provides better performance than InputStream or OutputStream. It can also be opened in blocking and non-blocking mode. Though FileChannles are read/write channels and they are always blocking, they cannot be put into non-blocking mode. The RandomAccessFile class treats a file as an array of Bytes. You can write your data in any position of the Array and you can read from any position. To do that, it uses a pointer that holds the current position and provides several methods like seek() to move that pointer.

Once you are in the right position, you can get the FileChannel from RandomAccessFile and starting reading data from a file. By the way, JDK 7 also introduced NIO 2, which makes dealing with files and directory even easier. Read Pro Java 7 NIO.2 by Anghel Leonard to learn more about.



How to read/write files using FileChannel and ByteBuffer

Before start coding, let's revise the basic concept of Channel and Buffer in Java NIO. In one word, buffers work with the channel. Channels are the tube through which data is transferred and buffers are the source and target of those data transfer. In the case of a write, data you want to write is placed in a buffer, which is passed to a channel than channel read that data from the buffer and write into the file.

Similarly in case of a read, a channel puts data in a buffer you provide a file, network or any other source. Since the same buffer is used for reading and writing i.e. you write data into the buffer but channel reads it for writing into the file, you must call the flip() method once you are done writing into the buffer. The flip() method changes the pointers and allows you to read data from the buffer. There are three types of the buffer in Java, direct, non-direct and mapped buffer. We will use the direct byte buffer in this example.



Steps to read/write data using FileChannel and Buffer

Here is the step by step guide to starting reading data from a file using RandomAccessFile, FileChannel, and ByteBuffer:
  1. Open the file you want to read/write using RandomAccessFile in read/write mode.
  2. Call the getChannel() method of RandomAccessFile to get the FileChannel. The position of the returned channel will always be equal to this object's file-pointer offset as returned by the getFilePointer() method.
  3. Create a ByteBuffer using ByteBuffer.allocate() method.
  4. Store the data into ByteBuffer using various put() method e.g. putInt(), putLong().
  5. Flip the Buffer so that Channel can read data from the buffer and write into a file. The flip() method changes the pointers and allows you to read data from the buffer. 
  6. Call the write() method of FileChannel.
  7. Close the FileChannel
  8. Close the RandomAccessFile.


Another important point to note is that you can use the same buffer for reading and writing, but you need to flip it. Now, let's see a sample Java program to read/write data from files using FileChannel and ByteBuffer in Java. After Memory Mapped File, this is the second fastest way to read and write from a file in Java.




Java Program to read/writes from file using FileChannel and ByteBuffer
Here is sample program to demonstrate how you can read and write data from a file (can be binary or text file) using FileChannel and ByteBuffer class. I have also used abstraction to create an interface called Persistable, which provides two methods persist() and recover(). Any object which implement this interface can be saved and loaded, but how do you save and load them is left to the implementor i.e. you can use Chanel and Buffer like we have done or you can use the old approach to read/write file in Java.

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Java Program to read and write on RandomAccessFile in Java
 * using FileChannle and ByteBuffer.
 *
 * @author Javin
 */
public class FileChannelDemo {

    public static void main(String args[]) {

        Tablet ipad = new Tablet("Apple", true, 1000);
        System.out.println("Writing into RandomAcessFile : " + ipad);
        write("tablet.store", ipad);

        Tablet fromStore = new Tablet();
        read("tablet.store", fromStore);
        System.out.println("Object read from RandomAcessFile : " + fromStore);

    }

    /*
     * Method to write data into File using FileChannel and ByteBuffeer
     */
    public static void write(String filename, Persistable object) {
        try {
            // Creating RandomAccessFile for writing
            RandomAccessFile store = new RandomAccessFile("tablet", "rw");

            // getting FileChannel from file
            FileChannel channel = store.getChannel();

            // creating and initializing ByteBuffer for reading/writing data
            ByteBuffer buffer = ByteBuffer.allocate(2048);

            // an instance of Persistable writing into ByteBuffer
            object.persist(buffer);

            // flip the buffer for writing into file
            buffer.flip();
            int numOfBytesWritten = channel.write(buffer); // writing into File
            System.out.println("number of bytes written : " + numOfBytesWritten);
            channel.close(); // closing file channel
            store.close(); // closing RandomAccess file

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /*
     * Method to read data from File using FileChannel and ByteBuffeer
     */
    public static void read(String filename, Persistable object) {
        try {
            // Opening RandomAccessFile for reading data
            RandomAccessFile store = new RandomAccessFile("tablet", "rw");

            // getting file channel
            FileChannel channel = store.getChannel();

            // preparing buffer to read data from file
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // reading data from file channel into buffer
            int numOfBytesRead = channel.read(buffer);
            System.out.println("number of bytes read : " + numOfBytesRead);

            // You need to filp the byte buffer before reading
            buffer.flip();

            // Recovering object
            object.recover(buffer);

            channel.close();
            store.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


Our interface to abstract reading and writing mechanism. This is also the actual use of the interface, provide abstraction, separating what to do from how to do. Like this interface just say persist and recover, doesn't say how you do that.

interface Persistable {

    public void persist(ByteBuffer buffer);

    public void recover(ByteBuffer buffer);
}


Concrete class to implement Persistable to make them readable and writable

class Tablet implements Persistable {

    private String brand;
    private boolean isCellular;
    private long cost; // in US Dollars

    public Tablet() {
        brand = "";
    }

    public Tablet(String brand, boolean isCellular, long cost) {
        this.brand = brand;
        this.isCellular = isCellular;
        this.cost = cost;
    }

    public final String getBrand() {
        return brand;
    }

    public final boolean isCellular() {
        return isCellular;
    }

    public final long getCost() {
        return cost;
    }

    public final void setBrand(String brand) {
        this.brand = brand;
    }

    public final void setCellular(boolean isCellular) {
        this.isCellular = isCellular;
    }

    public final void setCost(long cost) {
        this.cost = cost;
    }

    @Override
    public void persist(ByteBuffer buffer) {
        byte[] strBytes = brand.getBytes();
        buffer.putInt(strBytes.length);
        buffer.put(strBytes, 0, strBytes.length);
        buffer.put(isCellular == true ? (byte) 1 : (byte) 0);
        buffer.putLong(cost);
    }

    @Override
    public void recover(ByteBuffer buffer) {
        int size = buffer.getInt();
        byte[] rawBytes = new byte[size];
        buffer.get(rawBytes, 0, size);
        this.brand = new String(rawBytes);
        this.isCellular = buffer.get() == 1 ? true : false;
        this.cost = buffer.getLong();
    }

    @Override
    public String toString() {
        return "Tablet [brand=" + brand + ", isCellular=" + isCellular + ", cost=" + cost + "]";
    }

}

 
 
Output:
Writing into RandomAcessFile : Tablet [brand=Apple, isCellular=true, cost=1000]
number of bytes written : 18
number of bytes read : 1024
Object read from RandomAcessFile : Tablet [brand=Apple, isCellular=true, cost=1000]


Caution
Don't forget to flip the byte buffer after writing contents of the object into it, because file channel needs to read it in order to write data into RandomAccessFile. If you forget to call the flip() method before calling the FileChannel.write() then you end up writing nothing into the file.

Similarly, after reading data from the file into the buffer, flip it again so that you can read data from a buffer to popular contents of an object. Many Java programmer does this mistake of not flipping after writing and end up debugging hours because either nothing is written to file or nothing they can read from a file.

That's all about how to read/write a File using FileChannel and ByteBuffer in Java. In this demon, I have shown you how to read and write a RandomAccessFile using FileChannel and ByteBuffer, but you can apply the same technique to read any other text or binary file from Java program.


Further Reading
How to read the file in one line in Java 8? (solution)
How to read file line by line in Java using BufferedReader and Scanner? (answer)
How to read/write Excel file in Java? (program)
How to read CSV file in Java? (program)
How to append data into existing file in Java? (answer)
Pro Java 7 NIO.2 by Anghel Leonard (read here)

No comments :

Post a Comment