Java Notes
Complete guide to Byte Streams in Java — FileInputStream, FileOutputStream, reading and writing binary data, copying files, and understanding byte-level I/O operations.
What are Byte Streams?
Byte streams handle I/O of raw binary data — one byte (8 bits) at a time. They can read/write ANY type of data: text files, images, audio, video, PDFs, etc.
InputStream and OutputStream
These are the abstract parent classes for all byte streams:
| Class | Purpose | Key Methods |
|---|---|---|
InputStream | Read bytes | read(), read(byte[]), close() |
OutputStream | Write bytes | write(int), write(byte[]), flush(), close() |
FileOutputStream — Writing Bytes
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteWriteExample {
public static void main(String[] args) {
System.out.println("=== Writing Bytes to File ===\n");
// Method 1: Write one byte at a time
try (FileOutputStream fos = new FileOutputStream("output1.txt")) {
fos.write(72); // H (ASCII 72)
fos.write(101); // e (ASCII 101)
fos.write(108); // l
fos.write(108); // l
fos.write(111); // o
System.out.println("✓ Written 'Hello' byte by byte");
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// Method 2: Write byte array
try (FileOutputStream fos = new FileOutputStream("output2.txt")) {
String message = "Hello, Java Byte Streams!";
byte[] data = message.getBytes(); // Convert String to bytes
fos.write(data);
System.out.println("✓ Written entire string as byte array");
System.out.println(" Bytes written: " + data.length);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// Method 3: Append mode (true = append, don't overwrite)
try (FileOutputStream fos = new FileOutputStream("output2.txt", true)) {
fos.write("\nAppended line!".getBytes());
System.out.println("✓ Appended to existing file");
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// Method 4: Write partial array
try (FileOutputStream fos = new FileOutputStream("output3.txt")) {
byte[] data = "Hello World".getBytes();
fos.write(data, 0, 5); // Write only "Hello" (offset=0, length=5)
System.out.println("✓ Written partial array: first 5 bytes");
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
}
}Output:
FileInputStream — Reading Bytes
Output:
| Content | Hello, Java I/O! Welcome to byte streams. |
| Chunk (10 bytes) | 'Hello, Jav' |
| Chunk (10 bytes) | 'a I/O! Wel' |
| Chunk (10 bytes) | 'come to by' |
| Chunk (10 bytes) | 'te streams' |
| Chunk (1 bytes) | '.' |
| Total bytes read | 41 |
| All bytes (41) | Hello, Java I/O! Welcome to byte streams. |
| Available bytes | 41 |
| Remaining | Java I/O! Welcome to byte streams. |
Understanding read() Return Value
The read() method returns an int (not byte). Here's why:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ReadReturnValue {
public static void main(String[] args) throws IOException {
// Write bytes including values 0-255
try (FileOutputStream fos = new FileOutputStream("bytes.dat")) {
fos.write(0); // Zero byte (valid data)
fos.write(127); // Max positive byte
fos.write(128); // Would be -128 as signed byte
fos.write(255); // Would be -1 as signed byte
}
// Read and show int values
try (FileInputStream fis = new FileInputStream("bytes.dat")) {
int value;
while ((value = fis.read()) != -1) {
System.out.printf("int: %3d | byte: %4d | binary: %8s%n",
value, (byte) value, Integer.toBinaryString(value & 0xFF));
}
}
System.out.println("\nWhy int and not byte?");
System.out.println("- byte range: -128 to 127");
System.out.println("- But we need values 0-255 for raw bytes");
System.out.println("- AND we need -1 to signal end-of-file");
System.out.println("- int solves this: 0-255 for data, -1 for EOF");
new java.io.File("bytes.dat").delete();
}
}Output:
| int | 0 | byte: 0 | binary: 0 |
| int | 127 | byte: 127 | binary: 1111111 |
| int | 128 | byte: -128 | binary: 10000000 |
| int | 255 | byte: -1 | binary: 11111111 |
| - byte range | -128 to 127 |
| - int solves this | 0-255 for data, -1 for EOF |
File Copy Using Byte Streams
Output (approximate):
| Source file size | 1,024,000 bytes |
| Byte-by-byte | 1856 ms |
| 1KB buffer | 12 ms |
| 8KB buffer | 4 ms |
| 64KB buffer | 3 ms |
| Speedup (byte vs 8KB) | ~464x faster |
Key Lesson: Always use a buffer when doing I/O! Reading byte-by-byte requires a system call for each byte, which is extremely slow.
Writing Binary Data (Non-Text)
import java.io.*;
public class BinaryData {
public static void main(String[] args) throws IOException {
System.out.println("=== Writing/Reading Binary Data ===\n");
// Write various data types as bytes
try (FileOutputStream fos = new FileOutputStream("data.bin")) {
// Write an integer (4 bytes)
int number = 12345;
fos.write((number >> 24) & 0xFF);
fos.write((number >> 16) & 0xFF);
fos.write((number >> 8) & 0xFF);
fos.write(number & 0xFF);
// Write raw bytes
fos.write(new byte[]{10, 20, 30, 40, 50});
System.out.println("Written: int 12345 + 5 raw bytes");
}
// Read back
try (FileInputStream fis = new FileInputStream("data.bin")) {
// Read the integer back
int num = (fis.read() << 24) | (fis.read() << 16) |
(fis.read() << 8) | fis.read();
System.out.println("Read integer: " + num);
// Read remaining bytes
byte[] remaining = fis.readAllBytes();
System.out.print("Read bytes: ");
for (byte b : remaining) {
System.out.print(b + " ");
}
System.out.println();
}
new File("data.bin").delete();
// Better approach: Use DataOutputStream/DataInputStream
System.out.println("\n--- Using DataOutputStream (Better!) ---");
try (DataOutputStream dos = new DataOutputStream(
new FileOutputStream("data2.bin"))) {
dos.writeInt(12345);
dos.writeDouble(3.14159);
dos.writeBoolean(true);
dos.writeUTF("Hello!");
System.out.println("Written: int, double, boolean, String");
}
try (DataInputStream dis = new DataInputStream(
new FileInputStream("data2.bin"))) {
System.out.println("Read int: " + dis.readInt());
System.out.println("Read double: " + dis.readDouble());
System.out.println("Read boolean: " + dis.readBoolean());
System.out.println("Read String: " + dis.readUTF());
}
new File("data2.bin").delete();
}
}Output:
| Written | int 12345 + 5 raw bytes |
| Read integer | 12345 |
| Read bytes | 10 20 30 40 50 |
| Written | int, double, boolean, String |
| Read int | 12345 |
| Read double | 3.14159 |
| Read boolean | true |
| Read String | Hello! |
Common Mistakes
1. Not closing streams
// ❌ BAD: Resource leak if exception occurs
FileInputStream fis = new FileInputStream("file.txt");
int data = fis.read();
fis.close(); // Won't execute if read() throws!
// ✅ GOOD: Try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
int data = fis.read();
} // Automatically closed even on exception2. Reading byte-by-byte for large files
3. Using byte streams for text (encoding issues)
// ❌ BAD: Byte streams don't handle Unicode properly
FileOutputStream fos = new FileOutputStream("text.txt");
fos.write("Hello 日本語".getBytes()); // Encoding depends on platform!
// ✅ GOOD: Use character streams for text
FileWriter writer = new FileWriter("text.txt", java.nio.charset.StandardCharsets.UTF_8);
writer.write("Hello 日本語");Interview Questions
Q1: What is the difference between byte stream and character stream?
Answer: Byte streams (InputStream/OutputStream) handle raw binary data 8 bits at a time — suitable for images, audio, any file type. Character streams (Reader/Writer) handle Unicode text 16 bits at a time — suitable for text with proper encoding handling.
Q2: Why does read() return int instead of byte?
Answer: Because byte values range from 0-255 for raw data, and we need a special value (-1) to indicate end-of-file. A byte type (-128 to 127) can't distinguish between data byte -1 (which is actually 255 unsigned) and the EOF signal. Using int solves this: 0-255 = valid data, -1 = EOF.
Q3: What happens if you don't close a FileOutputStream?
Answer: (1) Data in internal buffers may not be written to disk (data loss), (2) the file handle leaks (OS limits the number of open files), (3) other programs may not be able to access the file. Always close streams, preferably with try-with-resources.
Q4: What is the difference between flush() and close()?
Answer: flush() forces buffered data to be written to the destination but keeps the stream open for more writes. close() flushes the buffer AND releases the resource (file handle). After close(), you cannot write anymore.
Q5: How would you efficiently copy a large file (e.g., 2GB)?
Answer: Use a buffered read/write loop with an 8KB-64KB buffer. For the best performance in modern Java, use Files.copy(source, destination) from java.nio which uses OS-level optimizations. Never read byte-by-byte for large files.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Byte Streams in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, file, handling, byte
Related Java Master Course Topics