Chapter 11: IO

11.1: Class File

Class java.io.File is created to connect to a directory or a file which already exists or is to be created, so that you can check whether the file already exists (using File's method exists is the only way to do this in Java), create, list, rename, delete, extract information, etc. You can also wrap other IO classes around it and create every kind of IO streams.

   
   import java.io.*;
   
   public class Test {
      public static void main(String [] s)
      {
         File dir = new File("C:\\000Frank");
         String [] l = dir.list();
   
         for(int i = 0; i < l. length; i++)
            System.out.println(l[i]);
   
         try {
            File file = new File("C:\\000Frank\\test.txt");
   
            if(!file.exists())
            {
                  PrintStream ps = new PrintStream(new FileOutputStream(file));
                  ps.println("This is a test!");
                  ps.flush();
                  ps.close();
              }
   
             BufferedReader br = new BufferedReader(
                                new InputStreamReader(
                                new FileInputStream(file)));
              System.out.println("\nRead back: " + br.readLine());
              br.close();
   
             System.out.println("\nAbsolute path of the new file: "
                                + f2.getAbsoluteFile());
         }
         catch(IOException e)
         {  e. printStackTrace(); }
      }
   }

Output will be:

   
   Books.mdb
   Deitel
   Note-Java.doc
   survey.txt
   Test.class
   
   Read back: This is a test!
   
   Absolute path of the new file: C:\000Frank\test.txt

The string passed to the File's constructor can be a series of pathnames or pathnames ended with a filename. The Method list( ) returns a String array of the names of the files in the directory. Here "\\" instead of "\" is used, because single slash is used by Java for escape characters.

* Method list( ) with Searching Criteria

When you call method list( ) without any argument, it is to list all files in that directory. You can also list only those files whose name conforms to a certain filtering criteria, by passing to list( ) an object which implements interface FilenameFilter, which has a filtering method to check whether a directory or file conforms to the filtering criteria:

   
   public interface FilenameFilter {
      boolean accept(File dir, String name);
   }

This is another use of call back technique.

One example of implementation of FilenameFilter:

   
   import java.io.*;
   
   class Filter1 implements FilenameFilter {
      private String criteria;
   
      public Filter1(String s)
      {  criteria = s; }
   
      public boolean accept(File dir, String name)
      {
         String s1 = dir. getName();  // strip away the pathname prefix
         String s2 = new File(name).getName();
         return (s1.indexOf(criteria) != -1) || (s2.indexOf(criteria) != -1);
      }
   }
   
   public class Test {
      public static void main(String [] args)
      {
         File f;
         String [] s;
   
         if(args. length == 0)
         {
            f = new File(".");
            s = f. list();
         }
         else if(args. length == 1)
         {
            f = new File(args[0]);
            s = f. list();
         }
         else
         {
            f = new File(args[0]);
            s = f. list(new Filter1(args[1]));
         }
   
         for(int i = 0; i < s. length; i++)
            System.out.println(s[i]);
      }
   }

When you type in

   
   java c:\\mydocu txt

It will list all files and directories in directory c:\mydocu whose name contains string txt in its filename.

11.2: Absolute and Relative Pathname

Suppose the program is in "C:\Java", and there is a directory "C:\Java\Dir1\Dir2". Java takes the current directory as the default directory. To create a File object connected to the current default directory:

   
   File f = new File("."); 

To create a File object connected to directory C:\Java\Dir1\Dir2:

   
   File f = new File("Dir1\\Dir2");

or

   File f = new File("C:\\java\\Dir1\\Dir2");

When you provide "Dir1\\Dir2", you are providing a relative pathname, in relative to the default directory. When you provide "C:\\java\\Dir1\\Dir2", you are providing the absolute pathname.

* Get the Pathname of Current Default Directory

The current default directory is named by system property user.dir. To find out the current directory, use method System.getProperties( ).get("user.dir") to get an object containing the pathname string. The following example first acquires the current default directory from the system, then create a new directory "Dir3" under it, then print out the absolute pathname of the new directory:

   
   import java.io.*;
   
   public class Test {
      public static void main(String [] s)
      {
         String def1 = (System.getProperties().get("user.dir")).toString();
         System.out.println("Current default directory is: " + def1);
         File f = new File(def1 + "\\Dir3");
         f.  mkdir();
         System.out.println("New created directory is: " + f. getAbsolutePath());
       }
   }

Output will be:

   
   Current default directory is: C:\Mydocu\Java
   New created directory is: C:\Mydocu\Java\Dir3

* Parent directory

Suppose you have directory "c:\000Frank\dir1" and "c:\000Frank\dir2", in dir1 if you want to access file "c:\000Frank\index.html", you can also say "/.. /index.html", where ".." means the parent directory. If you want to access "c:\000Frank\dir2\index.html" from "dir1", you can also say "/.. /dir2/index.html".

11.3: File Separators

Two file separators are recognized by Java: "/" and "\\" - "\" is regarded as a escape sequence. Therefore, a valid filename can be

   
   String filename = "home/www/index.html"

or

   "home\\www\\index.html"

Java has a platform-independent way to express file separator: File.separator. So the file name can also be:

   
   "home" + File.separator + "www" + File.separator + "index.html"

11.4: IO Stream Classes

Java IO classes in package java.io can be classified into two groups: basic group and enhanced group.

* Basic group

The basic group of IO classes translate the data on different physical media into a standard stream format. Different classes in this group deal with different physical data sources such as arrays, strings or files, but they provide identical interfaces to allow enhanced group classes to interact with them. They act as media translators and do not provide sophisticated functionality.

Data Sources

Character Streams

Byte Streams

Functionality

Memory

CharArrayReader

CharArrayWriter

ByteArrayInputStream

ByteArrayOutputStream

Read from and write to memory. Can be created on an existing array.

String

StringReader

StringWriter

StringBufferInputStream

StringReader is used to read characters from a String in memory.

StringWriter is used to write to a String. It uses a StringBuffer, which is later converted to a String.

StringBufferInputStream is similar to StringReader, except that it reads bytes from a StringBuffer.

Pipe

PipedReader

PipedWriter

PipedInputStream

PipedOutputStream

Used to direct the output from one program (or thread) into the input of another. The wraping structure is:

InputStream/Reader < input pipe < output pipe < OutputStream/Writer

File

FileReader

FileWriter

FileInputStream

FileOutputStream

Used to read from or write to a file on the native file system.

* Enhanced group

The enhanced group of IO classes do not directly interact with physical data source. They wrap around basic group classes to attach more functionality to them, such as buffering, reading and writing of primitive types and reading a whole line. Because basic group classes provide identical interfaces, they are exchangable to enhanced group classes.

Process

Character Streams

Inherit

Constr. Argu.

*

Byte Streams

Inherit

Argu

Buffering

BufferedReader

Reader

Reader

*

BufferedInputStream

IS

IS

=

BufferedWriter

Writer

Writer

*

BufferedOutputStream

OS

OS

Filtering

FilterReader

Reader

File, String

*

FilterInputStream

IS

IS

=

FilterWriter

Writer

Writer

*

FilterOutputStream

OS

OS

Bytes Û Characters

InputStreamReader

Reader

IS

**

   

=

OutputStreamWriter

Writer

OS

*

     

Concatenation

   

**

SequenceInputStream

IS

IS, enum

Object Serialization

   

**

ObjectInputStream

IS

IS

=

     

*

ObjectOutputStream

OS

OS

Conversion

   

*

DataInputStream

IS

IS

=

     

*

DataOutputStream

OS

OS

Counting

LineNumberReader

Reader

Reader

*

LineNumberInputStream

IS

IS

Peeking Ahead

PushbackReader

Reader

Reader

*

PushBackInputStream

IS

IS

Printing

PrintWriter

Writer

Writer, OS

*

PrintStream

OS

OS

Buffer data while reading or writing.

The bridge between byte streams and character streams. They wrap around the opposite type of stream.

Concatenates multiple input streams into one input stream.

Used to serialize objects.

Read or write primitive Java data types in a machine-independent format.

Keep track of line numbers while reading. It has a method getLineNumber to return the number of the current line being read.

They have a one-character (or byte) pushback buffer. Sometimes when reading data from a stream, you will find it useful to peek at the next item in the stream in order to decide what to do next. However, if you do peek ahead, you'll need to put the item back so that it can be read again and processed normally.

Contain convenient printing methods. These are the easiest streams to write to, so you will often see other writable streams wrapped in one of these.

11.5: Byte-Stream Group & Character-Stream Group

Java IO classes can be divided into another two groups:

* Byte-stream

They all inherit from InputStream or OutputStream, and deals with bytes. Among them, DataInputStream / DataOutputStream and PrintStream are always used as the outer shells of wrapping. DataInputStream / DataOutputStream is for generating standard-format IO stream, and PrintStream is for display-format output. Other streams such as BufferedInputStream or LineNumberInputStream are usually used as middle layers to provide one special function.

* Character-stream

They all inherit from Reader or Writer, and deals with characters. Among them, BufferedReader/BufferedWriter and PrintWriter are more often used as outer shells of wrapping - one for generating standard-format IO stream and the other for display-format output. However, they do not have much more methods than other streams. BufferedReader has a useful method readLine. BufferedWriter and FileWriter can both write a string.

Especially, readLine reads a line of text (a line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a '\r\n'), and return a string without the line terminator.

* Do not mix the two stream groups

The two stream groups have different ways to handle data. Therefore, if you use DataOutputStream to output data, you should use DataInputStream to input it. You can not mix them, e.g., use DataOutputStream to output and BufferedReader to read. You will encounter problem.

InputStream (IS) and OutputStream (OS) can only handle 8-bit byte, while Reader and Writer can handle 16-bit Unicode characters, which is used for internationalization. They are also faster than IS and OS. So always try to use Reader and Writer first.

In the following example, a mixture of strings and numbers are output and input using different groups of streams:

* IO Using Byte-Stream Group

   
   import java.io.*;
   
   public class Test1 {
      public static void main(String [] s)
      {
         try {
            DataOutputStream s1 = new DataOutputStream(
                                  new BufferedOutputStream(
                                  new FileOutputStream("test1.txt")));
            s1.writeChars("The value of PI is \n");
            s1.writeDouble(3.14159d);
            s1.close();
   
            DataInputStream s4 = new DataInputStream(
                                 new BufferedInputStream(
                                 new FileInputStream("test1.txt")));
            String str = "";
            char c1;
   
            do {
               c1 = s4.readChar();
               str += c1;
            }while(c1 != '\n');
   
            str += s4.readDouble();
            System.out.println(str);
         }
         catch(FileNotFoundException e)
         { System.out.println("FileNotFoundException!!"); }
         catch(EOFException e)
         {  System.out.println("EOFException!!"); }
         catch(IOException e)
         {  System.out.println("IOException!!"); }
      }
   }

Output will be:

   
   The value of PI is
   3.14159

If you open the "test1.txt" created by FileOutputStream you will find peculiar codes instead of the text shown on the screen. You will also find that there is a space before each character. This proves that the output is not for display.

Note that because DataInputStream's readLine( ) has been deprecated, you have to read characters one by one.

* IO with Character-Stream Group

   
   import java.io.*;
   
   public class Test1 {
      public static void main(String [] s)
      {
         try {
            BufferedWriter s1 = new BufferedWriter(
                                new FileWriter("test1.txt"));
   
            double d1 = 3.14159d;
            s1.write("The value of PI is \n");
            s1.write(Double. toString(d1));
            s1.close();
   
            BufferedReader s2 = new BufferedReader(
                                new FileReader("test1.txt"));
   
            String str = s2.readLine();
            d1 = Double.parseDouble(s2.readLine());
            System.out.print(str + '\n' + d1 + '\n'); 
         }
         catch(FileNotFoundException e)
         {  System.out.println("FileNotFoundException!!"); }
         catch(EOFException e)
         {  System.out.println("EOFException!!"); }
         catch(IOException e)
         {  System.out.println("IOException!!"); }
   
      }
   }

11.6: Output Format: Standard-Format & Display Format

When you use DataOutputStream, data are organized in a standard way so that it can be read by DataInputStream later regardless of platform. However, as you can see in last example, the format of the output is not neat or even recognizable for display.

   
   DataOutputStream s1 = new DataOutputStream(
                         new BufferedOutputStream(
                         new FileOutputStream("test1.txt")));
   s1.writeChars("First line\nSecond line\n ");
   s1.writeDouble(3.1415d);
   s1.close();

Output:

   
   _F_i_r_s_t___l_i_n_e_
   _S_e_c_o_n_d___l_i_n_e_
   @    !ÊÀƒo

If the output is for display for human, you should use PrintStream instead of DataOutputStream. Then the output is neat and understandable:

   PrintStream s2 = new PrintStream(
                    new BufferedOutputStream(
                    new FileOutputStream("test2.txt")));
   s2.print("First line\nSecond line\n ");
   s2.print(3.1415d);
   s2.close();

Output:

   First line
   Second line
   3.1415

11.7: OutputStream's Subclasses Must Implement write(int b)

Java requires that any subclass of OutputStream should implement at least its write(int b) method, because other OutputStream methods such as write(byte [ ], int off, int length) uses this method to do the job, and thus not necessarily be implemented.

11.8: Finding the End of Input Stream

Two ways to find out the end of an input stream or an eof. You can do it by catching an exception, but it is considered to be a misuse of exception. Exception should only be used for error handling.

The other way is to use method available( ) to find out the number of bytes that can be read from the stream before blocking. All of the enhanced-group input streams (ByteArrayInputStream, FileInputStream, ObjectInputStream, PipedInputStream) has this method. One example:

   
   while(i1.available() != 0)
      System.out.println(i1.readByte( ));

11.9: Two Traps with Buffered IO

When using a buffered output, because the output may stay in the buffer, remember to call flush when you want to see the output result or close the stream, unless you have passed true to the constructor of the buffered stream to achieve instant flush.

When using a buffered input, because a lot of data may have already been read in the buffer even if you haven't actually read anything from the buffered stream, do not try to read from the underlying stream, otherwise you may have skipped a block of data.

11.10: RandomAccessFile

Class java.io.RandomAccessFile wraps around a physical file in the hard disk. It can be used to both read and write. Its constructor takes two arguments: first one is the name of the file, second one can be "r" for read-only and "rw" for read and write.

Data are written into RandomAccessFile in a fixed length. For example, integer 11 and 1234567 may occupy different length when written into a file with normal methods, but when written into RandomAccessFile they occupy the same length. This enables random access to any record in the file.

   
   import java.io.*;
   
   public class Test {
      public static void main(String [] s)
      {
         try {
               RandomAccessFile r1 = new RandomAccessFile("test.txt", "rw");
               for(int i = 0; i < 10; i++)
                 r1.writeInt(i);
               r1.close();
   
               r = new RandomAccessFile("test.txt", "r");
               for(int i = 0; i < 10; i++)
                 System.out.println("Value " + i + ": " + r1.readInt());
               r1.close();
   
               r1 = new RandomAccessFile("test.txt", "rw");
               r1.seek(12);
               r1.writeInt(47);
               r1.close();
               System.out.println("\n \n");
   
               r = new RandomAccessFile("test.txt", "r");
               for(int i = 0; i < 10; i++)
                 System.out.println("Value " + i + ": " + r1.readInt());
               r1.close();
         }
         catch(FileNotFoundException e)
         {  System.out.println("FileNotFoundException!"); }
         catch(IOException e)
         {  System.out.println("IOException!"); }
      }
   }

Output will be:

   
   Value 0: 0
   Value 1: 1
   Value 2: 2
   Value 3: 3
   Value 4: 4
   Value 0: 0
   Value 1: 1
   Value 2: 2
   Value 3: 47
   Value 4: 4

11.11: System IO Objects

There are three standard IO objects: System.out, System.err and System.in, which have been connected to the standard IO sink such as the keyboard and the screen.

System.out and System.err have been pre-wrapped as a PrintStream object. So you can directly write to the to place data on screen. But System.in is a raw InputStream with no wrapping. So you have to use other enhanced-group streams to wrap it before you can read from keyboard.

   
   import java.io.*;
   
   public class Test {
       public static void main(String [] args)
       {
           try{
                 InputStreamReader i1 = new InputStreamReader(System.in);
              
             /* It also can be e.g.
               DataInputStream i1 = new DataInputStream(
                                      new BufferedInputStream(System.in)); */
                 char c1;
               while(true)
               {
                   c1 = (char)i1.read();
                   System.out.print(c1);
               }
           }
           catch(IOException e)
           {  System.out.println("IOException!"); }
       }
   }

Although method read( ) reads one character, it returns an integer. So you have to use explicit casting from int to char. Another example:

   
   import java.io.*;
   
   public class Test {
       public static void main(String [] argu)
       {
           try{
               BufferedReader r1 = new BufferedReader(
                                   new InputStreamReader(System.in));
               String s1 = r1.readLine();
               System.out.println(s1);
           }
           catch(IOException e)
           {  System.out.println("IOException!"); }
       }
   }

11.12: StreamTokenizer

Class java.io.StreamTokenizer wraps around an InputStream object. It breaks an InputStream into a sequence of tokens separated by delimiters.

It keeps a default list of delimiters such as whitespace, #, &, %, $, \n, etc. So if you use it on a stream of "AAA BBB#CCC&DDD%EEE", it will automatically break it into "AAA", "BBB", "CCC", "DDD", "EEE".

* Fields of StreamTokenizer:

int ttype: status variable, with four constant values listed below.

String sval: contains a string word -- if the current token is a word.

double nval: contains the value of the current token -- if it is a number. As you can see in the following example, StreamTokenizer can distinguish numbers from normal strings even if it was written as a normal string.

static int TT_EOF: an constant value for ttype indicating eof has been read.

static int TT_EOL: an constant value for ttype indicating eol (can be '\n' or '\r') has been read.

static int TT_NUMBER: an constant value for ttype indicating that a number has been read.

static int TT_WORD: an constant value for ttype indicating that a number has been read.

* Some methods of StreamTokenizer:

Constructor argument: Reader

void eolIsSignificant(boolean flag) - Decides whether or not eol ('\n' or '\r') will be treated as a token. If not, it will just be treated as a normal delimiter and be discarded.

int nextToken( ) - get the next token

void whitespaceChars(int from, int to) - Treat the range of characters as delimiters.

void wordChars(int from, int to) - Treat the range of characters as part of a word, i.e., not as delimiters.

void ordinaryChar(int) & void ordinaryChars(int from, int to) - treat the characters as "ordinary". I don't know what it means. It seems to be the same as whitespaceChars.

Example:

   
   import java.io.*;
   
   public class Test {
      public static void main(String [] args)
      {
         try{
            BufferedWriter d1 = new BufferedWriter(
                                new FileWriter("test.txt"));
            String ss1 = "AAA BBB.CCC#DDD%EEE\nFFF$36.5&GGGxHHHxII-II";
            d1.write(ss1);
            d1.close();
            StreamTokenizer s1 = new StreamTokenizer(
                                 new InputStreamReader(
                                 new FileInputStream("test.txt")));
            // Configuring the StreamTokenizer
            s1.eolIsSignificant(true);     // treat eol as a token
            s1.whitespaceChars('.', '.');  // treat '.' as a delimiter
            s1.ordinaryChar('x');          // treat 'x' as a delimiter
            s1.ordinaryChar('-');          // treat '-' as a delimiter
            s1.wordChars('#', '#');        // do not treat '#' as delimiter
            while(s1.nextToken() != StreamTokenizer. TT_EOF)
            {
               switch(s1.ttype) {
                  case StreamTokenizer. TT_EOL:
                     System.out.println('\n');
                     break;
                  case StreamTokenizer. TT_NUMBER:
                     System.out.print(Double. toString(s1.nval * 2.0d) + " | ");
                     break;
                  case StreamTokenizer. TT_WORD:
                     System.out.print(s1.sval + " | ");
               }
            }
            System.out.println('\n');
         }
         catch(IOException e)
         {  System.out.println("IOException: " + e. getMessage()); }
      }
   }

The following output is generated without the five configuring statements:

   
   AAA | BBB.CCC | DDD | EEE | FFF | 73.0 | GGGxHHHxII-II |

You can see that here whitespace, '#', '%', '\n', '$' and '&' are treated as delimiters and simply discarded, while '.', '-' and 'x' are not. After the five configuring statements, '.', '-' and 'x' are treated as delimiters, and '#' are removed from the delimiter list, and eol '\n' are treated as a token and thus produced a new line:

   
   AAA | BBB | CCC#DDD | EEE |
   FFF | 73.0 | GGG | HHH | II | II |

11.13: StringTokenizer

Class java.util.StringTokenizer is a simplified StreamTokenizer specially dealing with strings. It does not have its own default delimiter list. Its constructor takes a String and a delimitor as arguments. You can also pass the delimitor each time calling nextToken. When you pass the delimiter String, each character in the string will be used as delimiters.

Its constructor's third optional boolean argument decides whether delimiters themselves will be returned as tokens.

Some methods:

Constructor argument: (String) or (String str, String delimiter)

int countTokens( ): number of tokens left in the string

boolean hasMoreElements( ): return true if there is more tokens left in the string

boolean hasMoreTokens( ): same as hasMoreElements

Object nextElement( ): return the next token as an Object

String nextToken( ): return the next token

String: nextToken(String delimiter): return the next token

11.14: Redirecting Standard IO

Class System has three methods to allow you to redirect the standard IO from keyboard and screen to any PrintStream you pass to the methods:

System.setIn(PrintStream)

System.setOut(PrintStream)

System.setErr(PrintStream)

11.15: Unbuffered InputStream with Method readLine

In Java only BufferedReader has method readLine. Because it is buffered, once it wraps around an InputStream, you can no longer read from it. So if you want to use method readLine but would like to avoid the buffer problem, you can use the following class:

   
   import java.io.InputStream;
   import java.io.IOException;
   
   public class ReadLineInputStream extends InputStream {
       private InputStream inputStream = null;
   
       public ReadLineInputStream(InputStream is)
       {
           inputStream = is;
       }
   
   
       public String readLine() throws IOException
       {
           final int arrayLength = 256;
           byte[] line = new byte[arrayLength];
           int index = 0;
           byte b = ' ';
           StringBuffer sb = new StringBuffer("");
   
           while(true)
           {
               b = (byte)inputStream.read();
   
               if(b == -1)
                   break;
               else if(b == '\r')
               {
                   inputStream.read();
                   break;
               }
   
               line[index] = b;
               index ++;
   
               if(index == arrayLength)
               {
                   sb.append(new String(line));
                   index = 0;
               }
           }
   
           sb.append(new String(line, 0, index));
           return sb.toString();
       }
   
       public int available() throws IOException
       {
           return inputStream.available();
       }
   
       public void close() throws IOException
       {
           inputStream.close();
       }
   
       public void mark(int readlimit)
       {
           inputStream.mark(readlimit);
       }
   
       public boolean markSupported()
       {
           return markSupported();
       }
   
       public int read() throws IOException
       {
           return inputStream.read();
       }
   
       public int read(byte [] b) throws IOException
       {
           return inputStream.read(b);
       }
   
       public int read(byte [] b, int off, int len) throws IOException
       {
           return inputStream.read(b, off, len);
       }
   
       public void reset() throws IOException
       {
           inputStream.reset();
       }
   
       public long skip(long n) throws IOException
       {
           return inputStream.skip(n);
       }
   }