File I/O

The FileIO Application

Download the source archive
FileIO.zip
Install it as a Java Application with Existing Sources. See the Using NetBeans document for details.

Like others, the FileIO project has multiple packages with multiple Main Classes intended to illustrate various independent features. The simplest way to run the various classes is to right-click the file and select Run File.

Files and Paths

A text file, like any other file, is just a sequence of bytes on some device. The nature of text files makes them always read or written sequentially, and one only ever operates in read mode or write mode. This type of access is very different from so-called random access, where bytes can be accessed at any point in the file, possibly read and then written.

The Java File I/O API changed significantly from version 1.6 to 1.7. The relevant pre-1.7 classes for accessing text-based files are Java 1.7 provides these extra "convenience" classes:

File, Path and Paths classes

The File, Path and Paths classes manage information information about a file, not the file contents. You can delete a File object, but this is still not working with the content. The older APIs use File objects exclusively while newer ones reference Path objects. Java makes it easy to go between File and Path with these conversions:
File file = new File( ... );
Path path = file.toPath();
or
Path path = Paths.get( ... );
File file = path.toFile();
The Paths class has only one member function:
Paths.get( ... );
Its utility has to do with easily constructing paths through subdirectories in a system-independent way.

As indicated in the file/path generation, file objects are typically created from file names used within the constructor. Path objects use names as well, but primarily in through the Paths helper class.

Demo Programs

The FilePathInfo illustrates how to get information about a file via the File and Path objects which represent the named text file.

fileio.FilePathInfo
package fileio;
 
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
 
public class FilePathInfo {
 
  public static void main(String[] args) {
    String[] filenames = {
      "tell-tale-heart.txt",
      "non-existent.txt"
    };
 
    for (String filename : filenames) {
      File file = new File(filename);
      System.out.println("file = " + file);
      System.out.println("file.exists() = " + file.exists());
      System.out.println("file.getAbsolutePath() = " + file.getAbsolutePath());
      System.out.println("file.length() = " + file.length());
      System.out.println();
    }
 
    System.out.println("------------------------------");
    System.out.println();
 
    for (String filename : filenames) {
      Path path = Paths.get(filename);
      System.out.println("path = " + path);
      System.out.println("path.toAbsolutePath() = " + path.toAbsolutePath());
      System.out.print("path.toRealPath() = ");
 
      try {
        System.out.println(path.toRealPath());
      }
      catch (IOException ex) {
        System.out.println("NON-EXISTENT");
      }
      System.out.println();
    }
  }
}
The one member function which stands out is the toRealPath function. All the File member functions give information about a file whether it exists or not. In contrast, toRealPath only generates information if the file actually exists.

The ComplexPath program illustrates the construction of a path to a file name 2 directory levels deep. It shows how to create this with Paths (simpler) or a File construction. Uncomment the commented section to effect the latter version.

The issue here is that the file separator character is OS-dependent:
Windows: \  (as a Java String: "\\")
Other:   /
The File.separator expression solves the problem, but generating the outcome using Paths.get is clearly the winner.

You should "mess with" this program by making minor alterations in the names:
topdir
nextdir
sample.txt

fileio.ComplexPath
package fileio;
 
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
 
public class ComplexPath {
 
  public static void main(String[] args) {
    Path path;
    File file;
 
    path = Paths.get("topdir", "nextdir", "sample.txt");
    file = path.toFile();
 
    // or
 
//    file = new File(
//        "topdir" + File.separator + "nextdir" + File.separator + "sample.txt"
//    );
//    path = file.toPath();
 
    System.out.printf("file = %s, path = %s\n", file, path);
    System.out.println("file exists: " + file.exists());
    System.out.println("file.getAbsolutePath() = " + file.getAbsolutePath());
    System.out.print("path.toRealPath() = ");
    try {
      System.out.println(path.toRealPath());
    }
    catch (IOException ex) {
      System.out.println("NON-EXISTENT");
    }
  }
 
}
The OtherPaths program illustrates ways to obtain paths to other locations on your system.

fileio.OtherPaths
package fileio;
 
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
 
public class OtherPaths {
 
  public static void main(String[] args) {
 
    // list all root directories
    System.out.println("------ root paths -----------");
    for (Path p: FileSystems.getDefault().getRootDirectories()) {
      System.out.println(p);
    }
 
    Path rootpath = 
       FileSystems.getDefault().getRootDirectories().iterator().next();
 
    System.out.println("rootpath = " + rootpath);
 
    System.out.println("------ more paths -----------");
 
    String homedir = System.getProperty("user.home");
    String workdir = System.getProperty("user.dir");
 
    Path[] morepaths = {
      Paths.get(URI.create("file:///")),
      Paths.get(URI.create("file:///c:/")),
      Paths.get(homedir),
      Paths.get(homedir, "Documents"),
      Paths.get(workdir),
    };
 
    for(Path path: morepaths) {
      System.out.println("path = " + path);
      try {
        System.out.print("  realpath = ");
        System.out.println(path.toRealPath());
      }
      catch (Exception ex) {
        System.out.println("NON-EXISTENT");
      }
    }
 
  }
 
}
We have made use of the user.dir property before. Here is the list of available system properties:
Java System Properties

Read/Write

The CopyFile program illustrates read/write of a file form the top level to a target 2 subdirectories deep. Alter the names used slightly to see the effects:
tell-tale-heart.txt
topdir
nextdir
output.txt
We have already dealt with the IO exceptions which can occur, but this can illustrate an exception on the write aspect as well as the read.

fileio.CopyFile
package fileio;
 
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;
 
public class CopyFile {
 
  public static void main(String[] args) {
    try {
      Scanner keyboard = new Scanner(System.in);
 
      File inFile = new File("tell-tale-heart.txt");
 
      Path inPath = inFile.toPath();
 
      Path outPath = Paths.get("topdir", "nextdir", "output.txt");
      //Path outPath = Paths.get(URI.create("file:///output.txt"));
 
      System.out.printf("attempt copy %s to %s\n", inPath, outPath);
 
      Files.deleteIfExists(outPath);
 
      String content = new String(Files.readAllBytes(inPath));
 
      System.out.print("read complete, enter to write ==> ");
      keyboard.nextLine();
 
      Files.write(outPath, content.getBytes());
    }
    catch (IOException ex) {
      System.err.println("---- FAILED ----");
      System.err.println("message:   " + ex.getMessage());
      System.err.println("exception: " + ex.getClass());
    }
  }
}

The Files and Stream classes

We want to examine the file read and write operations more closely. As indicated in the last section, the File and Path classes are about getting file information. It is the class
java.nio.file.Files
which actually deals with the file content and what generates an exception if something goes wrong. The Files operations use a Path object obtained one way or another:
Path path = someFile.toPath();
// or
Path path = Paths.get( ... );
The read operation, expanded is:
byte[] byte_content = Files.readAllBytes(path);
String content = new String(byte_content);
Condensed we see it simply as:
String content = new String(Files.readAllBytes(path));
Likewise, we can expand the write operation like this:
String content = /* ... */;
Path path = /* ... */;
byte[] byte_content = content.getBytes();
Files.write(path, byte_content);

FileReader/FileWriter

You can achieve the same effect of reading all bytes and then writing them at once using the classes:

fileio.ReaderWriterCopy
package fileio;
 
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
 
public class ReaderWriterCopy {
 
  public static void main(String[] args) {
 
    try {
      Scanner keyboard = new Scanner(System.in);
 
      File inFile = new File("tell-tale-heart.txt");
      File outFile = new File("output.txt");
 
      outFile.delete(); // delete the output file
 
      FileReader reader = new FileReader(inFile);
 
      char[] chars = new char[(int)inFile.length()];
      reader.read(chars);
      reader.close();
 
      String content = new String(chars);
 
      System.out.print("read complete, enter to write ==> ");
      keyboard.nextLine();
 
      FileWriter writer = new FileWriter(outFile);
 
      writer.write(content);
      writer.close();         // ensure completion
    }
    catch (IOException ex) {
      System.err.println("---- FAILED ----");
      System.err.println("message:   " + ex.getMessage());
      System.err.println("exception: " + ex.getClass());
    }
  }
}

Stream Copy

The StreamCopy program illustrates reading and writing a file through the Stream classes.

fileio.StreamCopy
package fileio;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
 
public class StreamCopy {
 
  public static void main(String[] args) {
 
    try {
      Scanner keyboard = new Scanner(System.in);
 
      File inFile = new File("tell-tale-heart.txt");
      File outFile = new File("output.txt");
 
      outFile.delete(); // delete the output file
 
      FileInputStream istr = new FileInputStream(inFile);
 
      byte[] data = new byte[(int) inFile.length()]; 
      istr.read(data);
      istr.close();
 
      String content = new String(data);
 
      System.out.print("read complete, enter to write ==> ");
      keyboard.nextLine();
 
      FileOutputStream ostr = new FileOutputStream(outFile);
 
      ostr.write(content.getBytes());
      ostr.close();        // ensure completion
    }
    catch (IOException ex) {
      System.err.println("---- FAILED ----");
      System.err.println("message:   " + ex.getMessage());
      System.err.println("exception: " + ex.getClass());
    }
  }
}

Closing Streams

A stream represents a live connection to some memory device and so we are obliged to close this connection upon completion of the operation. It is usually more urgent to close an ouput stream than an input, read-only stream. In the previous program, two streams are involved, istr and ostr. The one we really need to close is:
ostr.close();

Read a Text file Line-by-line

Java provides several ways to read line-by-line from a text file. Here are 3 short demo program offering different ways to read the lines of "tell-tale-heart.txt" and printing those which contain the word "very".
  1. The FilesRead program uses the Files class and a slick "one-liner" to read all lines into a List<String>. You have (hopefully) already seen ArrayList<String>; the List is just a bit more general.
  2. The StreamRead program shows the BufferedReader class used to promote (i.e., "wrap around") an InputStreamReader object. With this promotion, we are able to read line-by-line with the call:
    String a_line = brdr.readLine()
    Getting a null value means we've reached the end.
  3. The ScannerRead shows the Scanner class defined on a file (or path) and uses the nextLine method to get the lines.

readlines.FilesRead
package readlines;
 
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
 
public class FilesRead {
 
  public static void main(String[] args) throws IOException {
 
    File file = new File("tell-tale-heart.txt");
    Path path = file.toPath();
    List<String> lines = Files.readAllLines(path);
 
    int num = 0;
    for(String line: lines) {
      ++num;
      if (line.toLowerCase().matches(".*\\bvery\\b.*")) {
        System.out.format("%4d: %s\n", num, line);
      }
    }
  }
}

readlines.StreamRead
package readlines;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
 
public class StreamRead {
 
  public static void main(String[] args) throws IOException {
    File file = new File("tell-tale-heart.txt");
    FileInputStream istr = new FileInputStream(file);
    InputStreamReader irdr = new InputStreamReader(istr);
    BufferedReader brdr = new BufferedReader(irdr);
 
    String a_line;
    int num = 0;
    while ((a_line = brdr.readLine()) != null) {
      ++num;
      if (a_line.toLowerCase().matches(".*\\bvery\\b.*")) {
        System.out.format("%4d) %s\n", num, a_line);
      }
    }
    brdr.close();;
  }
 
}

readlines.ScannerRead
package readlines;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
 
public class ScannerRead {
 
  public static void main(String[] args) throws FileNotFoundException {
    File file = new File("tell-tale-heart.txt");
    Scanner keyboard = new Scanner(file);
    //Scanner keyboard = new Scanner(file.toPath());
    int num = 0;
    while (keyboard.hasNextLine()) {
      ++num;
      String line = keyboard.nextLine();
      if (line.toLowerCase().matches(".*\\bvery\\b.*")) {
        System.out.format("%4d) %s\n", num, line);
      }
    }
  }
}
All 3 programs declare thrown exceptions when the file contents are accessed on usage of (resp):
  1. Files.readAllLines
  2. the object creation: new FileInputStream(file)
  3. the object creation: new Scanner(file)
Note the new usage of the regular expression "\b" which is a word boundary; it matches anything (including empty) so long as it is not a word character. The full regular expression is
.*\\bvery\\b.*
meaning the occurrence of the isolated word "very" anywhere within the line. Using this form omits matching positively against, say, the word "every".

Serializability: objects in files

Java provides the ability to write objects to (binary) files and then read the object back at a later time. Creating a binary representation of an object is referred to as serializing the object. It's a complex procedure whereby pointer chains are followed through to the actual data and the whole thing is laid out in as systematic form so that it can be unserialized later.

A Java class which permits serialization must implement the Serializable interface. There are no member functions in this interface; it is merely a declaration of the ability to be serialized.

Most standard Java classes are serializable. Arrays of serializable objects are serializable. The classes which are not serializable are those representing some kind of "connection" like I/O stream classes and database connection-related classes. A user-defined class can be made serializable simply by declaring
class MyClass implements Serializable {
  // ...
}
The implicit requirement is that all the data members are of serializable classes.

Writing an object to a File:

To write an object to a file, we obtain a raw output stream for the file and promote it to an ObjectOutputStream in order to use the writeOject member.
File f = new File( /*...*/ )
try {
  FileOutputStream  ostr = new FileOutputStream(f); 
 
  ObjectOutputStream oostr = new ObjectOutputStream( ostr ); 
 
  obj = /* some serializable object */
 
  oostr.writeObject( obj );
  oostr.close();  // must do this to ensure completion
}
catch(IOException ex) {
  ...
}
In place of
FileOutputStream ostr = new FileOutputStream(f);
we can use:
OutputStream ostr = Files.newOutputStream(f.toPath());
If the object is not serializable, this exception is thrown:
java.io.NotSerializableException
This is, of course, an unexpected error, so we are not obliged to handle it under most circumstances.

Reading an object from a file

To write an read from a stream, we promote the initial raw stream object to an ObjectInputStream object in order to use the readOject member function.
File f = new File( /*...*/ )
try {
  FileInputStream  istr = new FileInputStream(f); 
  ObjectInputStream oistr = new ObjectInputStream( istr );
 
  Object theObject = oistr.readObject();
  oistr.close();  // probably not necessary
} 
catch(IOException ex) {
  ...
} 
catch(ClassNotFoundException ex) {
  ...
}
Note the extra exception which we must either deal with or pass on:
java.lang.ClassNotFoundException
In contrast to NotSerializableException which occurs if we try to write a non-serializable class, ClassNotFoundException is much more likely to happen; for example, we could inadvertently read a binary file which is not the result of a written object.

The type of the object read is usually known and to use it one typically casts it to the known type:
MyClass c = (MyClass) theObject;
Casting to the wrong type will cause the exception
java.lang.ClassCastException
and so if there are more than one possible type for the object, we can usually distinguish between choices using the instanceof operator in statements like this:
if (theObject instanceof MyClass)  {
  MyClass c = (MyClass) theObject;
  // etc.
}

The ReadObject and WriteObject demo programs

These demo classes illustrate the writing, then reading of an int array and an object of a user-defined Person class. In both cases we simply pass on the declared exceptions.

serial.Person
package serial;
 
import java.io.Serializable;
 
public class Person implements Serializable {
  private String name;
  private int age;
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  public String getName() { return name; }
  public int getAge() { return age; }
}

serial.WriteObject
package serial;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
 
public class WriteObject {
 
  private static void writeObject(Object obj, File f) throws IOException {
    FileOutputStream ostr = new FileOutputStream(f);
    ObjectOutputStream obj_ostr = new ObjectOutputStream(ostr);
    obj_ostr.writeObject(obj);
    obj_ostr.close();
  }
 
  public static void main(String[] args) throws IOException {
    File file;
 
    int[] numArray = new int[]{33, 44, 55};
    file = new File("numarray.dat");
 
    System.out.println("Writing numArray to file: " + file);
    writeObject(numArray, file);
 
    Person joe = new Person("Joe Cool", 23);
    file = new File("person.dat");
 
    System.out.println("Writing Person joe to file: " + file);
    writeObject(joe, file);
  }
}

serial.ReadObject
package serial;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Arrays;
 
public class ReadObject {
 
  private static Object readObject(File f)
      throws IOException, ClassNotFoundException {
    FileInputStream istr = new FileInputStream(f);
    ObjectInputStream obj_istr = new ObjectInputStream(istr);
    Object obj = obj_istr.readObject();
    obj_istr.close();
    return obj;
  }
 
  public static void main(String[] args)
      throws IOException, ClassNotFoundException {
 
    Object obj;
 
    obj = readObject(new File("numarray.dat"));
    System.out.println("object class = " + obj.getClass());
 
    int[] numArray = (int[]) obj;
 
    System.out.println(Arrays.toString(numArray));
 
    obj = readObject(new File("person.dat"));
    System.out.println("object class = " + obj.getClass());
 
    Person thePerson = (Person) obj;
 
    System.out.format("name: %s, age: %s\n",
        thePerson.getName(), thePerson.getAge()
    );
  }
 
}

Exercise: BaseballStats GUI App

The applications contains the following GUI program which is meant to illustrate the utility of saving an object to file and reconstituting it. The two controller classes are:
baseballstats.Player  
baseballstats.Stats  
The app presents a list of baseball player names and their batting averages. The class Player holds the relevant information for each player. The list maintained is an ArrayList of Player objects.

A Swing JList presents these Player objects. By clicking on a record in the JList you present the average in a dedicated textfield. Changing this, and pressing the Change button changes the value in the record; the list is refreshed and the change is immediately seen.

However, once the app is shut down, all changes are lost. What we want to do is to make these changes permanent by maintaining the ArrayList in an external file using the techniques of the previous section.

JList

This application employs a new Java Swing GUI component called the
JList
A key ingredient to using it is the interface
ListSelectionListener
which requires implementation of the valueChanged function. Once an element is selected either of these two member functions can be used:
Object getSelectedValue()
int getSelectedIndex()
To maniplate the contents of a JList (add or modify) we need a separate object model object. The most common model class is
DefaultListModel
which is declared as a data member:
private final DefaultListModel content = new DefaultListModel();
This model object is then assigned to the JList (from the frame) via:
frame.getList().setModel(content);
Once we have this in place, we add elements to the list (initially) by:
content.addElement(...);
Other member functions can be used to change the content if necessary.

Solution

1. For this to work, the Player class must be Serializable. Modify and fix the imports:
public class Player implements Serializable {
2. Add the writeObject static helper function from the previous section, and then writing the players object to a dedicated file whenever the Change button is activated.
public class Stats {
 
// add this data member
  private static final File players_file = new File("players.dat");
 
// modify the Change Button handler:
    frame.getChangeButton().addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent evt) {
        try {
          Player player = (Player) frame.getList().getSelectedValue();
          String avg_text = frame.getField().getText();
          double new_avg = Double.parseDouble(avg_text);
          player.setAvg(new_avg);
          frame.getList().repaint();
 
          // you are adding this one line, but you must
          // restructure to deal with the exceptions
          writeObject(players, players_file);
        }
        catch (NumberFormatException ex) {
          JOptionPane.showMessageDialog(frame, "Invalid numeric value");
        }
        catch (Exception ex) {
          ex.printStackTrace(System.err);
          System.exit(1);
        }
      }
    });
  }
 
// add as a helper function, just above main:
  private static void writeObject(Object obj, File f) throws IOException {
    FileOutputStream ostr = new FileOutputStream(f);
    ObjectOutputStream obj_ostr = new ObjectOutputStream(ostr);
    obj_ostr.writeObject(obj);
    obj_ostr.close();
  }
Observe that we need to deal with the exceptions differently. Mistyping the new batting average is an expected error, wherease everything else is unexpected. 3. Finally, we must read in the file instead of using the initialization as it is, but only when the file exists. Add the readObject member function and modify the initialization in the constructor:
// instead of:
    players = players_initial;
    for (Player player : players) {
      content.addElement(player);
    }
 
// use this code
    try {
      if (players_file.exists()) {
        players = (List<Player>) readObject(players_file);
      }
      else {
        players = players_initial;
      }
      for (Player player : players) {
        content.addElement(player);
      }
    }
    catch (Exception ex) {
      System.err.println(ex);
    }
 
// add this helper just above main:
  private static Object readObject(File f)
      throws IOException, ClassNotFoundException {
    FileInputStream istr = new FileInputStream(f);
    ObjectInputStream obj_istr = new ObjectInputStream(istr);
    Object obj = obj_istr.readObject();
    obj_istr.close();
    return obj;
  }

Binary Files

All files store data in binary form. A text file stores characters as ASCII codes, but allows the user to access the codes as character presentations. When you think of storing a sequence of numbers into a textfile, say:
33.4
-14.66
111.8
you are not actually storing numbers, just the character form of strings that are to be interpreted as numbers. In contrast, a binary file of numbers will store the actual binary form of the numbers.

Here is a two-part example which works with an array of doubles stored into a binary file. It's based on an example in the textbook where we can imagine that the binary data represents sales figures.

The WriteData program writes this array of numbers into the file. The new class used is:

java.io.DataOutputStream

binary.WriteData
package binary;
 
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
 
public class WriteData {
 
  public static void main(String[] args) throws IOException {
    double[] numbers = {
      2345.77, 433.5,   6008.33, 3378.0, 214.88, 5930.44, 444.55
    };
 
    File numsFile = new File("Numbers.dat");
    FileOutputStream ostream = new FileOutputStream(numsFile);
    DataOutputStream out_datastream = new DataOutputStream(ostream);
 
    System.out.println("Writing the numbers to " + numsFile);
 
    for (int i = 0; i < numbers.length; i++) {
      double num = numbers[i];
      out_datastream.writeDouble(num);
    }
 
    out_datastream.close();
  }
}
The ReadData program reads the numbers written into the file. The new class used is:
java.io.DataInputStream

binary.ReadData
package binary;
 
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
 
public class ReadData {
  private static final int BYTES_PER_DOUBLE = Double.SIZE / 8;
 
  public static void main(String[] args) throws IOException {
 
    System.out.println("BYTES_PER_DOUBLE = " + BYTES_PER_DOUBLE);
 
    File numsFile = new File("Numbers.dat");
 
    long num_doubles = numsFile.length() / BYTES_PER_DOUBLE;
 
    System.out.println("total numbers: " + num_doubles);
 
    FileInputStream istream = new FileInputStream(numsFile);
    DataInputStream in_datastream = new DataInputStream(istream);
 
    System.out.println("Reading numbers from " + numsFile);
    System.out.println("--------------------------------");
 
    // Read the contents of the file.
 
    for (int i = 0; i < num_doubles; i++) {
      double number = in_datastream.readDouble();
      String hex = getHexString(number);
      int byte_pos = i * BYTES_PER_DOUBLE;
      System.out.printf("%s) %2d: %s | %s\n", i, byte_pos, hex, number);
    }
 
    in_datastream.close();
  }
 
  private static String getHexString(double number) {
    long lval = Double.doubleToLongBits(number);
    String hex = Long.toHexString(lval).toUpperCase();
    StringBuilder zerofill = new StringBuilder();
 
    for (int i = 0; i < 2 * BYTES_PER_DOUBLE - hex.length(); i++) {
      zerofill.append("0");
    }
    hex = zerofill.toString() + hex;
    return hex.substring(0, 8) + " " + hex.substring(8, 16);
  }
}

Displaying Binary Data

Another novelty of this program is printing the binary data as hex strings. The idea is to imitate what would be seen by a binary (hex) editor as this:
BYTES_PER_DOUBLE = 8
total numbers: 7
Reading numbers from Numbers.dat
--------------------------------
0)  0: 40A2538A 3D70A3D7 | 2345.77
1)  8: 407B1800 00000000 | 433.5
2) 16: 40B77854 7AE147AE | 6008.33
3) 24: 40AA6400 00000000 | 3378.0
4) 32: 406ADC28 F5C28F5C | 214.88
5) 40: 40B72A70 A3D70A3D | 5930.44
6) 48: 407BC8CC CCCCCCCD | 444.55
There are many free hex editors for Windows or MAC like these:

Random Access

One key thing to note about how we have been accessing files up to this point is that the access has consistently been sequential and in read-only, or write-only mode. We start from the beginning and go towards the end, either reading or writing character or binary data.

When you use binary files like the Numbers.dat file, you can read or write at as certain place within the file without doing so sequentially. This arbitrary positional file access is called random access. The way to do it in Java is to use the class
java.io.RandomAccessFile;
Starting from a filename or File object we bypass the stream promotions entirely and write:
File numsFile = new File("Numbers.dat");
RandomAccessFile raFile = new RandomAccessFile(numsFile, "rw");
A random access file introduces the new seek operation to move to a specific byte position within the file. The "rw" string indicates the intended access which, in this case, is both read and write. Additionally one can use "r" (read only) or "w" (write only).

Since we know that our file consists of an array of doubles, we use code like this to move to the correct double:
long index = /* the "array index" */;
raFile.seek( index * bytes_per_double );
The bytes_per_double has been set to
final int bytes_per_double = Double.SIZE/8;
The constant Double.SIZE is the number of bits in a double element (64). Once we have correctly positioned the file pointer to the right place, we either read or write the double at that position:
double num = raFile.readDouble();
or
double num = /* ... */ ;
raFile.writeDouble(num);

Sales Data GUI Demo Program

The purpose of this program is to illustrate random read/write access into the binary file used in the previous section. The GUI Program is:
sales.SalesData
It uses the exact same frame as the stats.BaseballStats program. This one is taken to completion by presenting the sales data numbers, allowing the user to change one, and writing the changed value into the random access file. It is assumed that you have run the binary.WriteData program to generate the Numbers.dat file. Here is the controller code:

sales.SalesData
package sales;
 
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.DefaultListModel;
import javax.swing.JOptionPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import views.TheFrame;
 
public class SalesData {
  private static final int BYTES_PER_DOUBLE = Double.SIZE / 8;
 
  private final File numsFile = new File("Numbers.dat");
 
  private final TheFrame frame = new TheFrame();
 
  private final DefaultListModel content = new DefaultListModel();
 
  public SalesData() throws IOException {
    frame.setTitle(getClass().getSimpleName());
    frame.setLocationRelativeTo(null);
 
    frame.getList().setModel(content);
 
    long num_doubles = numsFile.length() / BYTES_PER_DOUBLE;
 
    RandomAccessFile raFile = new RandomAccessFile(numsFile, "rw");
 
    // read from file into the JList
    raFile.seek(0);
    for (int i = 0; i < num_doubles; i++) {
      double number = raFile.readDouble();
      content.addElement(number);
    }
 
    frame.getList().addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent evt) {
        double number = (double) frame.getList().getSelectedValue();
        frame.getField().setText("" + number);
      }
    });
 
    frame.getChangeButton().addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent evt) {
        try {
          // get value from text field as a double
          String avg_text = frame.getField().getText();
          double new_val = Double.parseDouble(avg_text);
 
          // find the position in the list of the selected value
          int index = frame.getList().getSelectedIndex();
 
          // set the new value in the JList
          content.set(index, new_val);
          frame.getList().repaint();
 
          // write the new value into position in the file
          raFile.seek(index * BYTES_PER_DOUBLE);
          raFile.writeDouble(new_val);
        }
        catch (NumberFormatException ex) {
          JOptionPane.showMessageDialog(frame, "invalid double");
        }
        catch (Exception ex) {
          ex.printStackTrace(System.err);
          System.exit(1);
        }
      }
    });
 
  }
 
  public static void main(String[] args) throws IOException {
    SalesData app = new SalesData();
    app.frame.setVisible(true);
  }
}


© Robert M. Kline