【Java】IO流完全指南

92 阅读10分钟

Java IO流完全指南

摘要

Java IO流是输入输出操作的核心技术,包括字节流和字符流两大类。通过InputStream、OutputStream、Reader、Writer四大抽象类及其子类,实现文件读写、缓冲处理、对象序列化等功能。


目录

  1. IO流概述
  2. IO四大抽象类
  3. 文件读写操作
  4. 缓冲流
  5. 转换流
  6. File类
  7. 特殊流类型
  8. IO异常处理

IO流概述

IO流分类

按流的方向分类:

  • 输入流(Input):从数据源读取数据到程序
  • 输出流(Output):从程序写入数据到目标

按数据单位分类:

  • 字节流(Byte):以字节为单位处理数据,适用于所有文件类型
  • 字符流(Character):以字符为单位处理数据,适用于文本文件

IO流体系结构

IO流体系
├── 字节流
│   ├── InputStream(输入)
│   └── OutputStream(输出)
└── 字符流
    ├── Reader(输入)
    └── Writer(输出)

io流家族类.jpg

IO四大抽象类

1. InputStream(字节输入流)

所有字节输入流的抽象超类。

构造方法
InputStream()
主要方法
方法返回类型描述
available()int返回可读取的字节数
close()void关闭流,释放资源
mark(int readlimit)void在流中标记当前位置
read()int读取下一个字节,返回-1表示结束
read(byte[] b)int读取字节到数组中
read(byte[] b, int off, int len)int读取指定长度的字节
reset()void重置到标记位置

2. OutputStream(字节输出流)

所有字节输出流的抽象超类。

构造方法
OutputStream()
主要方法
方法返回类型描述
close()void关闭流
flush()void刷新并强制写出缓冲的字节
write(byte[] b)void写入字节数组
write(byte[] b, int off, int len)void写入字节数组的指定部分
write(int b)void写入指定字节

3. Reader(字符输入流)

读取字符流的抽象类。

构造方法
Reader()
Reader(Object lock)  // 指定同步锁
主要方法
方法返回类型描述
close()void关闭流
read()int读取单个字符
read(char[] cbuf)int读取字符到数组
read(char[] cbuf, int off, int len)int读取字符到数组指定位置
ready()boolean判断是否准备好读取
skip(long n)long跳过指定数量的字符

4. Writer(字符输出流)

写入字符流的抽象类。

构造方法
Writer()
Writer(Object lock)  // 指定同步锁
主要方法
方法返回类型描述
close()void关闭流
flush()void刷新流
write(char[] cbuf)void写入字符数组
write(int c)void写入单个字符
write(String str)void写入字符串

文件读写操作

字符文件操作

1. FileReader - 字符文件读取
// 构造方法
FileReader(File file)
FileReader(String fileName)
2. FileWriter - 字符文件写入
// 构造方法
FileWriter(String fileName)              // 覆盖写入
FileWriter(String fileName, boolean append)  // append=true表示追加
字符文件复制示例
import java.io.*;

public class CopyText {
    public static void main(String[] args) throws IOException {
        FileReader fr = null;
        FileWriter fw = null;
        
        try {
            // 创建读取流与源文件关联
            fr = new FileReader("source.txt");
            // 创建写入流与目标文件关联
            fw = new FileWriter("target.txt");
            
            int ch;
            // 循环读取字符并写入
            while ((ch = fr.read()) != -1) {
                fw.write(ch);
            }
        } finally {
            // 关闭流资源
            if (fw != null) fw.close();
            if (fr != null) fr.close();
        }
    }
}

字节文件操作

1. FileInputStream - 字节文件读取
// 构造方法
FileInputStream(File file)
FileInputStream(String name)

// 主要方法
available()     // 返回可读字节数
read()         // 读取单个字节
read(byte[] b) // 读取字节到数组
2. FileOutputStream - 字节文件写入
// 构造方法
FileOutputStream(File file)
FileOutputStream(String name)

// 主要方法
write(byte[] b)                    // 写入字节数组
write(byte[] b, int off, int len)  // 写入字节数组的部分
write(int b)                       // 写入单个字节
字节文件复制示例(图片复制)
import java.io.*;

public class CopyImage {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try {
            fis = new FileInputStream("source.jpg");
            fos = new FileOutputStream("target.jpg");
            
            byte[] buffer = new byte[1024];
            int len;
            
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            throw new RuntimeException("文件复制失败", e);
        } finally {
            try {
                if (fis != null) fis.close();
                if (fos != null) fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

缓冲流

缓冲流通过内置缓冲区提高IO操作效率,减少对底层系统的访问次数。

字符缓冲流

1. BufferedReader
// 构造方法
BufferedReader(Reader in)
BufferedReader(Reader in, int sz)  // 指定缓冲区大小

// 特有方法
readLine()  // 读取一行文本
2. BufferedWriter
// 构造方法
BufferedWriter(Writer out)
BufferedWriter(Writer out, int sz)

// 特有方法
newLine()  // 写入行分隔符
字符缓冲流示例
import java.io.*;

public class BufferedCopyText {
    public static void main(String[] args) {
        BufferedReader bufr = null;
        BufferedWriter bufw = null;
        
        try {
            bufr = new BufferedReader(new FileReader("source.txt"));
            bufw = new BufferedWriter(new FileWriter("target.txt"));
            
            String line;
            while ((line = bufr.readLine()) != null) {
                bufw.write(line);
                bufw.newLine();  // 写入换行符
                bufw.flush();    // 刷新缓冲区
            }
        } catch (IOException e) {
            throw new RuntimeException("读写失败", e);
        } finally {
            try {
                if (bufr != null) bufr.close();
                if (bufw != null) bufw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

字节缓冲流

1. BufferedInputStream
// 构造方法
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
2. BufferedOutputStream
// 构造方法
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)
字节缓冲流示例(音频复制)
import java.io.*;

public class BufferedCopyAudio {
    public static void main(String[] args) {
        BufferedInputStream bufis = null;
        BufferedOutputStream bufos = null;
        
        try {
            bufis = new BufferedInputStream(new FileInputStream("source.mp3"));
            bufos = new BufferedOutputStream(new FileOutputStream("target.mp3"));
            
            int by;
            while ((by = bufis.read()) != -1) {
                bufos.write(by);
            }
        } catch (IOException e) {
            throw new RuntimeException("复制失败", e);
        } finally {
            try {
                if (bufis != null) bufis.close();
                if (bufos != null) bufos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

转换流

转换流是字节流与字符流之间的桥梁,主要用于字符编码转换。

1. InputStreamReader(字节流→字符流)

// 构造方法
InputStreamReader(InputStream in)                    // 使用默认字符集
InputStreamReader(InputStream in, String charsetName)  // 指定字符集

// 主要方法
getEncoding()  // 返回字符编码名称

2. OutputStreamWriter(字符流→字节流)

// 构造方法
OutputStreamWriter(OutputStream out)                    // 使用默认字符集
OutputStreamWriter(OutputStream out, String charsetName)  // 指定字符集

// 主要方法
getEncoding()  // 返回字符编码名称

转换流应用示例

import java.io.*;

public class TransformStreamDemo {
    public static void main(String[] args) {
        BufferedReader bufr = null;
        BufferedWriter bufw = null;
        
        try {
            // 系统输入转为字符流,使用UTF-8编码
            bufr = new BufferedReader(
                new InputStreamReader(System.in, "UTF-8")
            );
            
            // 系统输出转为字符流,使用UTF-8编码
            bufw = new BufferedWriter(
                new OutputStreamWriter(System.out, "UTF-8")
            );
            
            String line;
            while ((line = bufr.readLine()) != null) {
                if ("exit".equals(line)) {
                    break;
                }
                
                // 转为大写并输出
                bufw.write(line.toUpperCase());
                bufw.newLine();
                bufw.flush();
            }
        } catch (IOException e) {
            throw new RuntimeException("处理失败", e);
        } finally {
            try {
                if (bufr != null) bufr.close();
                if (bufw != null) bufw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

File类

File类用于封装文件或文件夹,提供对文件系统的操作功能。

构造方法

File(String pathname)                    // 通过路径名创建
File(String parent, String child)       // 通过父目录和子文件名创建
File(File parent, String child)         // 通过父File对象和子文件名创建

主要方法

创建和删除操作
方法返回类型描述
createNewFile()boolean创建新文件
mkdir()boolean创建目录
mkdirs()boolean创建目录(包括必需的父目录)
delete()boolean删除文件或目录
判断操作
方法返回类型描述
exists()boolean判断是否存在
isFile()boolean判断是否为文件
isDirectory()boolean判断是否为目录
canRead()boolean判断是否可读
canWrite()boolean判断是否可写
获取信息
方法返回类型描述
getName()String获取文件名
getPath()String获取路径
getParent()String获取父目录
length()long获取文件大小
lastModified()long获取最后修改时间
目录操作
方法返回类型描述
list()String[]获取目录中文件名数组
listFiles()File[]获取目录中文件对象数组

File类应用示例

1. 条件查找文件
import java.io.*;

public class FileFilterDemo {
    public static void main(String[] args) {
        File dir = new File("C:\\Users\\Desktop");
        
        // 使用FilenameFilter过滤条件
        File[] jpgFiles = dir.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.toLowerCase().endsWith(".jpg");
            }
        });
        
        // 输出找到的JPG文件
        if (jpgFiles != null) {
            for (File file : jpgFiles) {
                System.out.println(file.getName() + " - 大小: " + file.length() + " bytes");
            }
        }
    }
}
2. 递归遍历目录
import java.io.*;

public class DirectoryTraversal {
    public static void main(String[] args) {
        File dir = new File("C:\\Projects");
        traverseDirectory(dir, 0);
    }
    
    public static void traverseDirectory(File dir, int level) {
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        
        // 打印目录名(带缩进)
        printWithIndent(dir.getName() + "/", level);
        
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    traverseDirectory(file, level + 1);
                } else {
                    printWithIndent(file.getName(), level + 1);
                }
            }
        }
    }
    
    private static void printWithIndent(String text, int level) {
        for (int i = 0; i < level; i++) {
            System.out.print("  ");
        }
        System.out.println(text);
    }
}

特殊流类型

1. 打印流

打印流提供便捷的打印功能,可以打印各种数据类型。

PrintStream(字节打印流)
// 构造方法
PrintStream(File file)
PrintStream(OutputStream out)
PrintStream(String fileName)

// 特有方法
print(Object obj)    // 打印对象
println(Object obj)  // 打印对象并换行
printf(String format, Object... args)  // 格式化打印
PrintWriter(字符打印流)
// 构造方法
PrintWriter(File file)
PrintWriter(Writer out)
PrintWriter(String fileName)

2. 对象流(序列化)

对象流用于对象的序列化和反序列化操作。

前提条件

被序列化的类必须实现Serializable接口:

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    
    // 构造方法、getter、setter...
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return name + ":" + age;
    }
}
ObjectOutputStream(对象输出流)
import java.io.*;

public class ObjectSerialize {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("person.dat"))) {
            
            Person person = new Person("张三", 25);
            oos.writeObject(person);
            System.out.println("对象序列化完成");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
ObjectInputStream(对象输入流)
import java.io.*;

public class ObjectDeserialize {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("person.dat"))) {
            
            Person person = (Person) ois.readObject();
            System.out.println("反序列化对象: " + person);
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

3. 数据流

数据流用于读写基本数据类型。

DataOutputStream
import java.io.*;

public class DataStreamWrite {
    public static void main(String[] args) {
        try (DataOutputStream dos = new DataOutputStream(
                new FileOutputStream("data.dat"))) {
            
            dos.writeInt(100);
            dos.writeDouble(3.14);
            dos.writeUTF("Hello World");
            dos.writeBoolean(true);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
DataInputStream
import java.io.*;

public class DataStreamRead {
    public static void main(String[] args) {
        try (DataInputStream dis = new DataInputStream(
                new FileInputStream("data.dat"))) {
            
            int num = dis.readInt();
            double pi = dis.readDouble();
            String str = dis.readUTF();
            boolean flag = dis.readBoolean();
            
            System.out.println("int: " + num);
            System.out.println("double: " + pi);
            System.out.println("String: " + str);
            System.out.println("boolean: " + flag);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4. RandomAccessFile

随机访问文件类,支持对文件的随机读写。

基本用法
import java.io.*;

public class RandomAccessFileDemo {
    public static void main(String[] args) {
        try {
            // 写入数据
            writeData();
            // 读取数据
            readData();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void writeData() throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile("random.dat", "rw")) {
            raf.writeUTF("张三");
            raf.writeInt(25);
            raf.writeUTF("李四");
            raf.writeInt(30);
        }
    }
    
    public static void readData() throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile("random.dat", "r")) {
            // 移动到指定位置读取
            raf.seek(0);
            String name1 = raf.readUTF();
            int age1 = raf.readInt();
            System.out.println(name1 + ": " + age1);
            
            String name2 = raf.readUTF();
            int age2 = raf.readInt();
            System.out.println(name2 + ": " + age2);
        }
    }
}

IO异常处理

常见IO异常类型

异常类描述
IOExceptionIO操作的通用异常
FileNotFoundException文件未找到异常
EOFException意外到达文件或流末尾
UTFDataFormatExceptionUTF格式数据异常
InvalidClassException序列化版本不匹配

IO异常处理最佳实践

1. try-with-resources(推荐)
public class IOExceptionHandling {
    public static void copyFile(String source, String target) {
        // 自动资源管理
        try (FileInputStream fis = new FileInputStream(source);
             FileOutputStream fos = new FileOutputStream(target)) {
            
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            
        } catch (FileNotFoundException e) {
            System.err.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("IO操作失败: " + e.getMessage());
        }
    }
}
2. 传统异常处理方式
public class TraditionalExceptionHandling {
    public static void copyFile(String source, String target) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try {
            fis = new FileInputStream(source);
            fos = new FileOutputStream(target);
            
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            
        } catch (FileNotFoundException e) {
            System.err.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("IO操作失败: " + e.getMessage());
        } finally {
            // 确保资源被正确关闭
            try {
                if (fis != null) fis.close();
            } catch (IOException e) {
                System.err.println("关闭输入流失败: " + e.getMessage());
            }
            
            try {
                if (fos != null) fos.close();
            } catch (IOException e) {
                System.err.println("关闭输出流失败: " + e.getMessage());
            }
        }
    }
}

总结

IO流选择指南

场景推荐流类型说明
文本文件读写FileReader/FileWriter + 缓冲流字符流处理文本更方便
二进制文件操作FileInputStream/FileOutputStream + 缓冲流字节流处理二进制数据
网络数据传输字节流 + 转换流网络传输基于字节
对象持久化ObjectInputStream/ObjectOutputStream序列化和反序列化
大文件处理缓冲流提高效率
随机访问RandomAccessFile支持随机定位

性能优化建议

  1. 使用缓冲流:减少系统调用次数,提高IO效率
  2. 合理设置缓冲区大小:根据文件大小和内存情况调整
  3. 及时关闭流:使用try-with-resources或finally块
  4. 批量操作:使用数组读写而非单个字节/字符
  5. 避免频繁的小数据读写:合并读写操作

最佳实践

  1. 资源管理:始终确保流被正确关闭
  2. 异常处理:捕获具体的异常类型并提供有意义的错误信息
  3. 编码处理:明确指定字符编码,避免乱码问题
  4. 线程安全:IO流通常不是线程安全的,多线程环境需要同步
  5. 内存管理:处理大文件时注意内存使用,避免一次性读取过多数据

通过掌握Java IO流的核心概念和常用类,可以高效地处理各种输入输出操作,为Java应用程序提供强大的文件处理能力。