Sunday, June 19, 2011

RandomAccessFile Read/Write Concurrency

Prerequisite

  • Java Basics I/O

Overview

This article details some useful features of RandomAccessFile java class.

RandomAccessFile

Instances of this class support both reading and writing to a random access file. A random access file behaves like a large array of bytes stored in the file system. There is a kind of cursor, or index into the implied array, called the file pointer; input operations read bytes starting at the file pointer and advance the file pointer past the bytes read.
If the random access file is created in read/write mode, then output operations are also available; output operations write bytes starting at the file pointer and advance the file pointer past the bytes written. Output operations that write past the current end of the implied array cause the array to be extended. The file pointer can be set by the seek method.

seek operation

Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs. The offset can be set beyond the end of the file. Setting the offset beyond the end of the file does not change the file length. The file length will change only by writing after the offset has been set beyond the end of the file

Concurrent Read/Write

This is a useful but very dangerous feature. It goes like this "if you create different instances of RandomAccessFile for a same file you can concurrently write to the different parts of the file."
You can create multiple threads pointing to different parts of the file using seek method and multiple threads can update the file at the same time. Seek allow you to move to any part of the file even if it doesn't exist( after EOF ), hence you can move to any location in the newly created file and write bytes on that location. You can open multiple instances of the same file and seek to different locations and write to multiple locations at the same time. Below is a working example to copy one file to another using multiple threads. You can tune this code to perform better then windows .
Note: writing concurrently is only a performance hack. If not properly implemented this can lead to race condition and many other critical bugs. You should avoid doing this unless necessary.
package www.directi.com.junk;

import java.io.IOException;
import java.io.RandomAccessFile;

/*
 * copy file(filePath) to (targetFileName) in chunks. It divide the file in parts of chuckOfFile and copy each part concurrently
 * $author = Paras Malik(masterofmasters22@gmail.com)
 * $Date = 20 June, 2011
*/
public class CopyFile implements Runnable {

    public static String filePath = "source.mkv";  //file to copy
    public static String targetFileName = "target.mkv";
    private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 5;
    private int startingByte;
    private int endingByte;
    private static int chunkOfFile = 1024 * 1024 * 70;  //size of one chunk of file

    public CopyFile(int startingByte, int endingByte) {
        this.startingByte = startingByte;
        this.endingByte = endingByte;
    }

    public static void main(String s[]) throws IOException {
        int copyStartingByte = 0;
        int copyEndingByte = chunkOfFile;
        int fileSize = (int) getFileSize();
        while (fileSize > copyStartingByte) {
            new Thread(new CopyFile(copyStartingByte, copyEndingByte)).start();
            copyStartingByte = copyEndingByte;
            if (copyEndingByte + chunkOfFile <= fileSize)
                copyEndingByte = copyEndingByte + chunkOfFile;
            else
                copyEndingByte = fileSize;
        }
    }

    static long getFileSize() throws IOException {
        RandomAccessFile f = new RandomAccessFile(filePath, "r");
        long contentLength = f.length();
        return contentLength;
    }

    /* copy one part of the file starting from startingByte till endingByte*/
    public void copyPartOfFile(int startingByte, int endingByte) throws IOException {
        int copied = 0;
        int contentLength = endingByte - startingByte;
        RandomAccessFile sourceFile = new RandomAccessFile(filePath, "rw");
        RandomAccessFile targetFile = new RandomAccessFile(this.targetFileName, "rw");
        targetFile.seek(startingByte);
        sourceFile.seek(startingByte);
        while (true) {
            byte buffer[];
            if (contentLength - copied > MAX_BUFFER_SIZE) {
                buffer = new byte[MAX_BUFFER_SIZE];
            } else {
                buffer = new byte[contentLength - copied];
            }
            int read = sourceFile.read(buffer, 0, buffer.length);

            if (read == -1 || copied == contentLength)
                break;
            targetFile.write(buffer, 0, read);
            copied += read;
        }
        sourceFile.close();
        targetFile.close();
    }

    @Override
    public void run() {
        try {
            copyPartOfFile(startingByte, endingByte);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}