文件内容读写与数据流操作详解

22 阅读6分钟

一、数据流基础概念

1.1 流(Stream)的基本原理

流对象是Java IO操作的核心抽象,其工作原理类似于水流:

  • 连续数据流动:数据像水流一样连续传输
  • 可控制流量:支持按需读取/写入不同大小的数据块
  • 单向性:输入流负责读取,输出流负责写入

核心优势

  • 内存效率高,支持大文件处理
  • 灵活的读写控制策略
  • 统一的编程接口

二、字节流操作

2.1 InputStream输入流

InputStream是字节输入流的抽象基类,FileInputStream是其常用实现。

构造方法

java

FileInputStream(File file)      // 通过File对象创建
FileInputStream(String path)    // 通过文件路径创建

核心方法

方法签名功能说明
int read()读取单个字节,返回0-255,结束返回-1
int read(byte[] b)读取字节数组长度的数据
int read(byte[] b, int off, int len)读取指定范围的数据
void close()关闭流释放资源

资源管理的重要性

必须显式关闭流的原因:

  • 文件描述符是有限的操作系统资源
  • 每个打开文件占用文件描述符表的一个表项
  • 不及时释放会导致资源泄漏,影响程序稳定性

实践示例

基础读取示例

java

// 传统try-catch-finally方式(已过时)
InputStream inputStream = null;
try {
    inputStream = new FileInputStream("test.png");
    // 读取操作...
} catch (IOException e) {
    throw new RuntimeException(e);
} finally {
    if (inputStream != null) {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 推荐:try-with-resources(Java 7+)
try (InputStream inputStream = new FileInputStream("test.png")) {
    int byteData;
    while ((byteData = inputStream.read()) != -1) {
        System.out.println(byteData);
    }
} catch (IOException e) {
    throw new RuntimeException(e);
}

批量读取优化

java

try (InputStream inputStream = new FileInputStream("test.png")) {
    byte[] buffer = new byte[1024];  // 1KB缓冲区
    int bytesRead;
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        // 处理读取到的数据
        for (int i = 0; i < bytesRead; i++) {
            System.out.print(buffer[i] + " ");
        }
        System.out.println();
    }
}

Scanner读取文本文件

java

// 适用于文本文件,不适用于二进制文件
try (InputStream inputStream = new FileInputStream("test.txt");
     Scanner scanner = new Scanner(inputStream)) {
    while (scanner.hasNext()) {
        String content = scanner.next();
        System.out.print(content + " ");
    }
}

2.2 OutputStream输出流

OutputStream是字节输出流的抽象基类,FileOutputStream是常用实现。

核心方法

方法签名功能说明
void write(int b)写入单个字节
void write(byte[] b)写入字节数组
void write(byte[] b, int off, int len)写入指定范围数据
void close()关闭输出流
void flush()强制刷新缓冲区

缓冲区机制详解

  • 性能优化:减少直接IO操作次数,提升写入效率
  • 数据安全flush()确保关键数据立即持久化
  • 自动管理:缓冲区满时自动触发写入操作

实践示例

基础写入操作

java

// 覆盖模式写入(默认)
try (OutputStream outputStream = new FileOutputStream("test.txt")) {
    outputStream.write(97);  // 'a'
    outputStream.write(98);  // 'b'
    outputStream.write(98);  // 'b'
    // 结果:abb(覆盖原内容)
}

// 追加模式写入
try (OutputStream outputStream = new FileOutputStream("test.txt", true)) {
    outputStream.write(76);  // 'L'
    // 在原有内容后追加
}

批量写入操作

java

try (OutputStream outputStream = new FileOutputStream("test.txt", true)) {
    byte[] data = {99, 98, 97, 96, 95};  // cba`_
    outputStream.write(data);
}

自动文件创建

java

// 文件不存在时自动创建
try (OutputStream outputStream = new FileOutputStream("new_file.txt")) {
    byte[] data = {99, 98, 97, 96, 95};
    outputStream.write(data);
}

使用PrintWriter增强功能

java

try (OutputStream outputStream = new FileOutputStream("test.txt");
     PrintWriter writer = new PrintWriter(outputStream)) {
    writer.print(98);    // 写入数字
    writer.println(99);  // 写入数字并换行
    writer.flush();      // 确保数据写入
}

三、字符流操作

3.1 字符流与字节流的区别

特性字节流字符流
数据单位字节(8bit)字符(16bit)
编码处理原始二进制自动字符编码转换
适用场景所有文件类型文本文件
核心类InputStream/OutputStreamReader/Writer

3.2 Reader字符输入流

文件读取示例

java

public static void main(String[] args) {
    try (Reader reader = new FileReader("test.txt")) {
        char[] buffer = new char[1024];
        int charsRead;
        while ((charsRead = reader.read(buffer)) != -1) {
            for (int i = 0; i < charsRead; i++) {
                System.out.print(buffer[i]);
            }
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

编码注意事项

  • 确保文件编码与Reader使用编码一致
  • 推荐使用UTF-8编码保证兼容性
  • IDE编码设置与运行环境编码保持一致

3.3 Writer字符输出流

文件写入示例

java

try (Writer writer = new FileWriter("test.txt")) {
    char[] content = {'H', 'E', 'l', 'l', 'o'};
    writer.write(content);
} catch (IOException e) {
    throw new RuntimeException(e);
}

四、综合实践练习

4.1 文件搜索与删除工具

java

public class FileSearchAndDelete {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("请输入要搜索的目录:");
        String rootDir = scanner.next();
        File directory = new File(rootDir);
        
        if (!directory.isDirectory()) {
            System.out.println("错误:输入的路径不是目录");
            return;
        }
        
        System.out.println("请输入要搜索的关键字:");
        String keyword = scanner.next();
        
        searchFiles(directory, keyword);
    }
    
    public static void searchFiles(File rootDir, String keyword) {
        File[] files = rootDir.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            System.out.println("正在扫描: " + file.getAbsolutePath());
            
            if (file.isFile()) {
                processFile(file, keyword);
            } else {
                searchFiles(file, keyword); // 递归搜索子目录
            }
        }
    }
    
    public static void processFile(File file, String keyword) {
        if (file.getName().contains(keyword)) {
            System.out.println("发现匹配文件: " + file.getName());
            System.out.println("是否删除?(输入 y 确认删除)");
            
            Scanner scanner = new Scanner(System.in);
            String confirmation = scanner.next();
            
            if ("y".equalsIgnoreCase(confirmation)) {
                if (file.delete()) {
                    System.out.println("文件删除成功");
                } else {
                    System.out.println("文件删除失败");
                }
            }
        }
    }
}

4.2 文件复制工具

java

public class FileCopyUtility {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("请输入源文件路径:");
        String sourcePath = scanner.next();
        File sourceFile = new File(sourcePath);
        
        if (!sourceFile.isFile()) {
            System.out.println("错误:源文件不存在或不是文件");
            return;
        }
        
        System.out.println("请输入目标文件路径:");
        String destPath = scanner.next();
        File destFile = new File(destPath);
        
        // 检查目标目录是否存在
        if (!destFile.getParentFile().isDirectory()) {
            System.out.println("错误:目标目录不存在");
            return;
        }
        
        System.out.println("源文件: " + sourceFile.getAbsolutePath());
        System.out.println("目标文件: " + destFile.getAbsolutePath());
        
        // 执行文件复制
        copyFile(sourceFile, destFile);
    }
    
    public static void copyFile(File source, File destination) {
        try (InputStream input = new FileInputStream(source);
             OutputStream output = new FileOutputStream(destination)) {
            
            byte[] buffer = new byte[1024];
            int bytesRead;
            
            while ((bytesRead = input.read(buffer)) != -1) {
                output.write(buffer, 0, bytesRead);
            }
            
            System.out.println("文件复制完成");
            
        } catch (FileNotFoundException e) {
            System.out.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("IO错误: " + e.getMessage());
        }
    }
}

4.3 高级文件内容搜索工具

java

public class AdvancedFileSearch {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("请输入要搜索的目录:");
        String searchDir = scanner.next();
        File directory = new File(searchDir);
        
        if (!directory.isDirectory()) {
            System.out.println("错误:输入的不是有效目录");
            return;
        }
        
        System.out.println("请输入要搜索的关键字:");
        String keyword = scanner.next();
        
        searchContent(directory, keyword);
    }
    
    public static void searchContent(File directory, String keyword) {
        File[] files = directory.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            if (file.isFile()) {
                processFileContent(file, keyword);
            } else {
                searchContent(file, keyword); // 递归搜索子目录
            }
        }
    }
    
    public static void processFileContent(File file, String keyword) {
        // 检查文件名
        if (file.getName().contains(keyword)) {
            System.out.println("文件名包含关键字: " + file.getName());
            return;
        }
        
        // 检查文件内容
        StringBuilder content = new StringBuilder();
        
        try (Reader reader = new FileReader(file)) {
            char[] buffer = new char[1024];
            int charsRead;
            
            while ((charsRead = reader.read(buffer)) != -1) {
                content.append(buffer, 0, charsRead);
            }
            
            if (content.indexOf(keyword) >= 0) {
                System.out.println("文件内容包含关键字: " + file.getName());
            }
            
        } catch (FileNotFoundException e) {
            System.out.println("文件无法访问: " + file.getName());
        } catch (IOException e) {
            System.out.println("读取错误: " + file.getName());
        }
    }
}

五、最佳实践总结

5.1 资源管理

  • 优先使用try-with-resources:确保资源自动关闭
  • 及时释放资源:避免文件描述符泄漏
  • 合理设置缓冲区:平衡内存使用与IO效率

5.2 异常处理

  • 精确捕获异常:区分FileNotFoundException和IOException
  • 提供有意义的错误信息:便于问题定位
  • 资源释放保证:finally块或try-with-resources

5.3 性能优化

  • 合适的缓冲区大小:通常1KB-8KB
  • 批量操作优先:减少系统调用次数
  • 及时刷新缓冲区:关键数据立即持久化

5.4 编码规范

  • 统一字符编码:推荐UTF-8
  • 清晰的命名规范:提高代码可读性
  • 适当的代码注释:说明复杂逻辑

通过掌握这些文件IO操作技术,能够高效处理各种文件读写需求,为后续学习更高级的IO操作(如NIO)奠定坚实基础。