More on arrays;introduction to input file processing; FileReader and BufferedReader objects. (10.3, 10.4)
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
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:
file (stream of bytes)
FileReader object (stream of chars)
BufferedReader object (buffers input allowing access from RAM instead of file)
readLine() message to Buffered Reader object returns String reference to first line of input
parse String object to extract data of appropriate data type.
In the case of input from the keyboard, these steps are implemented as:
System.in object attached to input stream from keyboard.
InputStreamReader istr = new InputStreamReader(System.in);
BufferedReader input = new BufferedReader (istr);
line = input.readLine();
i = Integer.parseInt (line);
An exactly parallel development applies to input from files. We would have the following steps:
String fileName associated with text file.
FileReader fr = new FileReader (fileName);
BufferedReader inFile = new BufferedReader (fr);
line = inFile.readLine();
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.