1. I/O Basics
Input/Output (I/O) in Java refers to the process of reading data from a source (input) and writing data to a destination (output). Java provides a robust I/O framework to handle various data sources and destinations, such as files, console, network sockets, and memory.
-
Key Concepts:
- I/O operations are performed using streams, which are sequences of data.
- Java I/O is built around the
java.io
package, with additional support injava.nio
(New I/O) for advanced operations. - I/O operations often involve exceptions (e.g.,
IOException
), requiring proper handling.
-
Types of I/O:
- Byte-oriented I/O: Handles raw binary data (e.g., images, files) using byte streams.
- Character-oriented I/O: Handles text data (e.g., Unicode characters) using character streams.
- Buffered I/O: Uses buffers to reduce direct access to underlying resources, improving performance.
- Non-buffered I/O: Direct access to resources, less efficient for frequent operations.
-
Why Important?:
- Enables interaction with external systems (files, network, console).
- Supports data persistence and communication.
- Critical for real-world applications (e.g., reading configuration files, logging).
2. Streams and Stream Classes
Streams are abstractions for reading from or writing to a data source/destination. Java provides two main types of streams: byte streams and character streams, each with specific classes in the java.io
package.
-
Byte Streams:
- Handle raw binary data (8-bit bytes).
- Base classes:
InputStream
: Abstract class for reading bytes.OutputStream
: Abstract class for writing bytes.
- Common subclasses:
FileInputStream
,FileOutputStream
: For file I/O.BufferedInputStream
,BufferedOutputStream
: For buffered I/O.DataInputStream
,DataOutputStream
: For reading/writing primitive data types.ObjectInputStream
,ObjectOutputStream
: For object serialization.
-
Character Streams:
- Handle Unicode characters (16-bit).
- Base classes:
Reader
: Abstract class for reading characters.Writer
: Abstract class for writing characters.
- Common subclasses:
FileReader
,FileWriter
: For file I/O.BufferedReader
,BufferedWriter
: For buffered I/O.InputStreamReader
,OutputStreamWriter
: Bridge between byte and character streams.
-
Key Features:
- Chaining: Streams can be wrapped (e.g.,
BufferedReader
wrapsFileReader
for efficiency). - Closing: Streams must be closed to release resources (use
close()
or try-with-resources). - Exceptions: Most I/O operations throw
IOException
(checked exception).
- Chaining: Streams can be wrapped (e.g.,
-
Example (Byte Stream):
package com.example.myapp; import java.io.*; public class ByteStreamDemo { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt")) { int byteData; while ((byteData = fis.read()) != -1) { // Read byte fos.write(byteData); // Write byte } } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
-
Example (Character Stream):
package com.example.myapp; import java.io.*; public class CharStreamDemo { public static void main(String[] args) { try (BufferedReader reader = new BufferedReader(new FileReader("input.txt")); BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) { String line; while ((line = reader.readLine()) != null) { writer.write(line); writer.newLine(); } } catch (IOException e) { System.out.println("Error: " + e.getMessage()); } } }
-
Key Points:
- Use byte streams for binary data, character streams for text.
- Buffering (e.g.,
BufferedReader
) improves performance. - Try-with-resources (Java 7+) ensures streams are closed automatically.
3. The Predefined Streams
Java provides three predefined streams in the java.lang.System
class for console I/O, automatically available in every program.
-
System.in:
- Type:
InputStream
. - Purpose: Reads input from the console (standard input, typically the keyboard).
- Example: Use with
Scanner
orBufferedReader
.
- Type:
-
System.out:
- Type:
PrintStream
. - Purpose: Writes output to the console (standard output).
- Example:
System.out.println("Hello")
.
- Type:
-
System.err:
- Type:
PrintStream
. - Purpose: Writes error messages to the console (standard error).
- Example:
System.err.println("Error occurred")
.
- Type:
-
Example:
package com.example.myapp; import java.io.*; public class PredefinedStreamDemo { public static void main(String[] args) { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); try { System.out.println("Enter your name: "); String name = reader.readLine(); System.out.println("Hello, " + name); System.err.println("This is an error message"); } catch (IOException e) { System.err.println("Error reading input: " + e.getMessage()); } } }
-
Key Points:
System.in
is typically wrapped withBufferedReader
orScanner
for easier input.System.out
andSystem.err
arePrintStream
objects, supporting methods likeprintln()
andprintf()
.- These streams are automatically open and do not need closing.
4. Reading from and Writing to Console
Console I/O involves reading user input from the keyboard (System.in
) and writing output to the console (System.out
, System.err
).
-
Reading from Console:
- Use
Scanner
(simpler) orBufferedReader
(more control). - Example with
Scanner
:package com.example.myapp; import java.util.Scanner; public class ConsoleReadDemo { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Enter an integer: "); try { int number = scanner.nextInt(); System.out.println("You entered: " + number); } catch (Exception e) { System.err.println("Invalid input: " + e.getMessage()); } finally { scanner.close(); } } }
- Example with
BufferedReader
:package com.example.myapp; import java.io.*; public class ConsoleReadBufferedDemo { public static void main(String[] args) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { System.out.println("Enter text: "); String text = reader.readLine(); System.out.println("You entered: " + text); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); } } }
- Use
-
Writing to Console:
- Use
System.out.println()
,System.out.printf()
, orSystem.out.print()
. - Example:
System.out.println("Simple message"); System.out.printf("Formatted: %d, %s%n", 42, "test"); System.err.println("Error message");
- Use
-
Key Points:
Scanner
is easier for parsing numbers, strings, etc.BufferedReader
is better for reading raw text or large inputs.- Always handle
IOException
forBufferedReader
and validateScanner
input. - Use try-with-resources to close
Scanner
orBufferedReader
.
5. Reading and Writing Files
File I/O involves reading from and writing to files using byte or character streams. Java provides classes like FileInputStream
, FileOutputStream
, FileReader
, and FileWriter
, often wrapped with buffered streams for efficiency.
-
Reading a File:
- Use
FileReader
withBufferedReader
for text files. - Use
FileInputStream
withBufferedInputStream
for binary files. - Example (Text File):
package com.example.myapp; import java.io.*; public class FileReadDemo { public static void main(String[] args) { try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); } } }
- Use
-
Writing to a File:
- Use
FileWriter
withBufferedWriter
for text files. - Use
FileOutputStream
withBufferedOutputStream
for binary files. - Example (Text File):
package com.example.myapp; import java.io.*; public class FileWriteDemo { public static void main(String[] args) { try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) { writer.write("Hello, Java!"); writer.newLine(); writer.write("This is a test."); } catch (IOException e) { System.err.println("Error writing file: " + e.getMessage()); } } }
- Use
-
Key Points:
- Use try-with-resources to ensure streams are closed.
- Handle
IOException
(e.g.,FileNotFoundException
for missing files). - Use
newLine()
for platform-independent line breaks in character streams. - For appending to a file, use
FileWriter(file, true)
(append mode).
-
File Class:
- The
java.io.File
class represents file/directory paths. - Methods:
exists()
,isFile()
,isDirectory()
,createNewFile()
,delete()
. - Example:
File file = new File("data.txt"); if (file.exists()) { System.out.println("File size: " + file.length()); }
- The
6. The Transient and Volatile Modifiers
The transient
and volatile
modifiers are related to I/O in the context of serialization and multithreading, respectively.
-
Transient Modifier:
- Used in serialization to mark fields that should not be serialized (saved) when an object is written to a stream.
- Serialization: Converting an object to a byte stream (e.g., using
ObjectOutputStream
). - Example:
package com.example.myapp; import java.io.*; public class TransientDemo implements Serializable { private String name; private transient String password; // Not serialized public TransientDemo(String name, String password) { this.name = name; this.password = password; } public static void main(String[] args) { TransientDemo obj = new TransientDemo("Alice", "secret"); // Serialize try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.ser"))) { oos.writeObject(obj); } catch (IOException e) { System.err.println("Error: " + e.getMessage()); } // Deserialize try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.ser"))) { TransientDemo deserialized = (TransientDemo) ois.readObject(); System.out.println("Name: " + deserialized.name); // Alice System.out.println("Password: " + deserialized.password); // null } catch (IOException | ClassNotFoundException e) { System.err.println("Error: " + e.getMessage()); } } }
-
Volatile Modifier:
- Used in multithreading to ensure a variable’s value is always read from and written to main memory, preventing thread-local caching.
- Ensures visibility of changes across threads but does not guarantee atomicity.
- Example:
package com.example.myapp; public class VolatileDemo { private volatile boolean running = true; public void stop() { running = false; } public void run() { while (running) { System.out.println("Running..."); try { Thread.sleep(500); } catch (InterruptedException e) { System.err.println("Interrupted"); } } System.out.println("Stopped"); } public static void main(String[] args) throws InterruptedException { VolatileDemo demo = new VolatileDemo(); Thread t1 = new Thread(demo::run); t1.start(); Thread.sleep(2000); demo.stop(); } }
-
Key Points:
transient
: Prevents sensitive data (e.g., passwords) from being serialized.volatile
: Ensures thread visibility for variables in multithreaded environments.- Use
transient
in I/O (serialization),volatile
in multithreading (with I/O implications for shared resources).
7. Using Instance of Native Methods
Native methods are Java methods implemented in a non-Java language (e.g., C or C++) using the Java Native Interface (JNI). They are used for performance-critical tasks or to access platform-specific features not available in Java.
-
Key Points:
- Declared with the
native
keyword and no implementation in Java. - Implemented in a native library (e.g.,
.dll
on Windows,.so
on Linux). - Loaded using
System.loadLibrary()
.
- Declared with the
-
Syntax:
public native void nativeMethod();
-
Example:
package com.example.myapp; public class NativeDemo { public native void printHello(); static { System.loadLibrary("NativeLib"); // Load native library } public static void main(String[] args) { NativeDemo demo = new NativeDemo(); demo.printHello(); // Calls native method } }
- C Implementation (e.g.,
NativeLib.c
):#include <jni.h> #include <stdio.h> JNIEXPORT void JNICALL Java_com_example_myapp_NativeDemo_printHello(JNIEnv *env, jobject obj) { printf("Hello from C!\n"); }
- Compile and link the C code into a shared library (platform-specific).
- C Implementation (e.g.,
-
Steps to Use Native Methods:
- Declare the
native
method in Java. - Generate a header file using
javah
(orjavac -h
in Java 9+). - Implement the method in C/C++.
- Compile the C/C++ code into a shared library.
- Load the library with
System.loadLibrary()
. - Call the native method.
- Declare the
-
Key Points:
- Native methods are rare in standard I/O but used in libraries like
java.io
for low-level file operations. - Risks: Platform dependence, memory leaks, and debugging complexity.
- Requires
CLASSPATH
to include native libraries.
- Native methods are rare in standard I/O but used in libraries like
-
Connection to I/O:
- Native methods are used in
java.io
classes (e.g.,FileInputStream
) for OS-level file access. - Example:
FileInputStream.read()
may call native code for direct disk access.
- Native methods are used in
Practice Program:
package com.example.myapp;
import java.io.*;
import java.util.Scanner;
public class IODemo implements Serializable {
private String data;
private transient int temp;
public IODemo(String data, int temp) {
this.data = data;
this.temp = temp;
}
public static void main(String[] args) {
// Console I/O
Scanner scanner = new Scanner(System.in);
System.out.println("Enter text: ");
String input = scanner.nextLine();
System.out.println("You entered: " + input);
scanner.close();
// File I/O
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write(input);
} catch (IOException e) {
System.err.println("Error writing file: " + e.getMessage());
}
// Serialization
IODemo obj = new IODemo("Test", 42);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.ser"))) {
oos.writeObject(obj);
} catch (IOException e) {
System.err.println("Error serializing: " + e.getMessage());
}
}
}
Compile and Run:
javac com/example/myapp/IODemo.java
java -cp . com.example.myapp.IODemo