JavaData & JavaSequential File Access Using Java

Sequential 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.

When talking about permanent retention of data, a common text file is the basic unit of data storage apart from using complicated database and other repositories. Variables and arrays we use for storage in code are temporary; they are stored in the primary storage area (such as RAM) and live until the program terminates or garbage collector(*) sweeps the memory clean. Thus, the only way to persist data permanently is to store them somehow in the secondary storage devices such as hard disks, optical disks, pen drives, and so forth, and retrieve on demand accordingly. In this article, we shall try to elaborate the way to process common sequential files through Java and some aspects of file handling APIs in general.

(*)Garbage collector is a component that works as a janitor in JVM, responsible for keeping the memory clean to avoid memory leaks—to release the occupied but out of scope players (read objects and variables) from the playground for other players to pour in. This is a very automated process; the programmer need not worry about it at all.

A Peek into File Structure

In Java, files are nothing but a sequential stream of data terminated by an end of file marker. The end of file marker is denoted by a special mark or count of total bytes, recorded in a system-maintained file data structure. This mechanism is not uniform across various platforms; the Linux file structure is quite different from that of Windows. No matter what the internal file data structure is, a Java program receives a consistent end of file indication from the JVM, which in turn is informed appropriately by the underlying operating system. So, processing a stream of file data in Java is not only seamless but also is consistent across multiple platforms.

Bytes and Character Streams

This file stream can be used for both data input as well as output, to and from a file either, as a stream of characters or bytes. Byte-based streams are mainly used for storing/manipulating data in binary format, whereas a character-based stream stores/manipulates data as a sequence of characters. For example, if we want to store, say 3, it would be stored in the binary format of the numeric value 3, or 011(3 in binary) in byte based stream. If the value were to be stored in character based stream it would store the binary format of the character 3 as 00000000 00110011 (=51 is 3 from the Unicode character set; similarly, 52 id 4, 53 is 5 and so on). At first glance, it may seem fussy, but the distinction is important. The character treatment of a stream of data is used to create text files. These text files can be read by any text editor. Binary files yielded from a binary stream require a special program that converts the data to a human-readable format. Their use is specific, mainly to interact with the system and to serve special purposes. For example, all the executable files found in the system are nothing but binary files.

File Overview in Java

The class java.io.File does not provide any file processing capabilities, and yet it is useful for retrieving information about files and directories from secondary storage. This class object comes in handy on several occasions, mainly in dealing with files in Java programming. To appreciate its utility, let’s try a simple program that works like the minimal ls command in Linux. Refer to Javadoc to understand the various functions provided by the File class.

import java.io.File;
import java.util.Calendar;

public class FileDemo {
   public static void main(String[] args) throws Exception {
      fileInfo("/home/mano/Pictures/");
   }
   public static void fileInfo(String filePath) throws Exception {
      File file = new File(filePath);
      if (file.exists()) {
         if (file.isFile())
            properties(file);
         if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
               properties(f);
            }
         }

      } else {
         System.out.printf(
            "%s: ERROR: cannot open '%s' (No such file or directory)",
            filePath, filePath);
      }
   }

   private static void properties(File f) {
      Calendar c = Calendar.getInstance();
      c.setTimeInMillis(f.lastModified());
      String s = String.format("%1$tb %1$2te %1$tY %1$2tl:%1$2tM %1$Tp", c);
      System.out.printf("n%s%s%s%s%st%8dt%st%s", f.isDirectory() ? "d"
      : "-", f.canRead() ? "r" : "-", f.canWrite() ? "w" : "-", f
      .canExecute() ? "x" : "-", f.isHidden() ? "h" : "-",
      f.length(), s, f.getName());
   }
}

Output:

File1
Figure 1: Output of preceding code

Sequential Access Text File

To keep things simple, we’ll work with text files and how to read and write streams of data into them. Because Java does not impose any structure while manipulating sequential access to a file, it is the responsibility of the programmer to establish a structure, meaning, how the data will be stored in the file. Let us create a class to establish a record structure. This record structure will be written into and read from, as a character-based stream. The class ProductRecord encapsulates product information that helps in and out flow of a stream of data from a file in a structured manner.

public class ProductRecord {
   private int productCode;
   private String composition;
   private String productName;
   private String company;
   private int stockQuantity;
   private int reorderLevel;
   private float unitPrice;

   public ProductRecord() {
      super();
   }

   public ProductRecord(int productCode,
         String composition, String productName,
         String company, int stockQuantity,
         int reorderLevel, float unitPrice) {
      super();
      this.productCode = productCode;
      //...
   }

   //...getters and setters
}

Once the record structure is created for the file, let’s create two methods: one to write the list of records to the file and another to read from it. The read method returns a list of records retrieved from the file for further processing.

public class ProductFileStream {

   public void writeToFile(List<ProductRecord> list, String file) {
      Formatter outFile = null;
      try {
         outFile = new Formatter(file);
         for (ProductRecord p : list) {
            outFile.format("%d %s %s %s %d %d %fn", p.getProductCode(),
            p.getComposition(), p.getProductName(), p.getCompany(),
            p.getStockQuantity(), p.getReorderLevel(),
            p.getUnitPrice());
         }

      } catch (FileNotFoundException fileNotFoundException) {
         System.err.println("Error creating file.");
      } catch (FormatterClosedException formatterClosedException) {
         System.err.println("Error writing to file.");
      } finally {
         if (outFile != null)
            outFile.close();
      }
   }

   public List<ProductRecord> readFromFile(String file) {
      List<ProductRecord> list = new ArrayList<>();
      Scanner inFile = null;
      try {
         inFile = new Scanner(new File(file));
         while (inFile.hasNext()) {
            ProductRecord p = new ProductRecord();
            p.setProductCode(inFile.nextInt());
            p.setComposition(inFile.next());
            p.setProductName(inFile.next());
            p.setCompany(inFile.next());
            p.setStockQuantity(inFile.nextInt());
            p.setReorderLevel(inFile.nextInt());
            p.setUnitPrice(inFile.nextFloat());
            list.add(p);
         }
      } catch (FileNotFoundException fileNotFoundException) {
         System.err.println("Error opening file.");
         fileNotFoundException.printStackTrace();
      } catch (NoSuchElementException noSuchElementException) {
         System.err.println("Error in file record structure");
         noSuchElementException.printStackTrace();
      } catch (IllegalStateException illegalStateException) {
         System.err.println("Error reading from file.");
         illegalStateException.printStackTrace();
      } finally {
         if (inFile != null)
            inFile.close();
      }
      return list;
   }
}

This is the test class where we feed some dummy records into the file and print them into the console upon retrieving it in a list.

public class ProductFileTest {

   public static void main(String[] args) {
      String filePath = "/home/mano/temp/product.txt";
      List<ProductRecord> list = new ArrayList<>();
      list.add(new ProductRecord(111, "Amitryptyline",
         "Amiline", "Torrent", 10, 5, 13.35f));
      //...dummy data

      ProductFileStream pf=new ProductFileStream();
      pf.writeToFile(list, filePath);
      List<ProductRecord> products=pf.readFromFile(filePath);
      printlist(products);

   }

   public static void printlist(List<ProductRecord> list) {
      System.out.printf("%-7s%-20s%-20s%-20s%-10s%-10s%-10sn", "Code",
      "Generic", "Name", "Company", "Stock", "Reorder", "Unit Price");
      for (ProductRecord p : list) {
         System.out.printf("%-7d%-20s%-20s%-20s%-10d%-10d%.2fn",
         p.getProductCode(), p.getComposition(), p.getProductName(),
         p.getCompany(), p.getStockQuantity(), p.getReorderLevel(),
         p.getUnitPrice());
      }
   }
}

Update Problems of a Sequential File

The problem of data in a sequential file is that they cannot be modified without the risk of destroying the existing record in the file. For example, if we want to modify a old name of 10 characters with a new name of say, 20 characters, the next field in the record will be overwritten because the new name requires more space. Therefore, the only way to update a sequential file is by rewriting the entire file. This obviously is a costly affair; imagine, to update a single record whole file has to be rewritten; however, if a substantial portion of the records needs to be updated, this may be all right. Nonetheless, a sequential file has its own uses on some occasion where no or less update or modification is required, such as persisting some property information of an application or some configuration data, and so on.

Conclusion

A sequential file structure can be used as a mechanism to persist records in a simple manner. The greatest advantage of using such a file is not in its efficiency but in the simplicity. They are useful on many occasiona in real life; in fact, whether writing a desktop application or an enterprise, simple text files always come in handy, especially for persisting some configuration properties. Further, they are excellent in learning record persistence in secondary storage in general. This notion further will be useful in building up the concept of database storage mechanism and its manipulation from Java.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories