Java 文件操作与IO(下):字节流、字符流实战IO读写

Java 文件操作与IO(下):字节流、字符流实战IO读写

承接上篇File类,本篇重点讲解数据流读写文件内容,InputStream、OutputStream字节流,Scanner、PrintWriter字符封装,附带3个完整实战小案例。

一、IO流的形象理解

把数据比作水池里面的水:

  • 输入流 Input :从水池(文件)往外取水 → 读文件
  • 输出流 Output :往水池(文件)灌水 → 写文件

Java IO分为:

  1. 字节流 :以字节byte为单位,InputStream / OutputStream,处理所有文件(文本、图片、视频)
  2. 字符流:以字符char为单位,专门处理文本文件

注意:InputStreamOutputStream都是抽象类,读取文件使用它的实现类:FileInputStreamFileOutputStream

二、字节输入流 InputStream(读文件)

核心方法:

复制代码
//读1个字节,读到文件末尾返回 -1
int read();

//最多读b.length字节,存入byte数组b;返回读到字节数量;读到末尾返回-1
int read(byte[] b);

//off:数组起始下标,len读取长度
int read(byte[] b, int off, int len);

void close(); //关闭流释放资源

资源关闭:IO流属于操作系统资源,必须关闭;推荐使用 try‑with‑resources语法,自动关闭流,不用手动写close。

FileInputStream构造

复制代码
FileInputStream(File file);
FileInputStream(String pathName);

两种读取文件写法对比

  1. 一次读一个字节(性能差,IO次数多)

    try (InputStream is = new FileInputStream("hello.txt")) {
    int b;
    while ((b = is.read()) != -1) {
    System.out.print((char) b);
    }
    }

  2. 缓冲区数组读取(性能好,推荐)

    try (InputStream is = new FileInputStream("hello.txt")) {
    byte[] buf = new byte[1024]; //缓冲区
    int len;
    while ((len = is.read(buf)) != -1) {
    //buf[0 ~ len‑1] 是本次读到有效数据
    for(int i = 0; i < len; i++){
    System.out.print((char)buf[i]);
    }
    }
    }

直接字节流读取中文很麻烦,需要手动做字符集解码,所以文本文件一般配合Scanner使用。

Scanner包装InputStream读取文本

复制代码
try(InputStream is = new FileInputStream("hello.txt")){
    try(Scanner sc = new Scanner(is,"UTF-8")){
        while(sc.hasNext()){
            String line = sc.next();
            System.out.println(line);
        }
    }
}

三、字节输出流 OutputStream(写文件)

核心API:

复制代码
void write(int b);                //写单个字节
void write(byte[] b);             //把整个字节数组写入
void write(byte[] b,int off,int len); //数组从off位置,写len个字节

void close();
void flush(); //❗刷新缓冲区,把缓冲区数据真正写入磁盘

缓冲区:操作系统为减少IO次数,数据先放到内存缓冲区,缓冲区满才写入磁盘。如果程序结束缓冲区没满,数据会留在内存丢失,必须调用flush()强制刷盘

FileOutputStream:文件字节输出流,打开文件默认覆盖原有内容

示例写文件:

复制代码
try(OutputStream os = new FileOutputStream("output.txt")){
    byte[] data = "你好中国".getBytes("UTF-8");
    os.write(data);
    os.flush(); //强制刷新
}

四、字符便捷打印:OutputStreamWriter + PrintWriter

直接写字节数组不方便,使用OutputStreamWriter把字节流转字符流,再套PrintWriter,就可以使用熟悉的print / println / printf

复制代码
try(OutputStream os = new FileOutputStream("output.txt")){
    try(OutputStreamWriter writer = new OutputStreamWriter(os,"UTF-8")){
        try(PrintWriter pw = new PrintWriter(writer)){
            pw.println("第一行文本");
            pw.printf("%d 号数据\n",100);
            pw.flush(); //一定要刷新缓冲区
        }
    }
}

五、实战练习案例

注意全部使用try‑with‑resources自动释放资源。

案例1:文件复制工具

读取源文件,写入目标文件,支持判断是否覆盖。

复制代码
import java.io.*;
import java.util.Scanner;

public class FileCopyDemo {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        System.out.print("输入源文件路径:");
        String srcPath = sc.next();
        System.out.print("输入目标路径:");
        String destPath = sc.next();

        File src = new File(srcPath);
        File dest = new File(destPath);

        if(!src.exists() || !src.isFile()){
            System.out.println("源文件错误");
            return;
        }
        if(dest.exists()){
            System.out.println("目标文件已存在,是否覆盖 y/n");
            String op = sc.next();
            if(!"y".equalsIgnoreCase(op)){
                System.out.println("复制终止");
                return;
            }
        }

        try(InputStream is = new FileInputStream(src);
            OutputStream os = new FileOutputStream(dest)){
            byte[] buf = new byte[1024];
            int len;
            while((len = is.read(buf)) != -1){
                os.write(buf,0,len);
            }
            os.flush();
        }
        System.out.println("复制完成");
    }
}

案例2:递归扫描目录,按文件名匹配删除文件

文件系统是树结构,使用递归深度优先遍历目录

复制代码
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class ScanDeleteDemo {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        System.out.print("输入扫描根目录:");
        String rootPath = sc.next();
        File root = new File(rootPath);
        if(!root.isDirectory()){
            System.out.println("目录非法");
            return;
        }
        System.out.print("输入文件名匹配关键字:");
        String keyword = sc.next();

        List<File> matchList = new ArrayList<>();
        scanDir(root,keyword,matchList);

        System.out.println("找到"+matchList.size()+"个匹配文件");
        for(File f : matchList){
            System.out.printf("文件:%s 是否删除(y/n) ",f.getCanonicalPath());
            String op = sc.next();
            if("y".equalsIgnoreCase(op)){
                f.delete();
                System.out.println("已删除");
            }
        }
    }

    private static void scanDir(File dir,String keyword,List<File> res){
        File[] files = dir.listFiles();
        if(files == null) return;
        for(File f : files){
            if(f.isDirectory()){
                scanDir(f,keyword,res);
            }else{
                if(f.getName().contains(keyword)){
                    res.add(f);
                }
            }
        }
    }
}

案例3:扫描目录,匹配文件内容包含关键字

注意:读取大文件性能差,不适合超大文件

复制代码
private static boolean contentContains(File file,String keyword) throws IOException{
    StringBuilder sb = new StringBuilder();
    try(InputStream is = new FileInputStream(file);
        Scanner sc = new Scanner(is,"UTF-8")){
        while(sc.hasNextLine()){
            sb.append(sc.nextLine());
        }
    }
    return sb.toString().contains(keyword);
}

六、核心要点总结

  1. try‑with‑resources:实现AutoCloseable接口的流对象,写在try()括号内,自动关闭资源,避免资源泄露;
  2. flush():输出流写操作结束务必flush,防止数据留在缓冲区丢失;
  3. 字节流InputStream/OutputStream处理一切文件;文本优先使用Scanner、PrintWriter封装简化字符编解码;
  4. 遍历文件系统使用递归,listFiles()拿到目录全部子File对象;
  5. read返回-1才代表读到文件末尾,不是返回0。

拓展提醒:Java NIO是另一套IO模型,本篇讲解传统BIO,是面试基础必考内容。

相关推荐
xixingzhe21 小时前
spring boot项目接口访问慢问题解决方案
java·数据库·spring boot
MetaLite1 小时前
SpringBoot整合FastJson2数据脱敏-接口日志与失败降级
java·spring boot·后端
事圆则缓1 小时前
Kotlin 高阶工程化与 Android 深入实践
android·开发语言·kotlin
驭渊的小故事1 小时前
java抽奖项目-奖品上传中传递参数类型的错误
java·开发语言
E_ICEBLUE1 小时前
为 Excel 工作簿和工作表设置密码保护【Python教程】
python·excel
名字还没想好☜1 小时前
Go 时间格式化为什么用 2006-01-02:time.Format/Parse 的参考时间、时区与解析踩坑
开发语言·后端·golang·go
段一凡-华北理工大学1 小时前
高炉智能布料技术与炉料分布优化~系列文章03:布料矩阵解码:环位、角度、圈数与料流的量化控制
java·网络·人工智能·矩阵·高炉智能化·高炉布料矩阵优化·高炉智能布料技术
Dream Cosmos1 小时前
C++ 多态下篇:虚函数表、动态绑定与多态底层原理
开发语言·c++
qfljg4 小时前
OpenRewrite 实战指南
java