Writing and Reading Bytes


Document files don't really contain text. Image files don't really contain pictures. MP3 files don't really contain music, and MPEG files don't really contain movies. They all contain bytes, because all files just contain bytes. The bytes encode information; they are decoded by software appropriate to the encoded content. This is why filename extensions are so important. They tell the computer what decoding software to use. If you take an image file that encodes a really beautiful picture and you change filename extension to .mp3, it's probably going to sound terrible.

All files are sequences of bytes. Before we look at decoding and encoding the information represented by the files, you need to learn how to write and read plain ordinary bytes. You will make extensive use of two of the classes in the java.io package:

  • FileOutputStream

  • FileInputStream

A file output stream writes bytes to a file; a file input stream reads bytes from a file. Our purpose here is not to present both classes in their entirety. Here you will learn more than enough to be able to use them well. Whenever you want to complete your understanding, you can refer to the API documentation.

Both classes have constructors with String arguments, where the string specifies the name of the file. On Windows machines, the file separator (the character that goes between elements in a full pathname) is a backslash, and that can lead to problems. So let's begin with a digression on dealing with backslashes.

Backslashes in Filenames

If you want to write bytes to a file in the current working directory called xyz, you can construct a file output stream like this:

FileOutputStream fos; fos = new FileOutputStream("xyz"); 

Of course, you can create a file output stream in a similar way. Ignoring for the moment the issue of what you can actually do with those streams, you have to deal with the question of what happens when you want to specify a full pathname on a Windows system. For example, what if you want to write to a file whose full pathname is C:my_files\photos\abc? The following code will not do what you want:

FileOutputStream fos; fos = new FileOutputStream("C:my_files\photos\abc");

Surprisingly, this code will not compile! The compiler error says that there is an invalid escape character, whatever that means.

Actually, the problem has nothing to do with file output streams. It has to do with backslashes in literal strings. You would get the same compilation error if you tried the following:

String s = "C:my_files\photos\abc";

In Chapter 2, "Data," you saw that certain characters (most notably the newline and tab characters) are represented by escape codes, \n for newline and \t for tab. Those codes can also be embedded in literal strings. For example, the following code prints some numbers, separated by tabs, on two lines:

String s = "123\t456\t789\n987\t654\t432"; System.out.println(s);

You can see that the backslash character has special meaning to the Java compiler. In literal strings and chars, backslash means, "Ignore me and treat the next character as a special code." If you just want a simple ordinary backslash in a literal string or char, you have to use a double backslash. For example, to print out the word "hello" followed by a backslash, you have to do the following:

System.out.println("Hello\\");

Note the second backslash. Only one backslash is printed.

So you can see that

String s = "C:my_files\photos\abc";

won't compile, because \p and a are not valid escape codes. It's a good thing they aren't. The following code compiles, but with an unexpected result:

String s = "C:my_backup\temporary\news";

So if you are writing file access code for a Windows machine, you always have to remember to use double backslashes for file separators, like this:

FileOutputStream fos; fos = new FileOutputStream("C:my_files\\photos\\abc");

Now that you know how to specify filenames, we can move on to writing to files.

Writing Bytes

To create a file full of bytes, you have to do three things:

  1. Construct an instance of FileOutputStream.

  2. Write the bytes.

  3. Close the stream.

The following application creates a file called "xyz" in the current directory, writes 10 bytes, and then closes the file:

 1. import java.io.*;  2.  3. public class Write10Bytes  4. {  5.   public static void main(String[] args)  6.   {  7.     FileOutputStream fos;  8.     fos = new FileOutputStream("xyz");  9.     for (int i=0; i<10; i++) 10.       fos.write(i); 11.     fos.close(); 12.   } 13. }

Line 8 constructs the output stream. Line 10, which executes 10 times in the for loop, writes the bytes. It looks like line 10 actually writes ints, because i is an int, but the write() method actually only writes the low-order 8 bits of its argument. Line 11 "closes" the stream. Closing releases certain hidden operating system resources that the stream needs in order to access the disk. After a stream is closed, it can't be written to.

Our code example will not compile, because lines 8, 10, and 11 throw exceptions. The constructor on line 8 throws FileNotFoundException. The write() call on line 10 and the close() call on line 11 throw IOException. So the code can be improved as follows:

 1. import java.io.*;  2.  3. public class Write10Bytes  4. {  5.   public static void main(String[] args)  6.   {  7.     try  8.     {  9.       FileOutputStream fos; 10.       fos = new FileOutputStream("xyz"); 11.       for (int i=0; i<10; i++) 12.         fos.write(i); 13.       fos.close(); 14.     } 15.     catch (FileNotFoundException x) 16.     { 17.       System.out.println("Caught FileNotFoundEx"); 18.     } 19.     catch (IOException x) 20.     { 21.       System.out.println("Caught IOExn"); 22.     } 23.   } 24. }

This code compiles, and it executes correctly. But it can be simplified a bit. FileNotFoundException is a subclass of IOException. So we can eliminate lines 13-16:

 1. import java.io.*;  2.  3. public class Write10Bytes  4. {  5.   public static void main(String[] args_  6.   {  7.     try  8.     {  9.       FileOutputStream fos; 10.       fos = new FileOutputStream("xyz"); 11.       for (int i=0; i<10; i++) 12.         fos.write(i); 13.       fos.close(); 14.     } 15.     catch (IOException x) 16.     { 17.       System.out.println("Caught IOExn"); 18.     } 19.   } 20. }

After this application runs, the current directory contains a 10-byte file named "xyz".

The Simple Output Lab animated illustration demonstrates an application that writes several bytes to a file. To run the program, type "java io.SimpleOutputLab". The initial display is shown in Figure 13.1.

click to expand
Figure 13.1: Simple Output Lab

The animation is very simple, but it will give you a good graphical image of the relationships between the data, the output stream, and the file. Figure 13.2 shows the animation in progress.

click to expand
Figure 13.2: Simple Output Lab in progress

Reading Bytes

Reading bytes is almost exactly like writing bytes. You still have to do three things:

  1. Construct an instance of FileInputStream.

  2. Read the bytes.

  3. Close the stream.

The following application reads back the file that was created in the previous section:

 1. import java.io.*;  2.  3. public class Read10Bytes  4. {  5.   public static void main(String[] args)  6.   {  7.     try  8.     {  9.       FileInputStream fis; 10.       fis = new FileInputStream ("xyz"); 11.       for (int i=0; i<10; i++) 12.       { 13.         int theByte = fis.read(); 14.         System.out.println(theByte); 15.       } 16.       fis.close(); 17.     } 18.     catch (IOException x) 19.     { 20.       System.out.println("Caught IOExn"); 21.     } 22.   } 23. }

Line 8 creates the input stream, line 12 reads the bytes, and line 14 closes the stream. Line 12 prints out the bytes that were read, each one on its own line.

Note the strange variable on line 11. It is called theByte, but it is an int. The read() method of class FileInputStream reads a byte from the disk, but returns an int. Usually, the high-order 24 bits of the returned int are all 0s; the low-order 8 bits are the byte that was read from the disk. However, if the input stream has already read all the bytes in its file, the next read() call will return the int value -1. Recall that this value consists of 32 1's. This is distinct from the byte value of -1, which consists of eight 1's. If a file input stream reads such a byte from its file, the return value will have 1s in its low-order eight bits, and 0s in its high-order 24 bits. So there is no danger of confusing a byte read from the file whose value happens to be -1 with the int that signals that there is no more data in the file. Table 13.1 makes this clear.

Table 13.1: Byte -1 vs. Int -1

byte -1, returned as an int

int -1, signaling end of file

00000000 00000000 00000000 11111111

11111111 11111111 11111111 11111111

You can use the special return value when you don't know the length of the file you are reading. In this example, suppose you don't know that the file contains 10 bytes. As you learned in Chapter 5, when you don't know how many times the loop will execute, it's time to use a while loop:

import java.io.*;  public class Read10Bytes  {    public static void main(String[] args)    {      try      {        FileInputStream fis;        fis = new FileInputStream ("xyz");        while (true)        {          int theByte = fis.read();          if (theByte == -1)            break;          System.out.println(theByte);        }        fis.close(); }      catch (IOException x)      {        System.out.println("Caught IOException");      }    }  }

This application generates exactly the same output as the previous version, but this time there is no need to know the size of the file. This version can handle a file of any size.

The Simple Input Lab animated illustration demonstrates an application that reads several bytes from a file. To run the program, type "java io.SimpleInputLab". The animation is very simple, but like SimpleOutputLab, it will give you a good graphical image of the relationships between the data, the input stream, and the file. Figure 13.3 shows the animation in progress.

click to expand
Figure 13.3: Simple Input Lab in progress




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