File I/O
[Framework practical 5 of 7]
Now we're going to read some data in.
First we're going to need to import some classes. Specifically classes from within the
java.io
and java.util
packages. Using your notes on the lecture about using others' code, import
everything from these packages into IO. Compile your files to check the packages have been recognised.
Now we're going to put the code to read the file into the readData method of the IO class.
First, we need to tell the method which file to read. For now, we're going to hardwire this in - this will save us continually having to open the file with a FileDialog while we do development.
public double[][] readData() {
File f = new File("in.txt");
return new double[][] {};
}
Again, compile the file to check it. Note that we have't wired in the whole filepath, just the filename. This is fine -- java will look for the file in the directory that the application starts from. With unpackaged files, this is the directory the .class files are in. With packages, it is the directory that contains the package hierarchy. With executable jars (which we'll look at later), it is the directory the jar runs from. With an IDE, which often alter classpaths quite a lot, you'll need to write something out to find where it considers the current directory.
Now, after this we need the code from the the input/output lecture that:
1) Opens a FileReader on the file, f, above (but doesn't use this directly to read).
2) Opens a BufferedReader on the FileReader
3) Uses this to read data into an array of Strings, each line in the file being a single String in the array.
Ultimately, once we've finished this bit, readData will look something like this:
public double[][] readData() {
File f = new File("in.txt");
// Set up a FileReader (must be in a try-catch block).
// Wrap this in a BufferedReader
// Declare any variables needed below.
// Open a try block
// Read though counting the lines in the file.
// Make an appropriately sized 1D String array.
// Close the buffer and make a new FileReader and BufferedReader
// (you can use the same labels).
// Loop, copying file lines into the array, one line per cell.
// Close the Buffer (and thus the Reader as well).
// Close try-catch block.
}
Note: you *must* close the buffer and remake the FileReader and buffer after you've read through the file once, otherwise you'll just read blanks spaces off the bottom of the file. This isn't detailed in the lecture notes; we've left it for you to work out!
You'll need a couple of bits from the lecture. First, the bit that makes a FileReader and then the bit that wraps a BufferedReader around it and reads data into a 1D String array. Find these bits of code and copy them into the readData method as above.
It is worth a quick note here on exception-handling. The above outline includes a number of things that need try-catch blocks around them. In general you might as well let the compiler tell you when these are needed. Usually you also try to keep the blocks tight around the code generating them, so separate specific exceptions are caught by separate specific try-catch blocks. However, in the above description I've suggested putting a try-catch block around several things that need one for the same type of exception (reading lines, closing the FileReader). This is mainly to make it easier for you. Ultimately you might want to split these up, though in this case if one thing works, chances are the other will. If you're not sure of which exception is thrown by the code, you can find this information in the API docs; however, the compiler will also tell you if you leave out the try-catch blocks initially.
Once you've written the code, can you work out a way to test it is working? (hint, as we can't yet return a proper 2D double array you'll need to change the return type at least if you want to test it from Analyst, but you might be better testing it in IO).
Next we need to parse the 1D String array into a 2D double array.
Again, the code for doing this with
a StringTokenizer is in the lecture notes. The end of our readData
method will look something like this:
// Run through the array and use a StringTokenizer
// to parse the data into a double[][] array.
// return the full double[][] array rather than an empty one.
Note: you *must* return the data array, and delete the old return statement that returned an empty array.
Once you've done that, you should be in a position to return the double 2D array and test the method fully.
Once you're sure your code is working, we need to make sure the data is stored properly in our Storage class. Ultimately all our Analyst class constructor will do is this:
public Analyst () {
Storage store = new Storage();
IO io = new IO();
double[][] a = io.readData();
store.setData(a);
}
If you don't already have a setData()
method in Storage, here's the set and get which would go in the Storage class:
public void setData(double[][] dataIn) {
data = dataIn;
}
public double[][] getData () {
return data;
}
Note in our Analyst class that we don't need to know how large the array being drawing into Analyst is - we just attach the label "a" to whatever readData() gives us. We can actually do the above with less code:
public Analyst () {
Storage store = new Storage();
IO io = new IO();
store.setData(io.readData());
}
Add one of these sets of code to your Analyst class and test the code to make sure it is working ok.
NB: If it comes back complaining of a null pointer exception in using the StringTokenizer, check you've closed the buffer and re-opened both the FileReader and the BufferedReader where you need to re-set the file to the start, above. If you don't do this, the code to read the file will just count to the end of the file, then start reading code from that point off the end of the file, rather than restarting from the beginning. If this happens, your StringTokenizer will just be full of nulls.
Next, go on to Part Three, where we'll write out the data.