CSC 012

Introduction to Computer Science

Summary of 12/6 Lecture

More on arrays;introduction to input file processing; FileReader and BufferedReader objects. (10.3, 10.4)

More on arrays

In the previous lecture, we introduced two-dimensional arrays or tables.  Multidimensional arrays are homogenous structures; that is, they store data of only one type.  No mixing and matching.  For example, it's possible to represent a table (2-dim array) of int's or a table of char's, but not a table with a mixture of int's and char's.   This is actually a feature of all arrays, one-dimensional as well as multidimensional.

Example.  Write a program that inputs and outputs a gradebook.   In particular, the user is asked for the number of students and quizzes respectively.  The scores are read in one by one and then a table with headings is output to the screen.

//File:    TestTable0.java
import iostuff.*;
public class TestTable0
{
    public static void main (String [] args)
    {
        int [] [] score = new int [30] [15];
       
        System.out.print ("How many students are enrolled? ");
        int students = Keyboard.readInt();
       
        System.out.print ("How many quizzes were given? ");
        int quizzes = Keyboard.readInt();
       
        for (int student = 0; student < students; student++)
        {
            System.out.println ("Enter scores for student " + (student+1));
            for (int quiz=0; quiz < quizzes; quiz++)
            {
                score [student] [quiz] = Keyboard.readInt();
            }
        }       
       
        //The following sets up the headers for the table
        System.out.println ("The gradebook is as follows: ");
        System.out.print ("Quiz\t");
        for (int k=1; k<=quizzes; k++)
        {
            System.out.print (k + "\t");
        }
        System.out.println();
        System.out.println();
       
//The following displays the table of scores in the gradebook
        for (int s=0; s<students; s++)
        {
            System.out.print (s+1);
            for (int q=0; q<quizzes; q++)
            {
                System.out.print ("\t" + score [s] [q]);
            }
            System.out.println();
        }
    }
}

You may remember that we collected several useful list processing methods into the NumberList class.  We could do the same here.  In particular, suppose we abstract the code for displaying the table into a class method.  The IntTable class actually has two such methods.   The first asks the user to supply (as parameters) the table as well as the number of rows and columns in the table.

//File:    IntTable.java
public class IntTable
{
    //The user supplies table (x) and number of rows and columns
    public static void display (int [] [] x, int rows, int cols)
    {
        for (int row=0; row<rows; row++)
        {
            for (int col=0; col<cols; col++)
            {
                System.out.print ("\t" + x [row] [col]);
            }
            System.out.println();
        }
    }
   
    //The following recognizes that a two-dimensional
    // array is just a one-dimensional array (rows)
    //of one-dimensional arrays (columns)
    public static void display (int [] [] x)
    {
        //x.length is the number of rows in the table
        for (int row=0; row<x.length; row++)
        {
            //x[row].length is the number of columns (for that row)
            for (int col=0; col<x[row].length; col++)
            {
                System.out.print ("\t" + x [row] [col]);
            }
            System.out.println();
        }
    }
}

The next version of our table testing program employs the "just in time" instantiation we used with one-dimensional arrays.  That is, we won't instantiate (allocate memory) until after we know how many students and quizzes are to be represented.

//File:    TestTable1.java
import iostuff.*;
public class TestTable1
{
    public static void main (String [] args)
    {
        int [] [] score;
       
        System.out.print ("How many students are enrolled? ");
        int students = Keyboard.readInt();
       
        System.out.print ("How many quizzes were given? ");
        int quizzes = Keyboard.readInt();
       
        //Instantiate here          
     score = new int [students] [quizzes];
       
        for (int student = 0; student < students; student++)
        {
            System.out.println ("Enter scores for student " + (student+1));
            for (int quiz=0; quiz < quizzes; quiz++)
            {
                score [student] [quiz] = Keyboard.readInt();
            }
        }
       
       
        //The following sets up the headers for the table
        System.out.println ("The gradebook is as follows: ");
        System.out.print ("Quiz\t");
        for (int k=1; k<=quizzes; k++)
        {
            System.out.print (k + "\t");
        }
        System.out.println();
        System.out.println();
       
        //Choose either of the display() methods from the IntArray class.
        //IntTable.display (score);
        IntTable.display (score, students, quizzes);
    }
}

Finally, recall that multidimensional arrays can be ragged. A two-dimensional array in java is just a one-dimensional array of one-dimensional arrays. The rows can be of variable length.  What follows is a new version of our TestTable program that follows that approach.

//File:    TestTable2.java
import iostuff.*;
public class TestTable2
{
    public static void main (String [] args)
    {
        int [] [] score;
       
        System.out.print ("How many students are enrolled? ");
        int students = Keyboard.readInt();
       
        /* EACH STUDENT HAS DIFFERENT NUMBER OF QUIZZES
        System.out.print ("How many quizzes were given? ");
        int quizzes = Keyboard.readInt();
        */
       
        //CREATE AN ARRAY OF ARRAYS
        score = new int [students] [];
       
        //NEED TO KEEP TRACK OF LARGEST NUMBER OF QUIZZES TAKEN
        //IN ORDER TO SET UP TITLE FOR DISPLAYED TABLE
        int maximum_quizzes = 0;
        for (int student = 0; student < students; student++)
        {
            System.out.println ("How many scores for student "
            + (student+1) + "?");
            int number_of_quizzes = Keyboard.readInt();
           
            //UPDATE MAXIMUM_QUIZZES
            if (number_of_quizzes > maximum_quizzes)
                maximum_quizzes = number_of_quizzes;
           
            //INITIALIZE ARRAY OF QUIZZES FOR THIS STUDENT
            score[student] =new int [number_of_quizzes];
           
            System.out.println ("Enter scores for student " + (student+1));
            for (int quiz=0; quiz<number_of_quizzes; quiz++)
            {
                score [student] [quiz] = Keyboard.readInt();
            }
        }
       
        System.out.println ("The gradebook is as follows: ");
        System.out.print ("Quiz\t");
       
        for (int k=1; k<=maximum_quizzes; k++)
        {
            System.out.print (k + "\t");
        }
        System.out.println();
        System.out.println();
       
        IntTable.display (score);
    }
}

>java TestTable2
How many students are enrolled? 2
How many scores for student 1?
2
Enter scores for student 1
98
100
How many scores for student 2?
3
Enter scores for student 2
60
89
91

The gradebook is as follows:
Quiz     1         2            3

            98       100
            60        89         91

Introduction to input file processing

Let us look at the details behind I/O (Input/Output) in Java.  We've been using the text authors' Keyboard class for inputting data from the keyboard.  The details of the readInt() method from that class are displayed in all their glory in the following:

    static boolean iseof = false;
    static int i;
    static String line;

    public static int readInt ()
    {
        if (iseof) return 0;
        System.out.flush();
        try
        {
            InputStreamReader istr = new InputStreamReader(System.in);
            BufferedReader input = new BufferedReader (istr);
            line = input.readLine();
        }
        catch (IOException e)
        {
            System.exit(-1);
        }
       
        if (line==null)
        {
            iseof=true;
            return 0;
        }
       
        i = Integer.parseInt (line);
//         i = new Integer(s.trim()).intValue();     EQUIVALENT
        return i;
    }

It's perhaps wise to take a step back and try to get a view of the big picture.  Input is accomplished in Java through the use of input streams.  Until now, we have been content to use an input stream attached to the standard input stream (the keyboard).  We want to expand our options to include input streams attached to files.  In Java, files are viewed as byte streams ending with an end-of-file marker.   So the first task is to convert the byte streams to characters.  The character stream is then buffered.  From this buffer, each line of input can be extracted as a String.  Finally, the String object can be parsed to yield the data type required.   Let's indicate this process as follows:

  1. file (stream of bytes)

  2. FileReader object (stream of chars)

  3. BufferedReader object (buffers input allowing access from RAM instead of file)

  4. readLine() message to Buffered Reader object returns String reference to first line of input

  5. parse String object to extract data of appropriate data type.

In the case of input from the keyboard, these steps are implemented as:

  1. System.in object attached to input stream from keyboard.

  2. InputStreamReader istr = new InputStreamReader(System.in);

  3. BufferedReader input = new BufferedReader (istr);

  4. line = input.readLine();

  5. i = Integer.parseInt (line);

Input Files

An exactly parallel development applies to input from files.  We would have the following steps:

  1. String fileName associated with text file.

  2. FileReader fr = new FileReader (fileName);

  3. BufferedReader inFile = new BufferedReader (fr);

  4. line = inFile.readLine();

  5. i = Integer.parseInt (line);

The only difference appears in step 2, where for files, a FileReader object is required rather than an InputStreamReader object.

Putting all of these ideas together, we were able to construct a method that reads a list of integers from a data file.

    //File:    IntFiles.java
    public static int readIn (String fileName, int [] list)
    {
        int count=0;
        try
        {
        //The FileReader converts byte stream to char stream
        FileReader fr = new FileReader (fileName);

        //The BufferedReader enables efficient buffering of stream
        BufferedReader inFile = new BufferedReader (fr);
        String line = inFile.readLine(); //readLine() is in BufferedReader class
           
        while (line != null)     //null is the pointer
               //that points nowhere--when no more lines in file, line = null.
        {
        list [count] = Integer.parseInt (line);
        count++;
        line = inFile.readLine();
        }
           
                    inFile.close(); //not invoked if throws exception
        }
        catch (FileNotFoundException e)
        {
            System.out.println ("The file " + fileName + " was not found.");
        }
        catch (IOException e)
        {
        }
       
        return count;
    }

Where did that try and catch thing come from?  The construction of FileReader object can result in an error (called an I/O exception in Java).  For example, suppose the file doesn't exist?  Rather than accepting this state of affairs, Java requires the programmer to indicate awareness of the potential disaster and provide a response.  This is accomplished through the use of try-catch clauses.  The potentially offending code is enclosed in the try block while the response code is enclosed in a catch block.  Note that it is not required that the program actually respond to the exception, merely indicate that the programmer is aware of the possibility.  In the above example, should an IOException, e, be "thrown", our catch block is empty indicating that our program will take no action.

We will continue this next time, but you now should have enough to take a stab at combining what you have learned about arrays and the method to read integers.


Lab Exercise.  Implement one of the sorting algorithms we have discussed (Bubble sort, Shell Sort, or Quick Sort) and add it to our collection of list processing routines (NumberList.java). Test it first with data input from the keyboard. Then test it on a data file on disk that you make with notepad.


Back to CSC 012 Home Page