File文件

1.读取文件内容

读取方法 底层操作(核心) 上层处理(对字节的后续操作) 适用场景
read 读取字节到 &mut [u8] 数组 直接使用字节(不做任何转换) 二进制文件分块读、大文件
read_to_end 读取所有字节到Vec<u8> 直接使用字节(保留原始字节流) 二进制文件全读、小文件
read_to_string 读取所有字节到Vec<u8> 按 UTF-8 编码解码成 String 文本文件全读(.txt、.rs)

1.1 read_to_end读取

读取变为Vec<u8>

use std::fs::File;
use std::io::Read;

fn main() {
    let path = "a.txt";
    let mut fs = File::open(path).unwrap();
    let mut b = Vec::new();
    fs.read_to_end(&mut b).unwrap();
    // 获取的是byte类型
    println!("{:?}", b);
    // 把读取的内容转为String
    println!("{}",String::from_utf8(b).unwrap());
}
[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
hello world

1.2 read_to_sting

读取所有字节到 Vec<u8>转为String

use std::fs::File;
use std::io::Read;

fn main() {
    let path = "a.txt";
    let mut fs = File::open(path).unwrap();
    let mut s = String::new();
    fs.read_to_string(&mut s).unwrap();
    // 获取的是String类型
    println!("{:?}", s);
}
"hello world"

1.3 read

读取字节到 &mut [u8] 数组

1.3.1 注意

⚠️注意这样和 read_to_end 没有本质区别,还是会导致s内存溢出

use std::fs::File;
use std::io::Read;

fn main() {
    let path = "a.txt";
    let mut fs = File::open(path).unwrap();
    let mut buf = [0u8; 4]; // 每次读 4 字节的缓冲区
    let mut s = Vec::new();
    loop {
        match fs.read(&mut buf) {
            Ok(0) => break, // 字节读完(EOF)
            Ok(n) => {
                // println!("本次读 {} 字节:{:x?}", n, &buf[0..n]);
                s.extend_from_slice(&buf[0..n]);
            }
            Err(e) => panic!("读取失败:{}", e),
        }
    }
    println!("{:?}", s);
}

1.3.2 正确做法

  • 不需要解析字符(仅处理字节):保持当前代码,无问题
  • 需要解析字符(文本处理):避免直接分块转字符串,用「按行读取」或「缓冲编码解析」
use std::fs::File;
use std::io::Read;

fn main() {
    let path = "a.txt";
    let mut fs = File::open(path).unwrap();
    let mut buf = [0u8; 4]; // 每次读 4 字节的缓冲区
    loop {
        match fs.read(&mut buf) {
            Ok(0) => break,
            Ok(n) => {
                let b = &buf[0..n];
                // to do someting
            }
            Err(e) => panic!("读取失败:{}", e),
        }
    }
}

1.3.3 二进制文件入取

分块处理二进制文件(视频、压缩包等)

use std::fs::{File, OpenOptions};
use std::io::{Read, Write};

fn main() {
    // 原图片路径和目标保存路径
    let src_path = "input.png"; // 任意图片格式(PNG/JPG/WEBP 等)
    let dest_path = "output.png";

    // 打开源文件(只读)和目标文件(创建/覆盖写入)
    let mut src_file = File::open(src_path).expect("无法打开源图片");
    let mut dest_file = OpenOptions::new()
        .create(true) // 不存在则创建
        .write(true)  // 可写入
        .truncate(true) // 存在则覆盖
        .open(dest_path)
        .expect("无法创建目标图片");

    // 缓冲区:4KB(优化 IO 效率,契合磁盘页大小)
    let mut buf = [0u8; 4096];

    loop {
        // 分块读取源图片字节
        match src_file.read(&mut buf) {
            Ok(0) => break, // 读取完毕(EOF)
            Ok(n) => {
                // 写入当前块的有效字节(&buf[0..n]),确保字节完整
                dest_file.write_all(&buf[0..n]).expect("写入图片失败");
                dest_file.flush().expect("刷新缓冲区失败"); // 可选:确保数据写入磁盘
            }
            Err(e) => panic!("读取图片失败:{}", e),
        }
    }

    println!("图片复制成功!");
}

1.4 大文件读写BufReader

解决read读取字符分割问题

use std::fs::File;
use std::io::{BufRead, BufReader}; // 引入 BufReader 和 BufRead trait

fn main() {
    let path = "a.txt";
    let file = File::open(path).unwrap();
    let reader = BufReader::new(file); // 包装成带缓冲的读取器

    // 按行读取,每行都是完整的 UTF-8 字符串
    for line in reader.lines() {
        let line = line.unwrap(); // 处理可能的 IO 错误
        println!("完整行:{}", line);
    }
}
完整行:hello world

2.文件写入

2.1 OpenOptions

参数

读写

  • read(true)
    • 开启读取权限
  • write(true)
    • 开启写入权限

写入控制

truncateappend 互斥:不能同时设置为 true,否则编译报错

  • truncate(true)
    • 文件存在时,清空原有内容(覆盖模式)
  • append(true)
    • 文件存在时,在末尾追加内容(不覆盖)
  • create(true)
    • 文件存在则复用,不存在则创建;
  • create_new(true)
    • 仅当文件不存在时创建,存在则报错(禁止覆盖)

权限控制

  • mode(0oXXX)
    • 0o644(默认):所有者可读写,其他用户只读;
    • 0o755:所有者可读写执行,其他用户只读执行(适用于可执行文件);
    • 0o600:仅所有者可读写(敏感文件,如密码)。
use std::fs::OpenOptions;
use std::io::Write;

fn main() {
    let mut file = OpenOptions::new()
        .read(true)		// 开启读取模式
        .write(true)  // 开启追加模式
        .create(true) // 不存在创建文件
        .append(true)	// 开启追加模式(默认true)
        .open("data1.txt")
        .unwrap();

    file.write_all(b"Hello, Rust!").unwrap();
    file.write_all(b"Hello, Rust!").unwrap();
    file.write_all(b"Hello, Rust!").unwrap();
}

append用法

append(true)时,在文件末尾添加内容

.append(false)时,在文件开头添加内容,如果原有文件有内容,会出现替换场景

use std::fs::OpenOptions;
use std::io::Write;

fn main() {
    let mut file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .append(false)	// 关闭追加
        .open("data1.txt")
        .unwrap();

    file.write_all(b"Hello, Rust1!").unwrap();
  	// 多次写入不受append的影响
  	// 开启了 write(true)(写入权限),就可以多次调用 write_all,写入位置由文件指针控制
	  // file.write_all(b"Hello, Rust2!").unwrap();
    // file.write_all(b"Hello, Rust3!").unwrap();
}

2.2 File::create

等于OpenOptions::new().write(true).create(true).truncate(true).open(path)

File::create没有读取权限,如果读取的话会报错

use std::fs::File;
use std::io::Write;

fn main() {
    let mut file = File::create("hello.txt").unwrap();

    file.write_all(b"Hello, Rust!").unwrap();

    // 编译通过,但运行报错:不支持读取
  	// file1.read_to_string(&mut String::new()).unwrap();
}

2.3 fs::write

若只需写一次,fs::write 更简洁

  • 文件不存在,创建
  • 内容覆盖
use std::fs;

fn main() {
    fs::write("data.txt", "Hello, Rust!").unwrap();
}

3.文件删除

使用std::fs::remove_file;

3.1 简单删除

use std::fs::remove_file;

fn main() {
    let file_path = "data1.txt"; // 要删除的文件路径

    // 删除文件(文件不存在或权限不足会 panic)
    remove_file(file_path).unwrap();
    println!("文件 {:?} 删除成功!", file_path);
}

3.2 优雅错误处理

remove_file 返回 io::Result<()>,实际项目中应避免 unwrap,而是处理可能的错误(如文件不存在、权限不足):

use std::fs::remove_file;
use std::io::ErrorKind;

fn main() {
    let file_path = "data1.txt";

    match remove_file(file_path) {
        Ok(_) => println!("文件 {:?} 删除成功!", file_path),
        Err(e) => match e.kind() {
            ErrorKind::NotFound => eprintln!("错误:文件 {:?} 不存在", file_path),
            ErrorKind::PermissionDenied => eprintln!("错误:没有删除 {:?} 的权限", file_path),
            _ => eprintln!("删除文件失败:{}", e),
        },
    }
}

e.kind()

kind()std::io::Error 类型特有的方法—— 只有当错误是「IO 相关错误」(比如文件操作、网络请求、权限问题等)时,才能调用 e.kind() 获取 ErrorKind(错误类别枚举);其他类型的错误(比如解析错误、自定义错误)没有这个方法,直接调用会编译报错

  • 文件操作:fs::remove_fileFile::openOpenOptions::openfs::read_to_end 等;
  • 网络操作:std::net::TcpStream::connectudp::send_to 等;
  • 标准输入输出:std::io::stdin().read_line()std::io::stdout().write_all() 等;
  • 管道、进程通信等其他 IO 操作。

std::io::ErrorKind常用错误

ErrorKind 取值 含义
NotFound 文件 / 路径不存在
PermissionDenied 权限不足(读 / 写 / 删除权限不够)
InvalidInput 无效输入(如传入非法路径格式)
BrokenPipe 管道断开
ConnectionRefused 网络连接被拒绝
IsADirectory 操作对象是目录(如用 remove_file 删目录)
NotADirectory 操作对象不是目录(如用 remove_dir 删文件)
AlreadyExists 文件 / 目录已存在(如 create_new(true) 时)

4.文件权限调整

需要 sudo 的情况:文件属于其他用户(如 root),或你没有修改权限,比如修改 /etc/passwd(root 所有),必须用 sudo cargo run 运行程序,否则报 PermissionDenied 错误

编译后也一样,sudo ./target/debug/your_program

use std::fs;
use std::os::unix::fs::PermissionsExt; // 必须引入,否则没有 from_mode 方法
use std::io::ErrorKind;

fn main() {
    let file_path = "data.txt";

    // 1. 定义目标权限(八进制,0o 开头)
    let new_perms = fs::Permissions::from_mode(0o644); // 0o644 = rw-r--r--

    // 2. 应用权限到文件
    match fs::set_permissions(file_path, new_perms) {
        Ok(_) => println!("文件 {:?} 权限修改为 0o644 成功", file_path),
        Err(e) => match e.kind() {
            ErrorKind::NotFound => eprintln!("错误:文件 {:?} 不存在", file_path),
            ErrorKind::PermissionDenied => eprintln!("错误:权限不足,需用 sudo 运行程序"),
            _ => eprintln!("修改权限失败:{}", e),
        },
    }
}
posted @ 2025-11-11 00:00  lxd670  阅读(9)  评论(0)    收藏  举报