JavaData & JavaRandom File Access Using Java

Random File Access Using Java

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Although a sequential access mechanism traverses file record in a linear fashion, random access in a file enables us to access individual records directly without searching through other records. Flat files in general are not meant to be accessed in this manner; moreover, Java does not impose any structure on a file. As a result, there is no one specific technique to create random access files. We particularly have to customize the structure of a file and lay the foundation of direct access mechanism through Java code. Of course, Java IO provides some APIs  in this regard. Here, in this article, we shall see one such way where a flat file access mechanism can be customized like a database CRUD operation.

File Structure

The simplest way to implement a file structure is to impose a restricted size of an input record. Here, our record will have a fixed length, so that we can write a chunk of data into the file and read from it in chunks as well. Because a database record generally has a key to access individual record, here too we shall impose an specific id to the record to facilitate the exact calculation of direct access. Much like a database CRUD operation, we shall operate in a manner so that inserting a new record does not destroy existing records in the file; neither do the update and delete operations require us to rewrite entire file data. (A CRUD operation can also be implemented in a sequential access file as well, but in this case we have to rewrite the complete file content on each CRUD operation to achieve the same effect. This is infeasible, especially when we can formulate a direct access mechanism through code in Java.)

Let the following POJO be our simple record structure:

public class Person {
   private int id;
   private String name;
   private String phone;
   private String email;

   public Person() {
      this(0,"","","");
   }

   public Person(int id, String name,
      String phone, String email) {
      super();
      ...
   }
   // ...getters and setters

   @Override
   public String toString() {
      return "Person [id=" + id + ", name="
         + name + ", phone=" + phone
         + ", email=" + email + "]";
   }
}

Now, to modify the preceding record structure and impose a fixed length, PersonRecord extends the POJO Person.

public class PersonRecord extends Person {

   public static final int SIZE =
      Integer.BYTES + (3 * (Character.BYTES * 15));
   public PersonRecord() {
      this(0, "", "", "");
   }
   public PersonRecord(int id, String name,
      String phone, String email) {
      super(id, name, phone, email);
   }

   public void readFromFile(RandomAccessFile file)
      throws IOException {
      setId(file.readInt());
      setName(readString(file));
      setPhone(readString(file));
      setEmail(readString(file));
   }

   public void writeToFile(RandomAccessFile file)
      throws IOException {
      file.writeInt(getId());
      writeString(file, getName());
      writeString(file, getPhone());
      writeString(file, getEmail());
   }

   private String readString(RandomAccessFile file)
      throws IOException {
      char[] s = new char[15];
      for (int i = 0; i < s.length; i++)
         s[i] = file.readChar();
      return new String(s).replace('', ' ');
   }

   private void writeString(RandomAccessFile file, String s)
      throws IOException {
      StringBuffer buffer = null;
      if (s != null)
         buffer = new StringBuffer(s);
      else
         buffer = new StringBuffer(15);
         buffer.setLength(15);
         file.writeChars(buffer.toString());
      }

}

Because there are four properties (id as int, name, phone, and email as String), we impose size constraints in bytes as follows.

public static final int SIZE = Integer.BYTES
   + (3 * (Character.BYTES * 15));

name, phone, and email cannot be more than 15 characters in length, although the id would be of 4 bytes, so the total size is:

4 bytes (int)+3 (name, phone, email)
   *2 bytes (for each character)
   *15 (total characters for each String) = 94

Editor’s Note: The preceding formula was presented as code to be more visible and, perhaps, easier to comprehend.

The methods readString and writeString impose a restriction of a fixed length character sequence whi nreading or writing string values from the file, respectively. Each record is written and read from the file with the help of the methods writeToFile and readFromFile, respectively.

Implementing Random Access

The heart of the random access file mechanism is the class called java.io.RandomAccessFile. It has all the capabilities of FileInputStream and FileOutputStream. The instance of this class not only helps in creating or opening an existing file but also provide a parameter to specify the mode of operation such as read(r) or write(w) whien opening a file. We can almost visualize a random access file as an array of bytes stored in the file system. An array location, as we know, can be directly accessed if we supply an appropriate index number.

In much the same way, records in the random access file can be directly accessed with the index number supplied through the seek method and subsequently read/write chunks of data from/to the file. Whien traversing through the records, to ascertain if we have reached the end of file, we can either compare the current position of the file pointer (by using the getFilePointer method, which returns the current offset) with the total length of the file itself or, more simply, look for the occurrence of EOFException. EOFException is thrown when the file pointer tries to access beyond the end of file mark of the file. However, if any other error occurs other than EOFException, an IOException is thrown.

Implementing a CRUD Operation

CRUD is basically a database operation. Here we implement it to simulate a database-like implementation of a flat file. Because flat files are not meant to operate like a database, here, the Java code is more of a jugglery to achieve this end. However, this fun exercise can not only enrich you conceptually but also give you an glimpse of actual database storage structure and how difficult/easy is to create one from scratch. The code is simple enough to understand. However, simplification sometime overshadows important details; so is the case here. A lot of detail validation and verification is simply ignored. Nevertheless, one can always modify the following code to achieve a greater end once the basic concept is grasped.

public class FileDatabase {
   RandomAccessFile file;

   public FileEditor(String fileString)
         throws IOException {
      file = new RandomAccessFile(fileString, "rw");
   }

   public void close() throws IOException {
      if (file != null)
         file.close();
   }

   public PersonRecord getRecord(int id) throws IOException {
      PersonRecord record = new PersonRecord();
      if (id < 1)
         throw new IllegalArgumentException("invalid ID!!");
         file.seek((id - 1) * PersonRecord.SIZE);
         record.readFromFile(file);
         return record;
   }

   public void insertRecord(PersonRecord record)
         throws IllegalArgumentException, IOException {
      if (getRecord(record.getId()).getId() != 0)
         System.out.println("Cannot add new.
            Record already exists.");
      else {
         file.seek((record.getId() - 1) * PersonRecord.SIZE);
         record.writeToFile(file);
      }
   }

   public void updateRecord(PersonRecord record)
         throws IllegalArgumentException, IOException {
      if (getRecord(record.getId()).getId() == 0)
         System.out.println("Cannot update.
            Record does not exist.");
      else {
         file.seek((record.getId() - 1) * PersonRecord.SIZE);
         record.writeToFile(file);
      }
   }

   public void deleteRecord(PersonRecord record)
         throws IllegalArgumentException, IOException {
      if (getRecord(record.getId()).getId() == 0)
         System.out.println("Cannot delete.
            Record does not exist.");
      else {
         file.seek((record.getId() - 1) * PersonRecord.SIZE);
         record = new PersonRecord();
         record.writeToFile(file);
      }
   }

   public void showAllRecords() {
      PersonRecord record = new PersonRecord();
      try {
         file.seek(0);
         while (true) {
            do {
               record.readFromFile(file);
            } while (record.getId() == 0);
            System.out.println(record.toString());
         }
      } catch (EOFException ex1) {
         return;
      } catch (IOException ex2) {
         System.err.println("error reading file");
      }
   }
}

To test, we can supply some dummy data as follows:

public class Test {

   public static void main(String[] args)
         throws IOException {

      FileEditor fe = new FileEditor
         ("/home/mano/temp/people.dat");

      fe.insertRecord(new PersonRecord(1, "Brian", "Sullivan",
         "bs@hotmail.com"));
      fe.insertRecord(new PersonRecord(2, "Randal", "Wallace",
         "rw@hotmail.com"));
      fe.insertRecord(new PersonRecord(3, "Eric", "Bloch",
         "eb@hotmail.com"));
      fe.insertRecord(new PersonRecord(4, "Kapil", "Ansari",
         "ka@hotmail.com"));
      fe.showAllRecords();
      fe.updateRecord(new PersonRecord(4,"Tony","Li",
         "kapil@somemail.com"));
      fe.showAllRecords();
      fe.deleteRecord(new PersonRecord(1,"Brian","Sullivan",
         "bs@hotmail.com"));
      fe.showAllRecords();
      fe.close();
   }

}

Conclusion

There are many ways to organize records in a file. A sequential file structure is the common way where records are stored in order by the record key field. Random file access is the superimposed mechanism implemented through Java code to access individual records directly and quickly without searching through records in the file. End of file is determined either by a marker or a count of total bytes in the file maintained by the administrative data structure of the operating system. The IO APIs of Java provide the necessary methods to access files randomly or sequentially according to our need.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories