Writing and Reading Data


At this point, you might be asking yourself, "When would I ever want to write or read bytes?" After all, one of the huge disadvantages of SimCom, as compared to the JVM, is that it deals only in bytes, while Java supports eight primitive data types and limitless class types.

The answer, fortunately, is that you never have to write or read bytes if you don't want to. You still have to create file input and output streams, and you still have to close them when you're done using them, but you don't have to write to them or read from them. Not directly, anyway. The writing and reading can be done by two very useful classes in the java.io package:

  • DataOutputStream

  • DataInputStream

The constructor for DataOutputStream takes a single argument. This argument is not the name of a file. Instead, it is a reference to a file output stream. Data written to a data output stream gets chopped up into bytes, which the data output stream passes to its file output stream. The technique of connecting streams together is called chaining. Figure 13.4 shows a data output stream chained onto a file output stream.


Figure 13.4: Output chaining

DataOutputStream has a large number of methods that chop up data and deliver bytes to the next stream in the chain. Here we will discuss nine of these methods:

  • writeBoolean(boolean boo)

  • writeByte(int b)

  • writeShort(int s)

  • writeChar(int c)

  • writeInt(int i)

  • writeLong(long n)

  • writeFloat(float f)

  • writeDouble(double d)

  • writeUTF(String s)

It is obvious what the first eight methods do: They convert their primitive arguments into bytes. (It's surprising that writeByte(), writeShort(), and writeChar() take int args rather than the corresponding primitive types. That's just how it is.) What about UTF? Recall that Java's char type uses Unicode encoding. So a Java string is a run of Unicode characters. UTF is a standard for converting Unicode strings into bytes. Thanks to the writeUTF() method, you can use a data output stream to write any of Java's eight primitives, as well as any string. This is illustrated in the following application.

The following code chains a data output stream onto a file output stream, and then it writes one of each primitive type as well as one string:

import java.io.*;         public class WriteWithChain {   public static void main(String[] args)   {     boolean boo = true;     byte b = 12;     short sh = 12345;     char c = 'M';     int i = -654321;     long n = 12341234;     float f = 15;     double d = 1.23e88;     String s = "Where the devil did that dragon come from?";     try     {       FileOutputStream fos;       DataOutputStream dos;       fos = new FileOutputStream("abc");       dos = new DataOutputStream(fos);       dos.writeBoolean(boo);       dos.writeByte(b);       dos.writeShort(sh);       dos.writeChar(c);       dos.writeInt(i);       dos.writeLong(n);       dos.writeFloat(f);       dos.writeDouble(d);       dos.writeUTF(s);       dos.close();       fos.close();     }     catch (IOException x)     {       System.out.println("Caught IOException");     }   } }

Note the two close() calls at the end of the try block. Every input and output stream should be closed after use. A good rule of thumb is to close chained streams in the opposite

order from their creation. Since the file output stream was constructed before the data output stream, close the data output stream first and the file output stream second.

Now you know how to write data to a file, so it is time to learn how to read data from a file. Again, you will chain a high-level stream onto a stream that communicates with a file. But this time, you will chain a data input stream onto a file input stream. Figure 13.5 shows this arrangement.


Figure 13.5: Input chaining

The DataInputStream class reads bytes from a lower-level stream, such as a file input stream has nine reading methods that correspond to the nine writing methods of DataOutputStream:

  • readBoolean()

  • readByte()

  • readShort()

  • readChar()

  • readInt()

  • readLong()

  • readFloat()

  • readDouble()

  • readUTF()

These methods take no arguments. Their return types correspond to their names: boolean for readBoolean(), byte for readByte(), and so on. readUTF() returns a string. When any of these calls are made, the data input stream gets the appropriate number of bytes from its lower-level stream and assembles them to create the appropriate return value.

Now you can read the file by chaining a data input stream onto a file input stream:

import java.io.*; public class ReadWithChain {   public static void main(String[] args)   {     try     {       FileInputStream fis;       DataInputStream dis;       fis = new FileInputStream("abc");       dis = new DataInputStream(fis);       boolean boo = dis.readBoolean();       System.out.println("Read boolean: " + boo);       byte b = dis.readByte();       System.out.println("Read byte: " + b);       short sh = dis.readShort();       System.out.println("Read short: " + sh);       char c = dis.readChar();       System.out.println("Read char: " + c);       int i = dis.readInt();       System.out.println("Read int: " + i);       long n = dis.readLong();       System.out.println("Read long: " + n);       float f = dis.readFloat();       System.out.println("Read float: " + f);       double d = dis.readDouble();       System.out.println("Read double: " + d);       String s = dis.readUTF();       System.out.println("Read string: " + s);       dis.close();       fis.close();     }     catch (IOException x)     {       System.out.println("Caught IOException");     }   } } 

This application's output is

Read boolean: true Read byte: 12 Read short: 12345 Read char: M Read int: -654321 Read long: 12341234 Read float: 15.0 Read double: 1.23E88 Read string: Where the devil did that dragon come from?

Obviously, this output reflects the data that was originally written to the file in the previous example. The two applications work together because the reading code reads exactly the same types, in exactly the same order, as were written by the writing code. Whenever you read from a file that was created with a data output stream, your read calls have to correspond exactly to the write calls that created the file. Otherwise, your data will be garbled beyond all recognition.

For example, suppose you mistakenly called readLong() instead of readInt(). The data input stream would grab the next eight bytes so that it could build a long. Those eight bytes would be the four-byte int (which is the next item of data in the file), and the first four bytes of the eight-byte long (which follows the int in the file).

The Data Chain Lab animated illustration demonstrates code that first writes three pieces of data to a file, and then reads them back. To run the application, type "java io.DataChainLab". Figure 13.6 shows the display.

click to expand
Figure 13.6: Data Chain Lab

In the three lines that write data, you will see pull-down choices for configuring which data type to write out. You can choose from any of the seven methods that write numerical types. You can also choose the values to be written. When you change the type being written, the corresponding reading code changes as well. This is in keeping with the rule that the type that is written must match the type that is read.

When you're ready, click the "Run" button to view the animation. If you want to run it again, perhaps with different output methods or values, first click "Reset". Then choose new methods and values, and click "Run" again.

Figure 13.7 shows Data Chain Lab in progress. It has been configured to write and then read a byte, a long, and a double.

click to expand
Figure 13.7: Data Chain Lab in progress: Text, writers, and readers

File output streams and file input streams are used for files that contain raw bytes. Data output streams and data input streams are used for files whose bytes represent multibyte data. There is a third kind of file, whose bytes represent text. For access to text files, Java provides classes called readers and writers.

Before presenting readers and writers, we should take a moment to explain what is meant by "text file". Recall that Java uses the modern Unicode scheme to represent text, using two bytes per character. This means there are 65,536 possible characters that can be represented. That's more than enough to represent every character of every language on the planet... until you consider Chinese, Japanese, and Korean. These non-phonetic alphabets have huge numbers of characters, enough to consume all 65,536 bit combinations. The international Unicode Consortium decides which characters of which languages will be represented by which bit combinations.

Well, that's the modern way to represent characters. It doesn't seem quite modern enough when you think about those Chinese, Japanese, and Korean symbols that get left out, but it's better than what we had before. The old way of doing things, from the invention of computers through the introduction of Unicode, was to use 8-bit characters. Every language group was on its own to decide which of the 256 possible bit combinations would represent which character. Most files created during that time used an encoding called ASCII, which stands for "American Standard Code for Information Interchange". ASCII encodes all the characters in American English, plus punctuation marks, into the range 0-127. The range 128-255 encodes symbols such as accented vowels, which are used in western European languages, as well as some Greek characters, line-drawing symbols, and some others. All of the characters that are represented in ASCII are represented in Unicode.

So here's the situation today: Within the JVM, characters are represented by Unicode. But in the world in general, there are millions of text files that use ASCII or other 8-bit representations. So Java needs a way to read those files and present their contents as Unicode strings. Also, Java needs a way to write ASCII files (as well as other 8-bit formats), because files can be read by non-Java programs that don't know about Unicode. Note that the problem cannot be solved by using data input and output streams that do lots of readUTF() and writeUTF() calls, because UTF is compressed Unicode, not ASCII.

Readers and writers solve the problem of translating between 16-bit characters within a JVM and 8-bit characters in text files. Figure 13.8 illustrates the roles of readers and writers.

click to expand
Figure 13.8: Readers and writers

Reader is an abstract class that reads 8-bit text and delivers Unicode chars. Writer is an abstract class that reads Unicode chars and delivers 8-bit text. For our purposes, the two most important subclasses of these two classes are FileReader and FileWriter, which read and write 8-bit text files.

A FileWriter is a lot like a FileOutputStream. You construct one, passing as an argument the name of the file you want to make. Then you write, and when you have finished, you close the FileWriter. This all must happen in a try block, because the code can throw IOException and some of its subclasses. The following code writes two lines of text to a file called abc.txt:

1. try 2. { 3.   FileWriter fw = new FileWriter("abc.txt"); 4.   fw.write("Hello\n"); 5.   fw.write("Goodbye\n"); 6.   fw.close(); 7. } 8. catch (IOException x) {}

Line 4 writes "Hello", followed by a newline character. Line 5 writes "Goodbye", followed by a newline character. Note that the newline is not automatic (as it is in the System.out.println() call, for example). If you want multiple lines of text, you have to indicate the line breaks yourself. So the following code creates an identical file:

1. try 2. { 3.   FileWriter fw = new FileWriter("abc.txt"); 4.   fw.write("Hello\nGoodbye\n"); 5.   fw.close(); 6. } 7. catch (IOException x) {}

There are three common ways to indicate that a line has ended and a new line has begun:

  • A return character ('\r')

  • A newline character ('\n')

  • A return character followed by a newline character

Which should you use? It depends on which other programs will be reading the file you create. Programs that run on Windows platforms expect a return character followed by a newline character. If you are creating files for Windows, you should do something like the following:

1. try 2. { 3.   FileWriter fw = new FileWriter("abc.txt"); 4.   fw.write("Hello\r\nGoodbye\r\n"); 5.   fw.close(); 6. } 7. catch (IOException x) {}

If you run this code on a Windows machine and then double-click on the icon for the abc.txt file, Windows will open a Notepad window that displays (and lets you edit) the file. You can also open the file with any other program that reads text files, including Word.

You can read text files with the FileReader class, but this class is a bit limited. It is much easier to use the LineNumberReader class, where the readLine() method reads lines of text and returns strings. (This assumes that your text file has multiple lines, and that reading line by line will be useful to you. This is a safe assumption.) A call to readLine() reads one line from the input file. A line is a run of text, terminated by either a return character, a newline character, or a return character followed by a newline character. The line-termination characters are not part of the returned string.

A line number reader does not directly read from the input file. Rather, it is chained onto a file reader, in the same way a data input stream is chained onto a file input stream. Figure 13.9 shows the relationship between a line number reader and a file reader.


Figure 13.9: Line number reader and file reader

The following code reads and prints out the first two line of a character file:

try {   FileReader fr = new FileReader("zzz.txt");   LineNumberReader lnr = new LineNumberReader(fr);   System.out.println(lnr.readLine());   System.out.println(lnr.readLine());   lnr.close();   fr.close(); } catch (IOException x) { }

The LineNumberReader class keeps track of the number of lines it has read. You can retrieve the current line number by calling getLineNumber().

The readline() method returns null if the end of the input file has been reached. So the following code prints out the line number of all lines in file input.txt that contain the word "purple":

 1. try  2. {  3.   FileReader fr = new FileReader("input.txt");  4.   LineNumberReader lnr = new LineNumberReader(fr);  5.   String s = "";  6.   while (s != null)  7.   {  8.     s = lnr.readLine();  9.     if (s != null  &&  s.indexOf("purple") != -1) 10.       System.out.println("Found \"purple\" at line " + 11.                          lnr.getLineNumber()); 12.   } 13.   lnr.close(); 14.   fr.close(); 15. } 16. catch (IOException x) { } 

The while loop runs as long as s is not null (that is, as long as the end of the file has not been reached). So s has to be initialized to anything besides null so that the loop will not immediately terminate. Line 9 calls indexOf() on the string returned by the line number reader. This method returns the position (in the string on which the method was called) of the string that is the method's argument. For example, if s in line 9 is "A ferocious purple dragon", the indexOf() call will return 12. If the argument string does not appear at all, indexOf() returns -1. So the condition in line 9 evaluates to true when the reader has not yet reached the end of the file, and the string just read contains "purple".




Ground-Up Java
Ground-Up Java
ISBN: 0782141900
EAN: 2147483647
Year: 2005
Pages: 157
Authors: Philip Heller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net