Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
COMP2059 - Sun Certified Java Programmer Overview of Java I/O I/O in Java is built on streams. Input streams read data. Output streams write data. Different stream classes read and write particular sources of data but all fundamental output streams have the same basic methods to write data and all fundamental input streams have the same basic methods to read data. Once a stream has been created, you can ignore the details of exactly what it is you are reading or writing. Filter streams can be chained to either an input stream or an output stream. These can modify the data as it is read or written, eg by encrypting or compressing it, or they may provide additional methods for converting the data that's read or written to other formats. Readers and writers can be chained to input and output streams to allow programs to read or write text, ie characters, rather than bytes. Readers and writers can handle a wide variety of character encodings, not just the default Java character set Unicode. Files in Java (p429-433) Files are used for persistent storage of data. The Java File class is designed to provide programs with a mechanism for interacting with the file system of a computer. The actual reading and writing of data though, is accomplished by streams. A class diagram for the File class is shown with the most commonly used methods: File - String filename <<constructor>> + File(String) ... <<query>> + boolean canRead() + boolean canWrite() + boolean exists() + String getAbsolutePath() + boolean isFile() + boolean isDirectory() + boolean length() ... <<update>> + boolean createNewFile() + boolean delete() + void deleteOnExit() + boolean mkdir() ... Example - Displaying Information about a File 01 02 03 04 import java.io.*; public class FileInfo { public static void main(String[] args) { 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 String filename = "readme.txt"; if (args.length > 0) filename = args[0]; File f = new File(filename); if (f.exists()) { System.out.println("File name: " + f.getName()); System.out.println("File path: " + f.getAbsolutePath()); System.out.println("File length: " + f.length()); if (f.isFile()) System.out.println("File is normal"); if (f.isHidden()) System.out.println("File is hidden"); else System.out.println("File is not hidden"); if (f.isDirectory()) System.out.println("File is a directory"); if (f.canRead()) System.out.println("File is readable"); else System.out.println("File is not readable"); if (f.canWrite()) System.out.println("File is writable"); else System.out.println("File is not writable"); } else System.out.println("File does not exist"); } } The above code will display information about a file in the current directory. The filename is passed as a command line argument else it will look for the file readme.txt. Note that since the file hasn't been opened there is no need (nor is it possible) to close it. Example - Creating a New File 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 import java.io.*; public class CreateFile { public static void main(String[] args) { String filename = "readme.txt"; if (args.length > 0) filename = args[0]; File f = new File(filename); if (f.exists()) { System.out.println("File already exists"); } else { try { if (f.createNewFile()) { System.out.println(filename + " created"); } } catch (IOException e) {} } } } It is possible that a new file can't be created, eg the directory is read-only, in this case the createNewFile method will throw an IOException. This is handled in the above with a try...catch. Again the file hasn't actually been opened hence needn't be (actually can't be) closed. Accessing Text Files (p433-443) A file that contains exclusively char data is said to be a text file, and can be created and modified by text editor programs, eg Notepad, Textpad. The java.io package includes several classes to manipulate text files, many of these use the concept of reader and writer objects to massage stream data. In order to write to a file a program could create two objects - a writer object and an output stream; the actual output methods are performed on the writer. Similarly, a program can input from a file by way of a reader object that is connected to an input stream object. Executing Program OutputStreamWriter FileOutputStream File InputStreamReader FileInputStream File ProgramProgram Executing Program The file is opened for writing (and if necessary created) when the FileOutPutStream is instantiated. The file is opened for reading when the FileInputStream is instantiated. The OutputStreamWriter is a character writer for the underlying output stream. The InputStreamReader is a character reader for the underlying input stream. If this is all getting to be a bit complicated help is at hand in the form of the FileWriter and FileReader classes. These classes write/read text files using the platform's default character encoding. If all you want to do is write/read characters and strings to/from the file these classes will do the job by themselves. Example - Writing Characters and Strings to a Text File 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 import java.io.*; public class TextWrite { public static void main(String[] args) { try { FileWriter fW = new FileWriter("sample.txt"); BufferedWriter bW = new BufferedWriter(fW); bW.write('Z'); bW.write("\r\n"); bW.write("Fred Flintstone"); bW.write("\r\n"); bW.flush(); bW.close(); } catch(IOException e) { System.out.println("Error in writing"); } } } When the FileWriter object fW is instantiated, the file sample.txt is opened for writing. If it already existed all previous contents are deleted; if it didn't exist it is created. The next line chains a BufferedWriter object bW onto fW. The application would work without the BufferedWriter object but would be less efficient. BufferedWriter provides a character buffer that stores text being written until the buffer fills up or is flushed, only then is all the text written to the underlying stream. This reduces the number of separate writes to the underlying stream and hence file and so increases efficiency. Note that it is the methods of BufferedWriter that are used to do the writing. An abridged class diagram for BufferedWriter is shown: BufferedWriter <<constructor>> + BufferedWriter(Writer) ... <<update>> + void write(int c) + void write(String str) + void newLine() + void flush() + void close() ... Note that the newLine() method writes a platform-dependent line terminator string: \n on Unix, \r on the MAC, and \r\n on Windows. This method should not be used when writing network code, instead explicitly write the carriage return / line feed pair as has been done in the TextWrite class above. Example - Reading from a Text File In a similar way to writing two classes are used to read from a text file. These are the FileReader class and the BufferedReader class, though it is the methods of the BufferedReader class that are used to do the reading. We will look at two ways of reading a text file, either line at a time or character at a time; firstly line at a time:01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 import java.io.*; public class LineRead { public static void main(String[] args) { try { String inStr; FileReader fR = new FileReader("sample.txt"); BufferedReader bR = new BufferedReader(fR); inStr = bR.readLine(); while (inStr != null) { System.out.println(inStr); inStr = bR.readLine(); } bR.close(); } catch(IOException e) { System.out.println("Error in reading"); } } } Note that the String returned by readLine() does not include the end of line character ('\n'), the end of line is consumed by the method call. When readLine() is called after reaching the end of the file, null is returned. Similarly reading the file character at a time can also be done:01 02 03 04 05 06 07 08 09 10 11 12 13 14 import java.io.*; public class CharRead { public static void main(String[] args) { try { int inInt; char inChar; FileReader fR = new FileReader("sample.txt"); BufferedReader bR = new BufferedReader(fR); inInt = bR.read(); while (inInt != -1) { inChar = (char)inInt; System.out.print(inChar); inInt = bR.read(); 15 16 17 18 19 20 21 } bR.close(); } catch(IOException e) { System.out.println("Error in reading"); } } } In order to read a file Character at a time, BufferedReader provides a method called read. The read method is a bit unusual because it returns an int value rather than a char. A cast is required before the read method's value can be used as a char. Another characteristic of the read method is that it returns the value -1 to indicate any attempt to read past the end of file. The most commonly used methods of the BufferedReader class are shown in the following class diagram: BufferedReader <<constructor>> + BufferedReader(Reader) ... <<query>> + int read() + String readLine() ... <<update>> + void close() Example - Writing Formatted Data to a Text File 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 import java.io.*; public class FormatWrite { public static void main(String[] args) { try { PrintWriter pW = new PrintWriter(“sample.txt”); pW.print(12); pW.println(true); pW.print(98.7); pW.println("hello World"); pW.print('Z'); pW.flush(); pW.close(); } catch(IOException e) { System.out.println("Error in writing"); } } } As it is not just characters and strings that are being written to the file here, the FileWriter class can't be used. The code here uses PrintWriter. Use a text editor to check what is written to the file, if the file "sample.txt" already exists, it will be overwritten. The most commonly used methods of the PrintWriter class are shown in the following class diagram: PrintWriter <<constructor>> + PrintWriter(OutputStream) + PrintWriter(String) ... <<update>> + void close() + void flush() + void print(boolean) + void print(char) + void print(double) + void print(float) + void print(int) + void print(long) + void print(Object) + void print(String) + void println() + void println(boolean) + void println(char) + void println(double) + void println(float) + void println(int) + void println(long) + void println(Object) + void println(String) ... The PrintWriter class allow objects of different types to be converted to text and written to a text file. This is similar to the formatted output provided by the printf statement in C. The println() method of the PrintWriter class should not be used in networking code for the line separator problem described earlier. Larger Example Consider a program to read a text file and output the most used letter (or letters). One strategy for writing this program is to build a class into which all the letters in the file are entered and stored in a form that can easily be processed. The LetterData class defined as follows does this with an array that has an element for each letter of the alphabet in which the number of times that letter occurs is stored: LetterData - int[] data <<constructor>> + LetterData() <<update>> + void addLetter(int) <<query>> + int getMaxNo() + String getMaxLetters() The code to implement this is shown: 01 02 public class LetterData { private int[] data; 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public LetterData() { data = new int[26]; for (int n = 0; n <= 25; n++) data[n] = 0; } public void addLetter(int letter) { if ((letter >= 'A') && (letter <= 'Z')) data[letter - 'A']++; if ((letter >= 'a') && (letter <= 'z')) data[letter - 'a']++; } public int getMaxNo() { int max = 0; for (int n = 0; n <= 25; n++) { if (data[n] > max) max = data[n]; } return max; } public String getMaxLetters() { int max_count; String outString = ""; int max = getMaxNo(); if (max != 0) { max_count = 0; for (int n = 0; n <= 25; n++) { if (data[n] == max) { outString += ((char)(n + 'A')) + " "; max_count++; } } if (max_count > 1) outString += "are the most used letters"; else outString += "is the most used letter"; } else outString += "The file contained no letters"; return outString; } } A LetterData object can thus be instantiated, the file read one character at a time, the characters entered into the LetterData object using the addLetter method, and the most used letter found with the getMaxLetters method. An application which does this (using a file called "sample.txt" as input) and outputs the result to the console is shown: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 import java.io.*; public class MostLetters { public static void main(String[] args) { LetterData store = new LetterData(); try { FileReader fR; BufferedReader bR; fR = new FileReader("sample.txt"); bR = new BufferedReader(fR); int inInt; inInt = bR.read(); while (inInt != -1) { store.addLetter(inInt); inInt = bR.read(); } } catch(IOException e) { System.out.println("Error in reading input file"); } System.out.println(store.getMaxLetters()); 21 22 } } Exercises (1) Modify the above example to output the results to a file called results.txt rather than the console. (2) Add a method to the LetterData class called getLetterList() which returns a string which is a list of all the letters used in the file. Use this to add to what is written to the results.txt file. (3) Add a method to the LetterData class called getLetterNumber(char ch) which returns the number of instances in the file of the character passed to it. Use this to add a histogram (constructed with '*' characters) of the instances of all the letters to what is written to the results.txt file. Serialization (p433-457) Serialization allows us to write entire objects to a file and read them back at a later time. This is really powerful as large amounts of data can be stored with very little effort from the programmer. Example - Serializing an Object First we need an object to serialize. To get that object we need a class. We will use the simple Person class shown here: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 import java.io.*; class Person implements Serializable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } Note that the class implements the Serializable interface. This is just a marker interface so it doesn't require any methods to be implemented. Now we need a class that writes a Person object to a file: 01 02 03 04 05 import java.io.*; public class Write { public static void main(String[] args) { try { 06 07 08 09 10 11 12 13 Person p = new Person("Doris", 32); FileOutputStream fos = new FileOutputStream("serialized.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(p); oos.close(); } catch (Exception e) {} } } A Person object is created in line 6. A FileOutputStream and a file called "serialized.obj" are created in line 7. The FileOutputStream is wrapped in a ObjectOutput stream in line 8. Line 9 is where the big action happens! We call the writeObject method on the ObjectOutputStream and pass in our Person object. This passes our Person object through the ObjectOutputStream, then through the FileOutputStream, and writes it to the file. Line 10 closes the ObjectOutputStream, which closes the FileOutputStream, which closes the file. All the code is in a try block because working with streams can throw up a variety of exceptions. Running the Write application will create the "serialized.obj" file. For interest open this in a text editor. It is not a text file but you should recognise the odd string in there. Now we need a Read application to get our object back out of the file: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 import java.io.*; class Read { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("serialized.obj"); ObjectInputStream ois = new ObjectInputStream(fis); Person newP = (Person)ois.readObject(); ois.close(); System.out.println(newP.getName() + " is " + newP.getAge() + " years old"); } catch (Exception e) { System.out.println("problem"); } } } Line 6 creates a FileInputStream and connects it to the "serialized.obj" file. Line 7 wraps the FileInputStream in an ObjectInputStream. Line 8 is where the big action happens in this class. An object is read from the ObjectInputStream. However this comes out as a reference to a type Object, thus we do the cast to a type Person. Line 9 closes the streams and file. Lines 10 - 13 output the results. As an exercise, see what happens when you mark the age instance variable of the Person class as transient. Remember to recompile the Person class before using it again. Example - Manual Serialization (p449-452) Say, for example, the age variable in the Person class above was declared transient but you didn't want it to comeback as a default value on de-serialization. There is a way of manually saving the state of transient variables. This involves making the otherwise serializable class implement writeObject and readObject methods. In our case the Person class becomes: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.io.*; class Person implements Serializable { private String name; transient private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } private void writeObject(ObjectOutputStream os) { try { os.defaultWriteObject(); os.writeInt(age); } catch (Exception e) {} } private void readObject(ObjectInputStream is) { try { is.defaultReadObject(); age = is.readInt(); } catch (Exception e) {} } } We don't need to make any changes to the Read and Write classes. In true object-oriented fashion the Person class now knows how to manually serialize its unserializable (transient) bits. The writeObject method of lines 20 - 25 specifies the serialization process. First the serializable parts of the object are serialized by calling the defaultWriteObject method, then the age is written to the stream. ObjectOutputStream has a whole set of methods for writing primitive values (see JavaDocs). Similarly the readObject method in lines 27 - 32 specifies the de-serialization process.