Java Streams   «Prev  Next»

Lesson 7File streams
Objective Understand the purpose and use of file streams

Java File Streams and ObjectOutputStream

Up until now, most of the examples have involved System.out and System.in. These are convenient examples of output and input streams.
However, in real work you will more commonly be attaching streams to other data sources like files and network connections.
The java.io.FileInputStream and java.io.FileOutputStream classes are concrete subclasses of java.io.InputStream and java.io.OutputStream, respectively, that provide input and output streams connected to particular files. These classes have all the usual methods of their superclasses such as
  1. read(),
  2. write(),
  3. available(),
  4. flush().
These methods are used exactly as they are for any other input or output stream. The only methods of significance which are new are constructors:

public FileInputStream(String filename) throws IOException
  
public FileOutputStream(String filename) throws IOException


Java 8 lambdas, FileInputStream and FileOutputStream

Write a method using Java 8 lambdas, FileInputStream and FileOutputStream to read data from a file and write it out to another file. In this example, we will create a method called copyFile that takes two file paths as parameters, reads data from the source file using FileInputStream, and writes it to the destination file using FileOutputStream. We'll use Java 8 lambdas and the try-with-resources statement for cleaner code and automatic resource management:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;

public class FileCopy {

    public static void main(String[] args) {
        Path sourcePath = Path.of("source.txt");
        Path destinationPath = Path.of("destination.txt");

        try {
            copyFile(sourcePath, destinationPath);
            System.out.println("File copied successfully.");
        } catch (IOException e) {
            System.err.println("Failed to copy the file: " + e.getMessage());
        }
    }

    public static void copyFile(Path source, Path destination) throws IOException {
        try (FileInputStream fis = new FileInputStream(source.toFile());
             FileOutputStream fos = new FileOutputStream(destination.toFile())) {

            int bufferSize = 8192;
            byte[] buffer = new byte[bufferSize];

            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }

        } // try-with-resources will automatically close FileInputStream and FileOutputStream
    }
}

In this example, the copyFile method reads data from the source file using a FileInputStream and writes it to the destination file using a FileOutputStream. The try-with-resources statement ensures that both streams are closed automatically when the operation is complete. The copyFile method reads data in chunks using a buffer of size 8192 bytes (8 KB) to improve efficiency, and the while loop continues until the end of the file is reached (when fis.read(buffer) returns -1). This implementation works with both text and binary files.

ObjectOutputStream

The java.io.ObjectOutputStream and java.io.ObjectInputStream classes are considered to be higher-level classes in the java.io package, and as we learned earlier, that means that you will wrap them around lower-level classes, such as java.io .FileOutputStream and java.io.FileInputStream. Here is a small program that creates a (Cat) object, serializes it, and then deserializes it:
import java.io.*;
class Cat implements Serializable { } // 1
public class SerializeCat {
 public static void main(String[] args) {
  Cat c = new Cat(); // 2
  try {
   FileOutputStream fs = new FileOutputStream("testSer.ser");
   ObjectOutputStream os = new ObjectOutputStream(fs);
   os.writeObject(c); // 3
   os.close();
  } 
  catch (Exception e) { e.printStackTrace(); }
  try {
   FileInputStream fis = new FileInputStream("testSer.ser");
   ObjectInputStream ois = new ObjectInputStream(fis);
   c = (Cat) ois.readObject(); // 4
   ois.close();
  } 
  catch (Exception e) { e.printStackTrace(); }
 }
}

Let us take a look at the key points in this example:
  1. We declare that the Cat class implements the Serializable interface. Serializable is a marker interface; it has no methods to implement. (In the next several sections, we will cover various rules about when you need to declare classes Serializable.)
  2. We make a new Cat object, which as we know is serializable.
  3. We serialize the Cat object c by invoking the writeObject() method. It took a fair amount of preparation before we could actually serialize our Cat. First, we had to put all of our I/O-related code in a try/catch block. Next we had to create a FileOutputStream to write the object to. Then we wrapped the FileOutputStream in an ObjectOutputStream, which is the class that has the magic serialization method that we need. Remember that the invocation of writeObject() performs two tasks: it serializes the object, and then it writes the serialized object to a file.
  4. We deserialize the Cat object by invoking the readObject() method.
    The readObject() method returns an Object, so we have to cast the deserialized object back to a Cat.

Java7 NIO 2