Master Java I/O: From Byte Streams to Object Serialization
This article provides a comprehensive guide to Java I/O, explaining the concepts of input and output streams, distinguishing byte and character streams, detailing node and processing streams, and offering practical code examples for file handling, buffering, conversion, and object serialization.
Understanding I/O Streams
I/O stands for input and output, describing the process of copying data between memory and a data source.
Input: data loaded from a source into memory. Output: data written from memory back to the source. A data source can be any file type such as txt, image, SQL, mp4, etc.
Java I/O Stream Classification
Byte Streams and Character Streams
Java I/O is divided into two major categories: byte streams and character streams.
Byte streams read data in byte units, typically using InputStream and OutputStream .
Character streams read data as characters, usually more efficient for text, and classes contain Reader or Writer in their names.
ps: the 24‑bit in the diagram represents a Chinese character encoded in UTF‑8.
Thinking: How do byte and character streams differ in usage scenarios?
Character streams are ideal for text files (txt), while byte streams suit binary data such as images or videos.
Four sub‑categories arise from combining input/output with byte/character:
Byte Input Stream
Byte Output Stream
Character Input Stream
Character Output Stream
Different data sources require different stream types. The following summary lists common implementations:
Byte Input: InputStream , FileInputStream , ByteArrayInputStream , PipedInputStream … Byte Output: OutputStream , FileOutputStream , ByteArrayOutputStream , PipedOutputStream … Character Input: Reader , FileReader , CharArrayReader , PipedReader , StringReader … Character Output: Writer , FileWriter , CharArrayWriter , PipedWriter , StringWriter …
Node Streams and Processing Streams
Node streams directly read/write a specific data source, while processing streams wrap an existing stream to add functionality.
The following table lists common node and processing streams such as BufferedInputStream , PrintStream , and ObjectInputStream .
Common Node Stream Examples
FileInputStream and FileOutputStream
FileInputStream reads files as bytes. Example:
<code>public static void readFile() {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(new File("/path/text.txt"));
byte[] readData = new byte[10];
int bufferSize = 0;
while ((bufferSize = fileInputStream.read(readData)) != -1) {
System.out.print(new String(readData, 0, bufferSize));
}
System.out.println();
} catch (Exception e) {
e.printStackTrace();
} finally {
try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); }
}
}</code>Streams must be closed.
read() returns the actual number of bytes read.
FileOutputStream writes bytes to a file. Example (append mode):
<code>public static void writeFileVersion1() {
String filePath = "/path/text_2.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath, true);
fileOutputStream.write("input sth".getBytes());
fileOutputStream.write("input sth2".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); }
}
}</code>The second constructor argument controls overwrite vs. append.
Character streams persist data without explicit close, unlike FileOutputStream.
Thinking: Will reading the above code cause garbled characters?
Yes, because reading a UTF‑8 encoded file in fixed‑size byte chunks can split multibyte characters.
Solutions:
Increase the byte array size (limited by memory).
Read in multiples of the encoding’s byte length (not flexible for mixed content).
Use character streams such as FileReader, which handle encoding correctly.
FileReader and FileWriter
FileReader reads text as characters. Example:
<code>public static void readFile() {
FileReader fileReader = null;
try {
fileReader = new FileReader("/path/text.txt");
char[] chars = new char[3];
int readLen = 0;
while ((readLen = fileReader.read(chars)) != -1) {
System.out.println(new String(chars, 0, readLen));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); }
}
}</code>FileWriter writes characters to a file. Example (append mode):
<code>public static void writeFileVersion1() {
String filePath = "/path/text_3.txt";
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(filePath, true);
fileWriter.write("this is test\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); }
}
}</code>Without close or flush, data may not be persisted.
The append flag determines whether new data overwrites or appends.
PipedInputStream and PipedOutputStream
Piped streams enable communication between threads. Example:
<code>public static void main(String[] args) {
try (PipedOutputStream out = new PipedOutputStream()) {
PipedInputStream in = new PipedInputStream(out);
new Thread(() -> {
try {
Thread.sleep(2000);
StringBuilder item = new StringBuilder();
for (int i = 0; i < 1000; i++) { item.append("1"); }
out.write(item.toString().getBytes(StandardCharsets.UTF_8));
out.close();
} catch (IOException | InterruptedException e) { e.printStackTrace(); }
}).start();
byte[] temp = new byte[1024];
int receive;
while ((receive = in.read(temp)) != -1) {
System.out.println(new String(temp, 0, receive));
}
} catch (Exception e) { e.printStackTrace(); }
}</code>Common Processing Streams
BufferedInputStream and BufferedOutputStream
These streams add a buffer layer to improve performance by reducing direct disk I/O.
<code>public static void fileCopyByByte(String sourceFile, String destPath) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath));
byte[] tempByte = new byte[1024];
int len;
while ((len = bis.read(tempByte)) != -1) {
bos.write(tempByte, 0, len);
}
bos.close();
bis.close();
}</code>Close the outer buffered stream; it will close the underlying stream.
Remember to close or flush to ensure data is written to disk.
PrintStream and PrintWriter
PrintStream (byte‑based) and PrintWriter (character‑based) are used for formatted output. Example with PrintStream:
<code>public static void main(String[] args) throws IOException {
System.setOut(new PrintStream("/path/out.log"));
PrintStream out = System.out;
out.println("john hello");
out.write("test".getBytes());
out.close();
}</code>Example with PrintWriter:
<code>public static void main(String[] args) throws FileNotFoundException {
PrintWriter out = new PrintWriter("/path/out.log");
out.println("john hello");
out.write("test");
out.close();
}</code>InputStreamReader and OutputStreamWriter
Conversion streams handle different character encodings. Example writing and reading a GBK‑encoded file:
<code>public static void main(String[] args) throws IOException {
String filePath = "/path/file_1.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), "gbk");
osw.write("测试语句");
osw.close();
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
char[] chars = new char[1024];
int len = isr.read(chars);
System.out.println(new String(chars, 0, len));
isr.close();
}</code>ObjectInputStream and ObjectOutputStream
Object streams serialize and deserialize objects. Example writing various primitive types and a custom object:
<code>public static void writeObjDemo() throws IOException {
String destPath = "/path/obj_1.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(destPath));
oos.writeInt(100);
oos.writeBoolean(true);
oos.writeDouble(1.1);
oos.writeChar('a');
oos.writeUTF("测试语句");
oos.writeObject(new Dog(101, "name1"));
oos.close();
}</code>Reading back:
<code>public static void readObj() throws IOException, ClassNotFoundException {
String destPath = "/path/obj_1.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(destPath));
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readDouble());
System.out.println(ois.readChar());
System.out.println(ois.readUTF());
Dog dog = (Dog) ois.readObject();
System.out.println(dog);
ois.close();
}</code>Read/write order must match.
Serialized classes must implement Serializable and preferably define serialVersionUID .
All fields involved in serialization must themselves be serializable.
Subclass inherits serialization if its superclass implements it.
Sanyou's Java Diary
Passionate about technology, though not great at solving problems; eager to share, never tire of learning!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.