Effective Input/Output

Understanding I/O in Programming

  1. Importance of I/O:
    • Almost all programs need to interact with the outside world, which means they need to handle input and output.
    • Input can come from various sources, such as user input (via keyboard), files, databases, or network connections.
    • Output can be directed to the screen, files, printers, or over the network.
  2. Purpose of I/O:
    • Programs that only process data internally without any interaction are limited in functionality. For example, a program that computes a value without taking user input or displaying results has little practical use.
    • Effective I/O allows a program to be useful, interactive, and responsive to user needs.
  3. Historical Context:
    • Since the inception of programming, I/O has been a fundamental aspect. As programming languages evolved, so did their capabilities for handling I/O.
    • Modern programming languages, including Java, incorporate sophisticated I/O libraries to streamline these processes.

I/O in Java

  1. Mature I/O System:
    • Java provides a robust and flexible I/O system that allows developers to handle various types of I/O operations easily.
    • This includes not just basic reading and writing but also more advanced features like buffering, character encoding, and data serialization.
  2. Components of Java’s I/O System:
    • Streams: Java uses the concept of streams to handle I/O. A stream is a continuous flow of data that can be read from or written to. Java distinguishes between:
      • Byte Streams: For handling raw binary data (e.g., images, audio files).
      • Character Streams: For handling character data (text files).
    • Classes and Interfaces: Java provides various classes and interfaces to facilitate I/O operations, such as InputStream, OutputStream, Reader, Writer, and specific implementations like FileInputStream, BufferedReader, etc.
    • Exception Handling: I/O operations can often fail due to various reasons (file not found, access denied, etc.). Java provides exception handling mechanisms to manage these errors gracefully.
  3. Key Operations:
    • Reading Data: You can read data from different sources, including files, user input, or network sockets.
    • Writing Data: You can output data to various destinations, including the console, files, or other external systems.
    • File Manipulation: Java allows you to create, delete, and manipulate files and directories, making it easier to manage data storage.
  4. Example Use Cases:
    • Reading user input from the console to configure a program.
    • Writing logs to a file for later analysis.
    • Reading and processing data from a database or a remote server.


Expectations from an I/O System

A modern I/O system should meet several expectations:

  1. Communication with Various Sources: Java should facilitate reading from input devices (like keyboards, disks) and writing to outputs (like disks, printers).
  2. Handling Different Data Types: Java should manage I/O for various data types: bytes, characters, numbers, strings, records, and objects.
  3. Multiple Communication Modes: Support for different I/O modes, including sequential and random access.
  4. File System Interaction: Ability to manipulate files and directories, including paths, permissions, and timestamps.

File, Directory, and Drive Operations

Java provides the File class for performing operations like creating, copying, deleting, and moving files and directories. The following programs demonstrate how to use the File class.

Program 1: Check if a File Exists

This program checks if a specified file exists and retrieves its information.

// Obtain information about a file
package fileinfoproject;
import java.io.*;
import java.util.Date;

public class FileInfoProject {
    public static void main(String[] args) throws Exception {
        String str;
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.print("Enter filename: ");
            str = br.readLine();
            File f = new File(str);
            if (f.exists()) {
                String dname = f.getParent();
                System.out.println("Directory name: " + dname);
                String fname = f.getName();
                System.out.println("File name: " + fname);
                String abspath = f.getAbsolutePath();
                System.out.println("Full Name: " + abspath);
                long size = f.length();
                System.out.println("Size: " + size);
                String ext = str.substring(str.lastIndexOf("."));
                System.out.println("Extension = " + ext);
                System.out.println("Last Modified = " + new Date(f.lastModified()));
            }
        } catch (IOException e) {
            System.out.println("Error in input");
        }
    }
}

This program prompts the user to enter a filename, checks if it exists, and prints its details, including the directory name, file name, absolute path, size, extension, and last modified date.

Program 2: Recursive Listing of Files in Directories

This program lists all files and directories in the current directory, recursively navigating through subdirectories.

// Recursive listing of files in directories
package directorylisterproject;
import java.io.*;

public class DirectoryListerProject {
    public static void main(String[] args) {
        File d = new File(".");
        ListFiles(d, "");
    }

    static void ListFiles(File d, String indent) {
        System.out.println(indent + d.getName() + "/");
        for (File fi : d.listFiles()) {
            String str = indent + " " + fi.getName();
            System.out.println(str);
        }
        // Implement accept function of FileFilter interface
        FileFilter dirFilter = new FileFilter() {
            public boolean accept(File file) {
                return file.isDirectory();
            }
        };
        for (File di : d.listFiles(dirFilter)) {
            ListFiles(di, indent + " ");
        }
    }
}

This program prints the structure of the current directory, with directories marked by a trailing slash and indented to reflect their hierarchy.

Program 3: Obtain Information About Drives

This program lists the drives on the machine and provides details about their total and free space.

// Obtain information about all drives
package driveinfoproject;
import java.io.*;

public class DriveInfoProject {
    public static void main(String[] args) {
        for (File d : File.listRoots()) {
            System.out.println("Drive = " + d);
            System.out.println("Total Space = " + d.getTotalSpace());
            System.out.println("Free Space = " + d.getFreeSpace());
            System.out.println();
        }
    }
}

This program uses the listRoots() method of the File class to retrieve and display information about all available drives.


Java Streams

Java’s I/O system is centered around streams, which are sequences of bytes traveling from source to destination. Streams abstract the complexity of I/O operations, allowing programmers to focus on reading from or writing to these streams without worrying about the underlying hardware.

Types of Streams

  1. Byte Streams: Handle I/O of raw byte data (1 byte at a time).
  • Classes: InputStream, OutputStream, FileInputStream, FileOutputStream, etc.
  1. Character Streams: Handle I/O of character data (2 bytes at a time).
  • Classes: Reader, Writer, FileReader, FileWriter, etc.

Example Program: Writing Integers to Files

This example program demonstrates how to write an integer to files in various formats.

package byteandcharacterstreams;
import java.io.*;

public class ByteAndCharacterStreams {
    public static void main(String[] args) {
        int i = 123456;
        try {
            rawWrite(i);
            charWrite(i);
            unicodeWrite(i);
        } catch (IOException e) {
            System.out.println("IO error");
        }
    }

    static void rawWrite(int i) throws IOException {
        DataOutputStream ds = new DataOutputStream(new FileOutputStream("Stream.txt"));
        ds.writeInt(i);
        ds.close();
        System.out.println("Wrote 123456 as an integer");
        System.out.print("Length of file = ");
        System.out.println(new File("Stream.txt").length());
    }

    static void charWrite(int i) throws IOException {
        FileWriter fw = new FileWriter(new File("Char.txt"));
        fw.write(Integer.toString(i));
        fw.close();
        System.out.println("Wrote 123456 as a string");
        System.out.print("Length of file = ");
        System.out.println(new File("Char.txt").length());
    }

    static void unicodeWrite(int i) throws IOException {
        OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("CharU.txt"), "UTF-16");
        ow.write(Integer.toString(i));
        ow.close();
        System.out.println("Wrote 123456 as a Unicode string");
        System.out.print("Length of file = ");
        System.out.println(new File("CharU.txt").length());
    }
}

This program demonstrates three ways to write an integer to different files:

  • Raw Write: Writes the integer directly in binary format.
  • Character Write: Writes the integer as a string.
  • Unicode Write: Writes the integer as a Unicode string, showing the size difference in file lengths.
Sure! Let’s break down the concepts of streams in Java and the code examples step-by-step for better understanding.

Streams in Java

In Java, streams are used to perform input and output (I/O) operations. They are divided into two main categories:

  • Byte Streams: Handle I/O of raw binary data (1 byte at a time). Useful for reading and writing binary files.
  • Character Streams: Handle I/O of characters (2 bytes at a time). Useful for reading and writing text files.

Key Points:

  • Byte Stream: For example, when writing an integer (which takes 4 bytes), all 4 bytes are sent to the byte stream.
  • Character Stream: If you write the same integer as a string, it will use more bytes because each character (like ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’) takes 2 bytes in UTF-16 encoding.

Stream Classes in Java

Java provides several built-in classes for handling streams:

  • InputStream and OutputStream: Abstract classes for byte streams.
  • FileInputStream and FileOutputStream: For reading/writing byte data from/to files.
  • BufferedInputStream: For efficient reading using a buffer.
  • DataInputStream: For reading Java primitive types.
  • Reader and Writer: Abstract classes for character streams.
  • FileReader and FileWriter: For reading/writing character data from/to files.
  • BufferedReader: For efficient reading of text from a character stream.
  • PrintWriter: For formatted output.

Writing Different Types of Data to Files

The provided Java program demonstrates how to write the same integer (123456) to three different files using different methods:

a. Writing as an Integer (Raw Write)

static void rawWrite (int i) throws IOException {
    DataOutputStream ds = new DataOutputStream(
        new FileOutputStream("Stream.txt"));
    ds.writeInt(i);
    ds.close();
    System.out.println("Wrote 123456 as an integer");
    System.out.print("Length of file = ");
    System.out.println(new File("Stream.txt").length());
}
  • Uses DataOutputStream to write the integer directly to a file. This takes 4 bytes.

b. Writing as a String (Character Write)

static void charWrite (int i) throws IOException {
    FileWriter fw = new FileWriter(new File("Char.txt"));
    fw.write(((Integer) i).toString());
    fw.close();
    System.out.println("Wrote 123456 as a string");
    System.out.print("Length of file = ");
    System.out.println(new File("Char.txt").length());
}
  • Uses FileWriter to convert the integer to a string and write it to a file. This takes 6 bytes (for the characters ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’).

c. Writing as a Unicode String

static void unicodeWrite (int i) throws IOException {
    OutputStreamWriter ow = new OutputStreamWriter(
        new FileOutputStream("CharU.txt"), "UTF-16");
    ow.write(((Integer) i).toString());
    ow.close();
    System.out.println("Wrote 123456 as a Unicode string");
    System.out.print("Length of file = ");
    System.out.println(new File("CharU.txt").length());
}
  • Uses OutputStreamWriter with UTF-16 encoding. Each character takes 2 bytes, totaling 14 bytes for the string “123456”.

Reading from a File

This example reads the contents of a file line by line and displays them on the screen:

public class DisplayFileContents {
    public static void main(String[] args) throws IOException {
        File f = new File("path_to_file");
        if (f.exists() && f.canRead()) {
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(f));
                String line;
                while ((line = br.readLine()) != null)
                    System.out.println(line);
            } catch (FileNotFoundException ex) {
                System.out.println("Can't open " + f.getName());
            } finally {
                if (br != null) br.close();
            }
        }
    }
}
  • Uses BufferedReader with FileReader to read the file line by line.
  • It checks if the file exists and if it can be read, then prints each line.

Record I/O Example

This example demonstrates writing and reading records of employee data:

public class RecordIO {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("emp.dat");
        OutputStreamWriter osw = new OutputStreamWriter(fos);
        BufferedReader br1 = new BufferedReader(new InputStreamReader(System.in));

        // Receiving employee data
        String choice = "y", temp1, temp2, temp3;
        while (choice.equals("y")) {
            System.out.println("Enter employee id: ");
            temp1 = br1.readLine();
            System.out.println("Enter employee salary: ");
            temp2 = br1.readLine();
            System.out.println("Enter employee name: ");
            temp3 = br1.readLine();
            osw.write(temp1 + "@" + temp2 + "@" + temp3 + "\n");
            System.out.println("Want another (y/n): ");
            choice = br1.readLine();
        }
        osw.close();

        // Reading employee data
        FileInputStream fis = new FileInputStream("emp.dat");
        BufferedReader br2 = new BufferedReader(new InputStreamReader(fis));
        String rec, str[];

        System.out.println("\nEmployees Info: ");
        while (true) {
            try {
                rec = br2.readLine();
                str = rec.split("@", 3);
                System.out.println("Id: " + str[0]);
                System.out.println("Salary: " + str[1]);
                System.out.println("Name: " + str[2]);
            } catch (Exception e) {
                if (fis != null) fis.close();
                break;  // Exit loop if end of file is reached
            }
        }
    }
}
  • This program allows you to enter employee details and saves them to a file (emp.dat).
  • It then reads the records from the file and splits them based on the delimiter (@).

User-Defined Streams

You can create your own stream classes. For example, the UppercaseFilterReader converts all characters read from a file into uppercase:

class UppercaseFilterReader extends FilterReader {
    public UppercaseFilterReader(Reader s) {
        super(s);
    }
    public int read(char[] cbuf, int off, int count) throws IOException {
        int nb = in.read(cbuf, off, count);
        for (int i = off; i < off + nb; i++)
            cbuf[i] = transform(cbuf[i]);
        return nb;
    }
    private char transform(char ch) {
        if (Character.isLowerCase(ch))
            return Character.toUpperCase(ch);
        return ch;
    }
}
  • This class extends FilterReader and overrides the read method to transform all characters to uppercase before returning them.

File Encryption/Decryption

The Substitution Cipher example demonstrates a simple encryption scheme:

class Encrypt implements ITransform {
    String str = "xyfagchbimpourvnqsdewtkjzl"; // Substitution string
    public char transform(char ch) {
        if (Character.isLowerCase(ch))
            ch = str.charAt(ch - 'a'); // Substitute with corresponding char
        return ch;
    }
}
  • It replaces each lowercase letter with another predetermined character.
  • The Decrypt class does the reverse, mapping characters back to their original forms.