Title: I/O Starter
Course: CPS 261: Advanced Java Concepts ~ DL & MM MASTER

I/O Starter

We will cover I/O in great detail, later. However, it is very handy to have a few I/O features available now. Covering I/O will give us a better foundation for understanding Exceptions.

If you want to read what other books have on I/O, here are some other references:

- Chapter 15 in Sun Certified Programmer for Java 2
- Java Tutorial: Trails Covering the Basics: Essential Java Classes: Reading and Writing, but no 'rithmetic
- Thinking in Java: The Java IO System
- Java API Specification: java.io package

1. Universal I/O classes - We are going to focus on I/O to a file. However, java makes all I/O look similar. So the same techniques for sending to a file, and reading a file apply to other I/O sources such as sending and receiving data from a browser.
2. byte streams Versus char streams - Java has 2 families of classes. One family of classes focuses on data flow that consists of bytes (8 bit quantities), and the other family of classes focuses on Text related data which in Java means Unicode char (16 bit quantities). In many cases it looks like you have 2 classes that do the same thing, but as you look at it closer you will see that the char streams focus on text related issues, and the byte related streams focus on raw data issues.
3. xxxReader, xxxWriter, xxxOutputStream, and xxxInputStream - The classes we will be discussing are either input related or output related. Classes that end with Stream are dealing with bytes. Classes that end with either Reader or Writer are dealing with Unicode chars.
4. Low Level I/O - At the lowest level, Java defines some Abstract classes that have read, write routines. Note that because we have abstract classes, here you can't just call the constructors to create an instance. You need some other class --- see the next section: Disk I/O
  - InputStream - Contains read routines for bytes
  - OutputStream - Contains write routines for bytes
  - Reader - Contains read routines for chars
  - Writer - Contains write routines for chars
5. Disk I/O - We are going to focus on Disk Input Output for now. To create instances of Low Level I/O for Disk I/O we can use one of the following classes.


    // Open an instance of Reader from a File
    String filename = "myfile.txt";

    //FileReader is derived from Reader
    FileReader f = new FileReader(filename);
    // or you can do the following

    //FileInputStream is derived from InputStream
    FileInputStream f2 = new FileInputStream(filename);

    // For output, you do the following:
    // The file will be created if it doesn't exist.  If it does
    // exit then it will be completely overwritten from the beginning.
    FileWriter f3 = new FileWriter(filename);

    // Open in append Mode
    FileWriter f4 = new FileWriter(filename, true);

    // OutputStream File writeable opens:
    FileOutputStream f5 = new FileOutputStream(filename);

    // Or in append mode
    FileOutputStream f6 = new FileOutputStream(filename, true);


6. Exceptions - Most of the I/O classes throw exceptions that must be dealt with. We are going to look at Exceptions in more detail in a later section. For now to get around exceptions we will need to keep the compiler happy by adding the phrase:
throws IOException at the end of the procedure containing the I/O routine. For example:
int MyRoutine throws IOException
{ ......
7. Sample LowLevel Copy routine:

import java.io.*;

public class Copy {
    static void byteCopy(String from, String to) throws IOException
    {
    // Using Non-buffered Low Level I/O byte stream operations

        int b;
        // Using byte stream  (8 bit)
        FileInputStream in = new FileInputStream(from);
        FileOutputStream out = new FileOutputStream(to);

        while ((b = in.read()) != -1)
           out.write(b);

        in.close();
        out.close();
    }

    static void charCopy(String from, String to) throws IOException
    {
    // Using Non-buffered Low Level I/O char stream operations

        int c;
        // Using Unicode Approach char stream  (16 bit)
        FileReader in = new FileReader(from);
        FileWriter out = new FileWriter(to);

        while ((c = in.read()) != -1)
           out.write(c);

        in.close();
        out.close();
    }

    public static void main(String[] args) throws IOException {
        charCopy("Copy.java", "junk1.txt");
        byteCopy("junk1.txt", "junk2.txt");
    }
}




8. Buffering Classes - You can do all of your work with low level I/O routines, but your program will normally not be efficient. Reading or Writing the disk or almost any I/O device is inefficient without some kind of buffering. Reading the disk 1 byte at a time is very slow, but reading the disk in 4k hunks runs a lot faster. However, your application often wants the data in smaller hunks than 4k at a time. A Buffering class provides a layer of software that allows the application to access the I/O in small hunks, while the actual reads/writes to the I/O device is done in some larger buffer size.
  - BufferedInputStream - Buffering class for the InputStream class
  - BufferedOutputStream - Buffering class for the OutputStream class
  - BufferedReader - Buffering class for the Reader class
  - BufferedWriter - Buffering class for the Writer class
9. Adding buffering to your application - To add buffering to your application, you wrap your stream or your Reader class with a buffering class. As the following example shows, the code can look mostly the same, we are just adding buffering support. The buffering constructors take as a parameter an instance of the low level input source. For example:

import java.io.*;

public class CopyBuffered {
    static void byteCopy(String from, String to) throws IOException
    {
       int b;
    // Using Buffered I/O byte(8 bit) stream operations

        FileInputStream insrc = new FileInputStream(from);
        FileOutputStream outsrc = new FileOutputStream(to);
        BufferedInputStream in = new BufferedInputStream(insrc);
        BufferedOutputStream out = new BufferedOutputStream(outsrc);


        while ((b = in.read()) != -1)
           out.write(b);

        in.close();
        out.close();
    }

    static void charCopy(String from, String to) throws IOException
    {
     int c;

    // Using Buffered I/O char(16 bit Unicode) stream operations

        FileReader insrc = new FileReader(from);
        FileWriter outsrc = new FileWriter(to);
        BufferedReader in = new BufferedReader(insrc);
        BufferedWriter out= new BufferedWriter(outsrc);


        while ((c = in.read()) != -1)
           out.write(c);

        in.close();
        out.close();
    }

    public static void main(String[] args) throws IOException {
        charCopy("CopyBuffered.java", "junk1.txt");
        byteCopy("junk1.txt", "junk2.txt");
    }
}




10. PrintStreams - More Convenient Output Buffering Mechanism. So far most of our output has been done with:
System.out.println (.....) or
System.out.print(....)

The println, and print family of routines provide a higher level way to generate output than write one or more bytes. System.out.println normally sends output to your screen, but you can have the println, print family of routines available for any output device : screen, file, web_browser, etc.

System.out is an instance of the PrintStream class. Suppose I want to write to a file named myfile.text with a print, println interface. Here is a snippet of code that will do it:


    // the following methods can throw IOException so add throws IOException to
    // the method definition.
    FileOutputStream f = new FileOutputStream("myfile.text");
    PrintStream myout = new PrintStream(f);

    myout.println("My age is " + age);
    myout.print("My name is " + name);


11. MyOutput class - Consider the following MyOutput class that has 2 constructors:
MyOutput()
MyOutput(String filename)

This code has the property if you use the print and println methods of a class created with the first constructor, you send output to the screen. If you use the second constructor, you send output to the file contained in the filename parameter.

package tools;
import java.io.*;

public class MyOutput {
// If a file is not passed in, we will open up a PrintStream
// created from System.out

   PrintStream out = System.out;
   String fileName=null;

// output goes to screen
public MyOutput()  {
   }

// output goes to a file
public MyOutput(String file)
   {
   fileName = file;
   open2(fileName, false);
   }

// If append = true, then the output file will be appended as
// opposed to overwriting from the beginning.
public MyOutput(String file, boolean append)
   {
   fileName = file;
   if (!open2(fileName, append))
       System.out.println("Defaulting to Terminal Screen");
   }


public boolean open(String arg)
   {
        return open2(arg, false);
    }


public boolean open2(String arg, boolean append)
   {
       try {
               String file = arg;
               if (file.length() > 0)
                   {
                        FileOutputStream fw;
                        if (append)
                           fw = new FileOutputStream(file, true);
                        else
                           fw = new FileOutputStream(file);
                       out =    new PrintStream(fw);
                   }
               return true;
       }
       catch (FileNotFoundException e) {
           System.out.println("File Not Found Exception in get" +
                   e.getMessage());
           return false;
       }
   }

public void println (String str) {
   out.println(str);
}

public void print(String str)
   {
      out.print(str);
   }
public void close()
{
   out.close();
}
protected void finalize()
{
    close();
}

public static void main(String args[])  {
       // myout1 will write to screen
       MyOutput myout1 = new MyOutput() ;

       //myout2 will write to file junk.txt
       MyOutput myout2 = new MyOutput("junk.txt");

       myout1.println("First Line"); // write to screen
       myout2.println("First Line"); // write to junk.txt

       myout1.println("Second Line"); // write to screen
       myout2.println("Second Line"); // write to junk.txt

       myout1.close();
       myout2.close();
   }
}


12. Converting an InputStream class to a Reader class - Some of the better buffering mechanisms in the Java I/O classes live in the char Unicode world. For example the BufferedReader class has one method (readLine) which is very handy. The readLine routine allows you to read one line of input at a time. If you take a careful look at the MyInput class we have been using, you will find that it is using the readLine routine in the BufferedReader class.

Most devices, however, present a byte stream interface. For example
System.in is an InputStream.

If you have a byte stream but you want a Reader class so you can use a BufferedReader, then you want know about the conversion class:
InputStreamReader. Here is a snippet of code from the MyInput class that demonstrates the process:


    InputStream myInTemp = System.in;
    InputStreamReader myReader = new InputStreamReader(myInTemp);
    BufferedReader myIn = new BufferedReader(myReader);

    String str;
    str = myIn.readLine();



13. OutputStreamWriter class - The OutputStreamWriter class converts an OutputStream to a Writer stream. We will deal with this later.
14. MyInput class - We have been using the MyInput class, but we haven't pointed out some of its characteristics:

package tools;
import java.io.*;

public class MyInput {
// If a file is not passed in, we will open up a Buffered Reader
// created from System.in

   InputStreamReader instream = new InputStreamReader(System.in);
   BufferedReader in = new BufferedReader(instream);
   String fileName=null;

// read from keyboard
public MyInput()  {
   }

//read from file f
public MyInput(String f)
   {
     fileName = f;
     if (!open(fileName))
         System.out.println("Defaulting to reading from keyboard");
   }
public boolean open(String file)
   {
       try {
               if (file.length() > 0)
                   {
                       FileReader fr = new FileReader(file);
                       in =    new BufferedReader(fr);
                   }
               return true;
       }
       catch (FileNotFoundException e) {
           System.out.println("File Not Found Exception in get");
           return false;
       }
   }

public void close()
{
   try {
       in.close();
       }
   catch (IOException e){
       System.out.println("IO Exception on close call");
       }
}

protected void finalize()
{
    close();
}

public String get()
   {
       try {
               return in.readLine();
       }
       catch (FileNotFoundException e) {
           System.out.println("File Not Found Exception in get");
           return null;
       }
       catch(IOException e) {
           System.out.println("IO Exception in get");
           return null;
       }
   }

public int getInt()
{
    String str = get();
    if (str != null)
    {
       try {
            return Integer.parseInt(str);
            }
       catch (NumberFormatException e) { /* Do Nothing */}
    }
    System.out.println("Bad int input");
    return -1;
}

public static void main(String args[])  {
       MyInput input;
       String str;

       if (args.length > 0)
           input = new MyInput(args[0]);
       else
           input = new MyInput();

       do {
           str = input.get();
           if (str != null)
           {
               System.out.println(str);
                   if (str.equals("####"))
                       break;
           }
       } while (str != null);

       input.close();

   }
}


Exercises

1. Using MyInput and the StringTokenizer class, read in a file of text and print out each word discovered in the text. Now write the code without using the MyInput class.
2. Create a copy method that uses MyInput and MyOutput to copy one file to another. Demonstrate that it works. Also try to use some of the other approaches possible(not using MyInput, or MyOutput): Buffering classes and some reasonable size of buffer, Non-buffering with your own buffer, etc.
3. Create a program that calculates emplyee's pay for the week. You will have 2 input files and one output file. The first input file will contain employee names and hours worked: For example:
George 40
John 30
Fred 20

The second file contains hourly rate:
George 15.0
John 12.0
Fred 14.50

Your output file should contain total money earned:
George 800.0
John 630.0
Fred 360.0

If you want more of a challenge(not required), don't assume the 2 input files are in the same order. Now you need to read in one of the file completely and match records by employee names.
  - See if you can Sort the first file and use a binary search technique to find the appropriate records
  - Have your output sorted by Name
  - Have your output sorted by total pay
4. Create a program that uses the MyInput and MyOutput classes. Your program can take 0, 1, or 2 parameters.
  - If there are 0 parameters, read from System.in and write the output to System.out
  - If there is 1 parameter, read from the file specified from the parameter and write to System.out
  - If there are 2 parameters, then read from the file specified by the first parameter and write to the file specified by the second parameter.
To demonstrate that your program works, execute your program 3 times with 0, 1, and 2 parameters and show the output in your homework output listing.
5. Create a program that maintains test scores for a classroom. Your program will provide capabilities to read in test scores for the students in your classroom from a file. You can then update the scores by prompting your user. You can also printout a report and save the new data in a file.
You should consider using the StringTokenizer for this problem, and you should start with the following files:

Student.java


// The following needs to show up in file Student.java

public class Student
{
    private String name;
    // We can only handle upto 10 tests
    private int[] tests = new int[10];

    public String getName()
    {
        return name;
    }

    public void setName(String n)
    {
        // Update the name of the student
    }

    public int getTestAvg()
    {
        //Compute the average test score for this
        // Student.
        return 0;// Change this to the correct value
    }

    public int getTestScore(int index)
    {
       //Return the average test score
       return 0;// Change this to the correct value
    }

}
	

ClassList.java

public class ClassList
{
    private Student[] list = new Student[30];
    private int len; // number of student records used

    public void UpdateFromFile(String filename)
    {
        // Read in a file that looks like:
        // Name, Test1Score, Test2Score, ....
    }

    public void SaveToFile(String filename)
    {
        //Write out a comma delimited file to filename
    }

    public void UpdateRecords()
    {
        // Read from the keyboard and prompt your
        // user to enable him to update your records
    }

    public void PrintClassRecords()
    {
        // Printout a formatted list of data
    }

    public static void main(String[] args)
    {
       ClassList clist = new ClassList();
       String inputFile="ClassList.txt";

       if (args.length > 0)
            inputFile = args[0];

       // Initialize from file
       clist.UpdateFromFile(inputFile);

       // Manually update a few records
       clist.UpdateRecords();

       // Print out the records
       clist.PrintClassRecords();

       // We should be able to read back in the file
	   // written out here.
	   clist.SaveToFile("saveFile");
    }
}
	

ClassList.txt


John, 80, 90, 70, 65, 88
Sam, 20,   90, 99, 87, 66, 98, 95, 94
Ron, 88
Jim, 99, 98, 97, 96, 88, 77,55
Corky, 69, 80, 85, 77, 84
Jake, 80, 55, 90, 99, 93, 92
	

Last Updated: October 13, 2004 1:21 PM