IO stream

Published 2026-07-29 09:12 Updated 2026-07-29 09:12 2283 words 12 min read ... Page views

The article systematically introduces the core content of file operations and IO streams in Java, covering the path processing of File classes, file and directory creation, recursive operations, and the use of byte streams and character streams. It focuses on the classification of IO streams, the combined use of node streams and processing streams, common operations such as file copying, text reading and writing, object serialization, and the practice of configuration file processing and Apache POI reading and writing Excel files. Resource management, coding specifications, security and best practices in practical applications are emphasized.

IO stream

File class

File is an abstract representation of a file or directory path. Creating a File object only creates a path object in memory, and does not mean that files or directories on disk must exist.

constructor

File file1 = new File("d:/lesson/java2601/a.txt");
File file2 = new File("d:/lesson/java2601", "a.txt");
File parent = new File("d:/lesson/java2601");
File file3 = new File(parent, "a.txt");

You can use escaped backslashes or forward slashes in Windows path strings.

create files and directories

create a file

File file = new File("d:/lesson/java2601/test/a.txt");

try {
    boolean created = file.createNewFile();
    System.out.println(created ? "创建成功" : "文件已存在");
} catch (IOException e) {
    e.printStackTrace();
}

createNewFile() will not automatically create parent directories that do not exist.

File extensions are often used to help operating systems and applications identify file types, but the extensions themselves do not change file content.

create a directory

mkdir() can only create a single-level directory when the parent directory already exists.

File directory = new File("d:/lesson/java2601/test/a");
directory.mkdir();

mkdirs() can simultaneously create missing multi-level parent directories.

File directory = new File("d:/lesson/java2601/test/a/b/c");
directory.mkdirs();

create temporary files

File directory = new File("d:/lesson/java2601/test");
try {
    File tempFile = File.createTempFile("temp", ".txt", directory);
    System.out.println(tempFile);
} catch (IOException e) {
    e.printStackTrace();
}

Temporary file name prefixes require at least three characters.

common methods

MethodAction
exists()Determine whether there is
isFile()Determine whether it is an ordinary document
isDirectory()Determine whether it is a catalog
delete()Delete files or empty directories
getName()Get file or directory name
getParentFile()Gets the File object corresponding to the parent path
getPath()Path form used when obtaining construction objects
getAbsolutePath()Get the absolute path string
length()Gets the length of ordinary files in bytes
listFiles()Get the direct sub-item in the catalog

listFiles() may return null when the path is not a directory, IO errors occur, or no access rights are available. It should be checked before use.

File directory = new File("d:/lesson/java2601");
File[] files = directory.listFiles();

if (files != null) {
    for (File file : files) {
        System.out.println(file);
    }
}

File.equals() compares whether abstract path names are equal, and does not automatically normalize all relative paths, symbolic links, or paths containing ... When you need to compare actual specification paths, you can use getCanonicalFile() and process IOException.

recursive

Recursion is when a method calls itself directly or indirectly. Recursion must have clear termination conditions and ensure that each call approaches the termination conditions gradually.

factorial

public static long factorial(int number) {
    if (number < 0) {
        throw new IllegalArgumentException("阶乘参数不能为负数");
    }
    if (number <= 1) {
        return 1;
    }
    return number * factorial(number - 1);
}

Fibonacci sequence

public static long fibonacci(int number) {
    if (number < 0) {
        throw new IllegalArgumentException("参数不能为负数");
    }
    if (number <= 1) {
        return number;
    }
    return fibonacci(number - 1) + fibonacci(number - 2);
}

This direct recursion will repeatedly compute a large number of subproblems and is only suitable for demonstrating recursion. Use circular loops or cached results when processing large data.

Recursively find files

public static void findPdfFiles(File path, List<File> result) {
    if (path == null || !path.exists()) {
        return;
    }

    if (path.isFile()) {
        if (path.getName().toLowerCase().endsWith(".pdf")) {
            result.add(path);
        }
        return;
    }

    File[] children = path.listFiles();
    if (children == null) {
        return;
    }

    for (File child : children) {
        findPdfFiles(child, result);
    }
}

Recursively delete directory

public static boolean deleteRecursively(File path) {
    if (path == null || !path.exists()) {
        return true;
    }

    if (path.isDirectory()) {
        File[] children = path.listFiles();
        if (children == null) {
            return false;
        }

        for (File child : children) {
            if (!deleteRecursively(child)) {
                return false;
            }
        }
    }

    return path.delete();
}

When recursively processing directories, you should also be aware of access permissions, symbolic links, and possible stack overflows caused by excessively deep directories. Modern Java projects can also use the File Tree API provided by java.nio.file.Files.

IO Flow Overview

IO represents input and output. Using program memory as a reference:

  • The input stream reads external data into the program.
  • The output stream writes program data to external devices.

Classification by data unit

  • The InputStream and OutputStream systems process by byte and are suitable for pictures, audio, video and other binary data.
  • The Reader and Writer systems process characters and are suitable for text data.

Character stream requires decoding bytes into characters according to character encoding, or encoding characters into bytes.

Classified by connection target

  • Node flows directly connect data sources or targets such as files, memory, and networks, such as FileInputStream.
  • Processing streams wrap other streams and add functions such as buffering, data type reading and writing, and object serialization, such as BufferedInputStream and ObjectOutputStream.

Processing flows are not necessarily “more advanced” or “faster” than node flows and should be used in combination based on the required functionality.

using step

  1. Determine whether input or output.

  2. Determine whether to process bytes or characters.

  3. Select the node flow corresponding to the actual data source.

  4. Increase buffering, transcoding, or object processing streams as needed.

  5. Use try-with-resources to shut down resources in a timely manner.

FileInputStream and FileOutputStream

read bytes

The commonly used read() methods for FileInputStream are as follows:

  • read(): Read one byte, return 0 to 255, and return -1 at the end.
  • read(byte[] buffer): Read the array length at most by bytes, and return the actual read number.
  • read(byte[] buffer, int offset, int length): Read data to the specified range of the array.

When reading in batches, only the actual length read this time can be processed.

try (FileInputStream input = new FileInputStream(
        "d:/lesson/java2601/test/a.txt")) {

    byte[] buffer = new byte[1024];
    int length;

    while ((length = input.read(buffer)) != -1) {
        for (int i = 0; i < length; i++) {
            System.out.println(buffer[i]);
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}

When outputting text content, you should not force any bytes to characters one by one. You should use a character stream and specify correct encoding.

write byte

FileOutputStream overwrites the original file by default, and when the second parameter of the constructor is true, it means appending.

try (FileOutputStream output = new FileOutputStream(
        "d:/lesson/java2601/test/a.txt", true)) {

    byte[] data = "ABCDEFG".getBytes(StandardCharsets.UTF_8);
    output.write(data);
} catch (IOException e) {
    e.printStackTrace();
}

copy files

public static void copyFile(String source, String target) {
    try (FileInputStream input = new FileInputStream(source);
         FileOutputStream output = new FileOutputStream(target)) {

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

        while ((length = input.read(buffer)) != -1) {
            output.write(buffer, 0, length);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Copying binary files must use a byte stream.

BufferedInputStream and BufferedOutputStream

Buffered byte streams maintain buffers in memory and reduce the number of underlying read and write calls.

public static void copyFileWithBuffer(String source, String target) {
    try (BufferedInputStream input = new BufferedInputStream(
            new FileInputStream(source));
         BufferedOutputStream output = new BufferedOutputStream(
            new FileOutputStream(target))) {

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

        while ((length = input.read(buffer)) != -1) {
            output.write(buffer, 0, length);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

The buffer is flushed first when the output stream is turned off. flush() can be called when you need to immediately commit data to the underlying target while the stream remains open.

FileReader and FileWriter

FileReader and FileWriter are used to read and write text, but traditional constructors usually use the platform default character set. When file coding is fixed, it is recommended to use InputStreamReader and OutputStreamWriter to explicitly specify the character set.

try (FileReader reader = new FileReader(
        "d:/lesson/java2601/test/a.txt")) {

    char[] buffer = new char[1024];
    int length;

    while ((length = reader.read(buffer)) != -1) {
        System.out.print(new String(buffer, 0, length));
    }
} catch (IOException e) {
    e.printStackTrace();
}

FileReader itself is not BufferedReader, and it cannot be described as naturally having line buffering functions.

try (FileWriter writer = new FileWriter(
        "d:/lesson/java2601/test/a.txt")) {
    writer.write("1234567890");
} catch (IOException e) {
    e.printStackTrace();
}

BufferedReader and BufferedWriter

BufferedReader provides readLine(), which can read text per line. The returned string does not contain a line terminator and returns null when it reaches the end of the file.

try (BufferedReader reader = new BufferedReader(
        new FileReader("d:/lesson/java2601/test/a.txt"))) {

    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

BufferedWriter provides line separators corresponding to the newLine() writing platform.

try (BufferedWriter writer = new BufferedWriter(
        new FileWriter("d:/lesson/java2601/test/a.txt"))) {

    writer.write("第一行");
    writer.newLine();
    writer.write("第二行");
} catch (IOException e) {
    e.printStackTrace();
}

InputStreamReader and OutputStreamWriter

These two conversion streams connect the byte stream and the character stream and are responsible for character encoding conversions.

try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(
                new FileInputStream("d:/lesson/java2601/test/a.txt"),
                StandardCharsets.UTF_8))) {

    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
try (BufferedWriter writer = new BufferedWriter(
        new OutputStreamWriter(
                new FileOutputStream("d:/lesson/java2601/test/a.txt"),
                StandardCharsets.UTF_8))) {

    writer.write("使用 UTF-8 写入文本");
} catch (IOException e) {
    e.printStackTrace();
}

DataInputStream and DataOutputStream

The data stream can read and write data in binary format of Java’s basic type.

try (DataOutputStream output = new DataOutputStream(
        new FileOutputStream("d:/lesson/java2601/test/data.bin"))) {

    output.writeInt(1024);
    output.writeDouble(100.0);
    output.writeUTF("Java");
} catch (IOException e) {
    e.printStackTrace();
}

You must use the same order and type when reading, otherwise you will read wrong results or throw exceptions.

try (DataInputStream input = new DataInputStream(
        new FileInputStream("d:/lesson/java2601/test/data.bin"))) {

    int number = input.readInt();
    double price = input.readDouble();
    String text = input.readUTF();

    System.out.println(number);
    System.out.println(price);
    System.out.println(text);
} catch (IOException e) {
    e.printStackTrace();
}

Object serialization and deserialization

Serialization converts an object and its serializable object graph into a byte sequence, and deserialization restores the object based on the byte sequence.

Class needs to implement the Serializable tag interface.

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private transient String password;
}

The transient field will not participate in default serialization. serialVersionUID is used to determine whether serialized data is compatible with the current class version.

Serialize to byte array

public static byte[] serialize(Object object) throws IOException {
    try (ByteArrayOutputStream bytes = new ByteArrayOutputStream();
         ObjectOutputStream output = new ObjectOutputStream(bytes)) {

        output.writeObject(object);
        return bytes.toByteArray();
    }
}

deserialize from byte array

public static Object deserialize(byte[] data)
        throws IOException, ClassNotFoundException {

    try (ByteArrayInputStream bytes = new ByteArrayInputStream(data);
         ObjectInputStream input = new ObjectInputStream(bytes)) {

        return input.readObject();
    }
}

Serialization data does not require that JDK minor versions be completely consistent, but class names, field structures, serialVersionUID and related classes must meet compatibility conditions.

deserializing untrusted data may trigger dangerous object behavior. Bytestreams from untrusted sources must not be directly deserialized in real projects.

PrintWriter

PrintWriter provides convenient text output methods such as print(), println() and printf().

try (PrintWriter writer = new PrintWriter(
        "d:/lesson/java2601/test/a.txt",
        StandardCharsets.UTF_8.name())) {

    writer.println("你好");
    writer.println("中午吃什么");
} catch (FileNotFoundException | UnsupportedEncodingException e) {
    e.printStackTrace();
}

Some write errors in PrintWriter will not be directly thrown as IOException. checkError() can be called to check the status when needed. Closing the stream automatically flushes the buffer.

Properties Class

Properties is used to save string key value configuration and inherits from Hashtable<Object, Object>. getProperty() should be used first when reading the configuration, and setProperty() should be used when writing.

Configuration files are suitable for holding data that can be adjusted by the deployment environment but should not be hard-coded into the program.

Common configuration formats

  • XML has a clear structure, but it is relatively complex to write and parse.
  • Properties use simple key value forms and have limited hierarchical expression capabilities.
  • YAML has strong hierarchical expression capabilities and is often used in frameworks such as Spring Boot.

read configuration

p1.name=Tom
p1.age=20
p2.name=Jerry
p2.age=22
Properties properties = new Properties();

try (Reader reader = new InputStreamReader(
        new FileInputStream("src/myinfo.properties"),
        StandardCharsets.UTF_8)) {

    properties.load(reader);

    Person person1 = new Person();
    person1.name = properties.getProperty("p1.name");
    person1.age = Integer.parseInt(properties.getProperty("p1.age"));

    Person person2 = new Person();
    person2.name = properties.getProperty("p2.name");
    person2.age = Integer.parseInt(properties.getProperty("p2.age"));
} catch (IOException e) {
    e.printStackTrace();
}

Use Reader to explicitly select a character set. When InputStream is directly used to read Properties in Java 8, traditional formats are handled according to ISO-8859-1 rules, and non-ASCII characters usually require Unicode escape.

writing configuration

properties.setProperty("p3.name", "Tom");
properties.setProperty("p3.age", "22");

try (Writer writer = new OutputStreamWriter(
        new FileOutputStream("src/myinfo.properties"),
        StandardCharsets.UTF_8)) {

    properties.store(writer, "第三个人的信息");
} catch (IOException e) {
    e.printStackTrace();
}
image-001
image-001

Apache POI

Apache POI is a Java project of the Apache Software Foundation that can read and write Microsoft Office formats such as Excel and Word.

A typical exercise is to read data from a student’s achievement workbook, calculate the average, and write the results to another worksheet. When processing Excel, you need to select the corresponding model based on the file format:

  • .xls usually uses HSSF.
  • .xlsx usually uses XSSF.

When using POI to read and write workbooks, input streams, and output streams, resources should also be correctly turned off through try-with-resources, and attention should be paid to the handling of cell types, empty cells, and formula cells.

If you enjoyed this, leave a comment~

... Page views
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka