12.8. Random Access Files In Java¶
12.8.1. Understanding File I/O¶
In earlier tutorials we saw how we can use Java’s scanner class to read sequential text files. Scanners are great for reading files, but we can take a step farther out and conceptualize our data even more abstractly, instead of reading in files as strings we can instead read the raw bytes and then write those bytes to the file. By doing this we have no limits on the types of data we can store. Additionally, RandomAccessFiles, allow reading and writing anywhere within the file, jumping to any legal location within the file itself. Before reading this tutorial be sure to check the RandomAccessFile API.
Before getting started using the RandomAccessFile class, be sure you understand the following concepts.
1) Reading - Read a chunk of data (bytes) at a location on disk. These bytes can be located anywhere within the file.
2) Writing - Write a chunk of data (bytes) at a location on disk. We can “write” anywhere within the file.
3) File Pointer - A number representing the byte position we are at in the file. We can manipulate the File Pointer to point anywhere within our file. A RandomAccessFile should be treated similar to an array of bytes. It’s index will start at 0 and it’s last position will be one less than the total size of the file.
One very important thing to note about the RandomAccessFile class. It is very easy to seek past the end of the file and write to that location (as the RandomAccessFile class assumes that one might wish to grow a file), however, if the file pointer is moved past the end of file and written to at that location, if the user tries to read it may encounter the EndOfFile or EOF at the old size and as such will throw an error.
12.8.1.1. Using RandomAccessFile Class¶
Consider the code example below. It will generate a file of a size 0 to 999 bytes and it will then fill those bytes in order with numbers from 65 to 90 (randomly chosen). The program will output a plaintext file randomly filled with capital English alphabet letters. Why does this work? Remember that to a computer data is nothing but a collection of bits. For ASCII text we have a set of characters whose numeric values range from 0 to 255 (or the maximum value of one byte). The capital letters in ASCII range from values 65 to 90, thus when I write a value of 65-90 to a file it will be interpreted as a letter.
import java.io.RandomAccessFile;
import java.util.Random;
public class Main {
public static void main(String[] args) {
try {
RandomAccessFile raf = new RandomAccessFile(args[0], "rw");//Open our file with read/write access
Random rdn = new Random();//Get a new random generator
int min = 0; int max = 999;//Our file will have a maximum size of 999 bytes
int amin = 0; int amax = 25;//The alphabet has 26 letters thus we want to generate a random offset
//to "pick" those letters
int length = rdn.nextInt(max - min + 1) + min;//Get the length of our file
raf.setLength((long)length);//Set the length or size of the file, note that the we do not have to
//set length of the file as the write method will "grow" the file with each sequential write
for(int i=0; i < length; i++) {//Run until our file is full
int chr = rdn.nextInt(amax - amin + 1) + amin;//Randomly get our offset
chr += 65;//ASCII capital letters range from 65-90, so add 65 to whatever we got
raf.write(chr);//Write the int to the file
System.out.printf("Writing %c at %d\n", chr, raf.getFilePointer());//Ouput our char and where
//we wrote it.
}
raf.close();//Close our filestream.
} catch (Exception e) {
e.printStackTrace();
}
}
}
So we have generated a file and filled it with bytes that are randomly created. How do we read the file? The RandomAccessFile class has a number of read functions. The following example demos a few below. The example starts by reading in the entire file (generated from above) and outputing the contents to the terminal. The program then randomly generates a new position in the file and sets our file pointer to there, outputing the position, the int value of the byte, and the character it represents. Finally the program creates a new byte array the size of the file and attempts to read that many bytes, using the read method. As the comments explain, the read method returns an int representing how many bytes were actually read. After reading in the array, the program then outputs the position read started at, the amount of bytes read and the size of the array given to read. Running this program on my machine (after generating a file from the above code), produced the following output.
EXUHIULUQKLZICYUQIKMWZCQPXKYBEHKCJVUIBCIYHKJPCPENWXJLZEMDHVLPNBXDOTNVZIUYNMQTLZVTITTVMMLDWJTMEHSUZUBTSEQPATLOQRUOODL
Set pos to 111, value 85, character U
Tried reading at position 112, read 4 bytes, array was size 116
import java.io.RandomAccessFile;
import java.util.Random;
public class Main {
public static void main(String[] args) {
try {
RandomAccessFile raf = new RandomAccessFile(args[0], "rw");//Open our file with read/write access
Random rdn = new Random();
int min = 0; int max = (int)raf.length();
int pos = rdn.nextInt(max - min + 1) + min;
byte[] file = new byte[(int)raf.length()];//create an array to hold our file
raf.readFully(file);//Read from our RAF up to the length of our array
String str = new String(file);//Convert the bytes representing our file to text
System.out.println(str);//Print out our file size
raf.seek(pos);//Set our position randomly in the file
byte b = raf.readByte();//Read the byte at our current spot
System.out.printf("Set pos to %d, value %d, character %c\n", pos, (int)b, (char)b);
file = new byte[(int)raf.length()];//Create a new array the size of the file
pos = (int)raf.getFilePointer();//Get the current position in the file
int read = raf.read(file, 0, file.length);//Use the read function. The read function requires you
//provide a byte array, the offset from the current position you want to start and the amount of
//bytes you would like. This is largely based off a c function as traditionally when you pass an
//array you have no idea what the size of the array is. In most cases you will simply want to
//provide the length of the array you are passing in. Note that this function returns an int.
//This int represents the bytes read from the file.
System.out.printf("Tried reading at position %d, read %d bytes, array was size %d\n", pos, read,
file.length);
raf.close();//Close our filestream.
} catch (Exception e) {
e.printStackTrace();
}
}
}
There you go, reading and writing using bytes!