Chapter 16

Working with Streams

Most programs work with some kind of data, which could be stored in a local database, on a remote computer, or in a file located on your disk. Java has a concept of working with streams of data. You can say that a Java program reads sequences of bytes from an input stream (or writes into an output stream): byte after byte, character after character, primitive after primitive. Accordingly, Java defines various types of classes supporting streams, for example InputStream or OutputStream. There are classes specifically meant for reading character streams such as Reader and Writer. DataInputStream and DataOutputStream can read and write Java primitives, and to work with files you may consider such classes as FileInputStream and FileReader.

Classes that work with streams are located in two packages: java.io and java.nio. Classes from the former implement blocking of input/output (I/O): When bytes are being read/written by a process, they become unavailable for other threads of execution. The latter package offers non-blocking I/O with improved performance. In this lesson you’ll be dealing with streams defined in java.io.

Before deciding which Java class to use for I/O in each particular case, you need to understand what kind of data is coming from the stream in question. But no matter what you select, your code will need to perform three operations:

1. Open a stream that points at a specific data source: a file, a socket, a URL, and so on.

2. Read or write data from/to this stream.

3. Close the stream.

If a Java program uses a third-party program, such as database management systems (DBMS), you won’t need to program streams directly — the database drivers or object-relational mapping framework is all you need. In this lesson you’ll see examples of performing I/O operations with different streams.

Byte Streams

A program can read or write any file one byte at a time with the help of one of the subclasses of InputStream or OutputStream respectively. The example in Listing 16-1 shows how to use the class FileInputStream to read a file named abc.dat. This code snippet reads and prints each byte’s value:

download.eps

Listing 16-1: Using FileInputStream

FileInputStream myFile = null;
  try {
            myFile = new  FileInputStream("abc.dat");
            boolean eof = false;
 
            while (!eof) {
                int byteValue = myFile.read();
                System.out.print(byteValue + " ");
                if (byteValue  == -1){
                    eof = true;
                } 
           }
         // myFile.close(); // do not do it here!!!
  } catch (IOException e) {
           System.out.println("Could not read file: " + e.toString());
  } finally{
     if (myFile !=null){
        try{
            myFile.close();
        } catch (Exception e1){
             e1.printStackTrace();
        }
    }
  }
}

Because the code in Listing 16-1 doesn’t specify the directory where abc.dat is located, the program will try to find this file in the current directory, which is the root directory of the Eclipse project. At any given time you can easily find out the current directory programmatically by calling the method System.getProperty("user.dir").

The output of this program will be a sequence of numbers, which represents the codes of the characters located in the file. For example, if abc.dat contains the text “This is a test file,” the output on the system console will look like this:

84 104 105 115 32 105 115 32 97 32 116 101 115 116 32 102 105 108 101 -1   

When you are reading with FileInputStream, the end of the file is represented by a negative one, and this is how you know when to stop. The code in Listing 16-1 checks for -1 and sets the boolean variable eof to false to finish the loop. Note that the stream is closed in the clause finally. Do not call the method close() inside the try/catch block right after the file reading is done. In case of an exception during the file read, the program will jump over the close() statement into the catch section, and the stream will never be closed!

The code fragment in Listing 16-2 writes into a file called xyz.dat using the class FileOutputStream.

download.eps

Listing 16-2: Using FileOutputStream

// byte values are represented by integers from 0 to 255
  int somedata[]= {56,230,123,43,11,37};
  FileOutputStream myFile = null;
  try {
     myFile = new  FileOutputStream("xyz.dat");
     for (int i = 0; i <somedata.length; i++){
        file.write(data[i]);
     }
 } catch (IOException e) {
       System.out.println("Could not write to a  file: " + e.toString());
  } finally{
      if (myFile !=null){
        try{
          myFile.close();
        } catch (Exception e1){
          e1.printStackTrace();
        }
      }
  }
}

Buffered Streams

The code in the previous section was reading and writing one byte at a time. In general, disk access is much slower than the processing performed in memory; that’s why it’s not a good idea to access disk a thousand times to read a file of 1000 bytes. To minimize the number of times the disk is accessed, Java provides so-called buffers, which serve as reservoirs of data.

The class BufferedInputStream works as a middleman between FileInputStream and the file itself. It reads a big chunk of bytes from a file into memory in one shot, and the FileInputStream object then reads single bytes from there, which is memory-to-memory operations. BufferedOutputStream works similarly with the class FileOutputStream. The main idea here is to minimize disk access.

Buffered streams are not changing the type of reading — they just make reading more efficient. Think of it this way: A program performs stream chaining (or stream piping) to connect streams, just as pipes are connected in plumbing. Listing 16-3 shows an example in which a file is read so the data from FileInputStream will fill BufferedInputStream before processing.

download.eps

Listing 16-3: Chaining FileInputStream with BufferedInputStream

FileInputStream myFile = null;
BufferedInputStream buff =null;
  try {
     myFile = new  FileInputStream("abc.dat");
     buff = new BufferedInputStream(myFile);
            boolean eof = false;
            while (!eof) {
                int byteValue = buff.read();
                System.out.print(byteValue + " ");
                if (byteValue  == -1)
                    eof = true;
           }
} catch (IOException e) { ...  }
 finally{ 
if (myFile !=null){
        try{
          buff.close();  
          myFile.close();
        } catch (Exception e1){
          e1.printStackTrace();
        }
      }
 }

While reading a stream with the help of BufferedInputStream you watch for the end-of-file character. But when you write a stream via BufferedOutputStream with method write(), you need to do one extra step: Call the method flush() before closing the buffered stream. This ensures that none of the buffered bytes “get stuck” and that all are written out to the underlying output stream.

You might be wondering, how large is the buffer? While the default buffer size varies depending on the OS, you can control it using a two-argument constructor. For example, to set the buffer size to 5000 bytes instantiate the buffered stream as follows:

BufferedInputStream buff = new BufferedInputStream(myFile, 5000); 

Character Streams

The text in Java is represented as a set of char values (two-byte characters), which are based on the Unicode Standard. The Java classes FileReader and FileWriter were specifically created to work with text files, but they work only with default character encoding and don’t handle localization properly.

The recommended way is to pipe the class InputStreamReader with specified encoding and the FileInputStream. The class InputStreamReader reads bytes and decodes them into characters using a specified CharSet. Each JVM has a default charset, which can be specified during the JVM start-up and depends on the locale. Some of the standard charsets are US-ASCII, UTF-8, and UTF-16.

Listing 16-4 reads bytes from a text file and converts them from UTF-8 encoding into Unicode to return results as a String. For efficiency, the reading is piped with the BufferReader, which reads text from the stream buffering characters. Note that this code uses StringBuffer that usually works faster than String when it comes to performing text manipulations.

download.eps

Listing 16-4: Reading text files

StringBuffer buffer = new StringBuffer();
    try {
       FileInputStream myFile = new FileInputStream("abc.txt");
       InputStreamReader inputStreamReader = 
                  new InputStreamReader(myFile, "UTF8");
      Reader reader = new BufferedReader(inputStreamReader);
 
      int ch;  // the code of one character
 
      while ((ch = reader.read()) > -1) {
             buffer.append((char)ch);
      }
      
      buffer.toString();
 
    } catch (IOException e) {
         ...   
    }

For writing characters to a file, pipe FileOutputStream and OutputStreamWriter. For efficiency, use BufferedWriter, for example

try{
  String myAddress = "123 Broadway, New York, NY 10011";
  FileOutputStream myFile = new FileOutputStream("abc.txt");
  
  Writer out
     = new BufferedWriter(new OutputStreamWriter(myFile, "UTF8"));
  out.write(myAddress);
 
} catch(IOException e){
       ...
}

Listing 16-5 shows yet another version of the tax calculation program. This time I’ve added a text file, states.txt, that includes states that will be used to populate a drop-down box, chStates. My file is located in the root directory of the Eclipse project Lesson16, and it looks like this:

New York
New Jersey
Florida
California 

The program in Listing 16-5 requires a class, Tax, that you can borrow from Lesson 4. Make sure that it has the method calcTax().

download.eps

Listing 16-5: Bringing together Swing and streams

import java.awt.event.*;
import javax.swing.*;
import java.awt.GridLayout;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Vector;
 
public class TaxGuiFile extends JFrame implements ActionListener {
    JLabel lblGrIncome;
    JTextField txtGrossIncome = new JTextField(15);
    JLabel lblDependents=new JLabel("Number of Dependents:");
    JTextField txtDependents = new JTextField(2);
    JLabel lblState = new JLabel("State: ");
    
    //Define a data model for the ComboBox chState
    Vector states = new Vector(50); 
    
    //Create a combobox to get data from the model 
    JComboBox chState = new JComboBox(states);
 
    JLabel lblTax = new JLabel("State Tax: ");
    JTextField txtStateTax = new JTextField(10);
    JButton bGo = new JButton("Go");
    JButton bReset = new JButton("Reset");
    
    TaxGuiFile() {
        lblGrIncome = new JLabel("Gross Income: ");
        GridLayout gr = new GridLayout(5,2,1,1);
        setLayout(gr);
 
        add(lblGrIncome);
        add(txtGrossIncome);
        add(lblDependents);
        add(txtDependents);
        add(lblState);
        add(chState);
        add(lblTax);
        add(txtStateTax);
        add(bGo);
        add(bReset);
 
        // Populate states from a file
        populateStates();
        
        chState.setSelectedIndex(0);
 
        txtStateTax.setEditable(false);
 
        bGo.addActionListener(this);
        bReset.addActionListener(this);
 
        // Define, instantiate and register a WindowAdapter
        // to process windowClosing Event of this frame
 
        this.addWindowListener(new WindowAdapter() {
           public void windowClosing(WindowEvent e) {
                 System.out.println("Good bye!");
                  System.exit(0);
           }});
    }
 
    public void actionPerformed(ActionEvent evt) {
       Object source = evt.getSource();
        if (source == bGo ){
           // The Button Go processing
             try{
               int grossInc =Integer.parseInt(txtGrossIncome.getText());
               int dependents = Integer.parseInt(txtDependents.getText());
               String state = (String)chState.getSelectedItem();
 
               Tax tax=new Tax(dependents,state,grossInc);
               String sTax =Double.toString(tax.calcTax());
               txtStateTax.setText(sTax);
             }catch(NumberFormatException e){
                 txtStateTax.setText("Non-Numeric Data");
             }catch (Exception e){
                txtStateTax.setText(e.getMessage());
             }
         }
         else if (source == bReset ){
            // The Button Reset processing
             txtGrossIncome.setText("");
             txtDependents.setText("");
        chState.setSelectedIndex(0);
             txtStateTax.setText("");
             }
    }
   // The code below will read the file states.txt and  
   // populate the dropdown chStates
    private void populateStates(){
      
        states.add("Select State");
        
        FileInputStream myFile=null;
        InputStreamReader inputStreamReader;
        BufferedReader reader = null;
        
        try {
         myFile = new FileInputStream("states.txt");
         inputStreamReader = 
                      new InputStreamReader(myFile, "UTF8");
          reader = new BufferedReader(inputStreamReader);
    
          String nextLine;
          boolean eof = false;
          while (!eof) {
               nextLine = reader.readLine();
                 if (nextLine == null){
                   eof = true;  
                } else {
                   // Populate the model 
                   states.add(nextLine);
                }  
          }
          
        }catch (IOException e){
            txtStateTax.setText("Can't read states.txt");
        }
        finally{
          // Closing the streams
          try{
              if (myFile !=null){ 
               reader.close();
               myFile.close();
               }
          }catch(IOException e){
             e.printStackTrace();
          }
        }
    }
 
    public static void main(String args[]){
       TaxGuiFile taxFrame = new TaxGuiFile();
       taxFrame.setSize(400,150);
       taxFrame.setVisible(true);
    }
}

The code in Listing 16-5 reads the content of the file states.txt and populates a collection — a Vector with states. This collection plays the role of a data model for the combo box states. I used a constructor of JComboBox that takes a data model as an argument. This Swing component knows how to display the content of its data model.

This is an example of the implementation of the so-called MVC (model-view-controller) design pattern, which promotes the separation of data and UI. JComboBox plays the role of a view, the vector is a model, and the user works as controller when she selects a particular state and the view has to be updated. The output of the program from Listing 16-5 is shown in Figure 16-1.

Data Streams

If you are expecting to work with a stream of a known data structure (e.g., two integers, three floats, and a double) use either DataInputStream for reading or DataOutputStream for writing. A method, readInt(), of DataInputStream will read the whole integer number (four bytes) at once, and readLong() will get you a long number (eight bytes).

The class DataInputStream is yet another filter that can be connected to another stream. Listing 16-6 has an example of how you can “build a pipe” from the following pieces:

FileInputStream BufferedInputStream DataInputStream

download.eps

Listing 16-6: Using DataInputStream

FileInputStream myFile = new FileInputStream("myData.dat");
BufferedInputStream buff = new BufferedInputStream(myFile);
DataInputStream data = new  DataInputStream(buff);
 
try {
   int num1 = data.readInt();
   int num2 = data.readInt();
   float num2 = data.readFloat();
   float num3 = data.readFloat();
   float num4 = data.readFloat();
   double num5 = data.readDouble();
} catch (EOFException eof) {...}

In this example FileInputStream opens the file myData.dat for reading, BufferedInputStream makes the read more efficient, and DataInputStream extracts from the buffer two integers, three floats, and a double. The assumption here is that the file myData.dat contains exactly these data types and in the specified order. Such a file could have been created with the help of DataOutputStream, which allows you to write primitive Java data types to a stream in a portable way. It has a variety of methods to choose from: writeInt(), writeByte(), writeFloat(), and so on.

The Class File

The class File allows you to rename or delete a file, perform an existence check, create a directory, and more. If you need this functionality, start by creating an instance of this class:

File myFile = new File("abc.txt");

This line does not create a file; it just creates in memory an instance of the class File that’s ready to perform its action on the file abc.txt. If you want to create a physical file, use the method createNewFile() defined in the class File. Here’s the list of some methods of the class File:

  • createNewFile(): Creates a new, empty file named according to the file name used during the file instantiation. Creates a new file only if a file with this name does not exist.
  • delete(): Deletes file or directory.
  • renameTo(): Renames a file.
  • length(): Returns the length of the file in bytes.
  • exists(): Tests whether the file with the specified name exists.
  • list(): Returns an array of strings containing file and directory.
  • lastModified(): Returns the time that the file was last modified.
  • mkDir(): Creates a directory.

The next code fragment checks for the existence of the file customers.txt.bak, deletes it if it is found, and then renames the file customers.txt to customers.txt.bak:

File file = new File("customers.txt");
File backup = new File("customers.txt.bak");
if (backup.exists()){
       backup.delete();
}
file.renameTo(backup);

Try It

Write a program that will read a .zip archive file and print on the system console the list of files included in the zip archive. Do a little research about the class java.util.zip.ZipInputStream and use it together with FileInputStream. Read about the class ZipEntry too.

Lesson Requirements

You should have Java installed.

note.ai

You can download the code and resources for this Try It from the book’s web page at www.wrox.com. You can find them in the Lesson16 folder in the download.

Step-by-Step

1. Create a new Eclipse project called Lesson16.

2. Copy any .zip file into its root directory.

3. Open FileInputStream and connect it with ZipInputStream.

4. Write a loop that will use the method getNextEntry() from ZipInputStream. This method reads the ZipEntry, if any, and positions the stream at the beginning of the entry data.

5. Call the function getName() on each ZipEntry instance found.

6. Print the entry name on the system console.

7. Close the entry inside the loop.

8. Run the program and observe that it properly prints the file names from the selected .zip file.

9. If you want to learn how to create .zip files from Java, read about ZipOutputStream.

cd.ai

Please select Lesson 16 on the DVD with the print book, or watch online at www.wrox.com/go/fainjava to view the video that accompanies this lesson.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.151.153