Main Page

Previous Next

Reading a Text File

We can now attempt to read the very first file that we wrote in the previous chapter – charData.txt. We wrote this file as Unicode characters so we must take this into account when interpreting the contents of the file.

We can set up a File object encapsulating the file path to start with and create a FileInputStream object from that. We can then obtain a reference to the channel that we will use to read the file. We won't include all the checking here that you should apply to validate the file path, as you know how to do that:

File aFile = new File("C:/Beg Java Stuff/charData.txt");
FileInputStream inFile = null;

try {
  inFile = new FileInputStream(aFile); 

} catch(FileNotFoundException e) {
  e.printStackTrace(System.err);
  System.exit(1);
}
FileChannel inChannel = inFile.getChannel();    

Of course, we can only read the file as bytes into a byte buffer. We create that exactly as we saw previously when we were writing the file. We know that we wrote 48 bytes at a time to the file – we wrote the string "Garbage in, garbage out\n" that consists of 24 Unicode characters. However, we tried appending to the file an arbitrary number of times, so we should provide for reading as many Unicode characters as there are. We can set up the ByteBuffer with exactly the right size with the statement:

ByteBuffer buf = ByteBuffer.allocate(48);   

The code that we use to read from the file needs to allow for an arbitrary number of 24-character strings in the file. Of course, it will also allow for the end-of-file being reached while reading the file. We can read from the file into the buffer like this:

try {
  while(inChannel.read(buf) != -1) {
    // Code to extract the data that was read into the buffer...
    buf.clear();                    // Clear the buffer for the next read
  }
  System.out.println("EOF reached.");
 
} catch(IOException e) {
  e.printStackTrace(System.err);
  System.exit(1);
}

The file is read in the expression used for the while loop condition. The read() method will return-1 when the end-of-file is reached so that will end the loop. Within the loop we have to extract the data from the buffer, do what we want with it, and then clear the buffer ready for the next read operation.

Getting Data from the Buffer

After the read operation, the buffer's position will point to the byte following the last byte that was read. Before we attempt to extract any data from the buffer we therefore need to flip the buffer to reset the position back to the beginning of the data and the limit to the byte following the last byte of data. One way to extract bytes from the buffer is to use the ByteBuffer object's getChar() method. This will retrieve a Unicode character from the buffer at the current position and increment the position by two. This could work like this:

buf.flip();
StringBuffer str = new StringBuffer(buf.remaining()/2);
while(buf.hasRemaining())
  str.append(buf.getChar());

System.out.println("String read: "+ str.toString());

This code should replace the comment in the previous fragment that appears at the beginning of the while loop. We first create a StringBuffer object in which we will assemble the string. This is the most efficient way to do this – using a String object would result in the creation of a new object each time we add a character to the string. The remaining() method for the buffer returns the number of bytes read after the buffer has been flipped, so we can just divide this by 2 to get the number of characters read. We extract characters one at a time from the buffer in the while loop and append them to the StringBuffer object. The getChar() method increments the buffer's position by 2 each time so eventually hasRemaining() will return false,when all the characters have been extracted, and the loop will end. We then just convert it to a string and output it on the command line.

This approach works OK but a better way is to use a view buffer of type CharBuffer. The toString() method for the CharBuffer object will give us the string that it contains directly. Indeed, we can boil the whole thing down to a single statement:

System.out.println("String read: " +
                   ((ByteBuffer)(buf.flip())).asCharBuffer().toString());

The flip() method returns a reference of type Buffer so we have to cast it to type ByteBuffer to make it possible to call the asCharBuffer() method for the buffer.

We can assemble these code fragments into a working example.

Try It Out – Reading Text from a File

Here's the code for the complete program:

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ReadAString {
  public static void main(String[] args) {
    File aFile = new File("C:/Beg Java Stuff/charData.txt");
    FileInputStream inFile = null;
   
    try {
      inFile = new FileInputStream(aFile); 

    } catch(FileNotFoundException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }

    FileChannel inChannel = inFile.getChannel();    
    ByteBuffer buf = ByteBuffer.allocate(48);    
    try {
      while(inChannel.read(buf) != -1) {
        System.out.println("String read: " +
                            ((ByteBuffer)(buf.flip())).asCharBuffer().toString());
        buf.clear();                    // Clear the buffer for the next read
      }
      System.out.println("EOF reached.");
      inFile.close();                   // Close the file and the channel
 
    } catch(IOException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
    System.exit(0);
  }
}

When you compile and run this, you should get output something like:

String read: "Garbage in, garbage out

String read: "Garbage in, garbage out

String read: "Garbage in, garbage out

EOF reached.

The gap between the lines of output is because each string ends with a '\n' character.

How It Works

There's nothing new here beyond what we have already discussed. If you want to output the length of the file, you could add a statement to call the size() method for the inChannel object:

System.out.println("File contains "+ inChannel.size() + " bytes.");

Immediately before the while loop would be a good place to put it, as the size() method can throw an IOException. You might also like to modify the code to output the buffer's position and limit before and after the read. This will show quite clearly how these change when the file is read.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor