如何在bincode序列化文件中为不同记录添加分隔符?
在Bincode编码的文件中添加记录分隔符的正确方式
这个问题挺典型的——bincode作为二进制序列化格式,本身并没有内置的记录分隔机制,直接加普通字符(比如你现在用的\n)其实藏着坑:如果序列化后的二进制数据里恰好包含和分隔符一样的字节(比如员工名字里有换行符),读取时就会错误分割记录,导致反序列化失败。
下面给你两种靠谱的解决思路:
方案1:使用多字节特殊分隔符(规避冲突风险)
既然单字节分隔符可能和数据冲突,我们可以用一段不太可能出现在正常序列化数据中的多字节序列作为分隔符,比如三个连续的0xFF字节。
写入示例代码
use std::fs::OpenOptions; use bincode; #[derive(serde::Serialize, serde::Deserialize)] struct Employee { id: u64, name: String, } fn main() { let emp1 = Employee { id: 1546, name: "abcd".to_string(), }; let emp2 = Employee { id: 7890, name: "wxyz".to_string(), }; let mut file = OpenOptions::new() .write(true) .create(true) .open("employees.bin") .unwrap(); // 定义唯一的多字节分隔符 const RECORD_SEPARATOR: &[u8] = &[0xFF, 0xFF, 0xFF]; // 序列化第一个员工并写入,随后加分隔符 let buf1 = bincode::serialize(&emp1).unwrap(); file.write_all(&buf1).unwrap(); file.write_all(RECORD_SEPARATOR).unwrap(); // 序列化第二个员工并写入,最后一条记录可以选择不加分隔符 let buf2 = bincode::serialize(&emp2).unwrap(); file.write_all(&buf2).unwrap(); file.flush().unwrap(); }
读取示例代码
use std::fs::read; use bincode; #[derive(serde::Serialize, serde::Deserialize)] struct Employee { id: u64, name: String, } fn main() { const RECORD_SEPARATOR: &[u8] = &[0xFF, 0xFF, 0xFF]; let file_content = read("employees.bin").unwrap(); // 按分隔符分割内容,过滤空片段(比如最后一个分隔符后的空数据) let records = file_content.split_inclusive(|chunk| chunk == RECORD_SEPARATOR) .map(|chunk| { // 去掉末尾的分隔符 let data = &chunk[..chunk.len() - RECORD_SEPARATOR.len()]; bincode::deserialize::<Employee>(data).unwrap() }) .collect::<Vec<_>>(); for emp in records { println!("ID: {}, Name: {}", emp.id, emp.name); } }
方案2:先写入记录长度(最可靠的工业级方案)
这种方法完全不需要分隔符,而是遵循二进制文件存储的标准范式:先写序列化后数据的字节长度,再写实际的序列化数据。读取时先读长度,再读取对应长度的字节进行反序列化,彻底避免了分隔符冲突的问题。
写入示例代码(需要byteorder依赖)
use std::fs::OpenOptions; use bincode; use byteorder::{WriteBytesExt, LittleEndian}; #[derive(serde::Serialize, serde::Deserialize)] struct Employee { id: u64, name: String, } fn main() { let emp1 = Employee { id: 1546, name: "abcd".to_string(), }; let emp2 = Employee { id: 7890, name: "wxyz".to_string(), }; let mut file = OpenOptions::new() .write(true) .create(true) .open("employees.bin") .unwrap(); // 写入第一个记录:先写长度(小端序),再写序列化数据 let buf1 = bincode::serialize(&emp1).unwrap(); file.write_u32::<LittleEndian>(buf1.len() as u32).unwrap(); file.write_all(&buf1).unwrap(); // 写入第二个记录 let buf2 = bincode::serialize(&emp2).unwrap(); file.write_u32::<LittleEndian>(buf2.len() as u32).unwrap(); file.write_all(&buf2).unwrap(); file.flush().unwrap(); }
读取示例代码
use std::fs::File; use std::io::{Read, BufReader}; use bincode; use byteorder::{ReadBytesExt, LittleEndian}; #[derive(serde::Serialize, serde::Deserialize)] struct Employee { id: u64, name: String, } fn main() { let file = File::open("employees.bin").unwrap(); let mut reader = BufReader::new(file); let mut records = Vec::new(); loop { // 尝试读取记录长度,到达文件末尾则退出循环 let record_len = match reader.read_u32::<LittleEndian>() { Ok(len) => len as usize, Err(_) => break, }; // 读取对应长度的字节数据 let mut record_buf = vec![0u8; record_len]; reader.read_exact(&mut record_buf).unwrap(); // 反序列化并收集记录 let emp = bincode::deserialize::<Employee>(&record_buf).unwrap(); records.push(emp); } for emp in records { println!("ID: {}, Name: {}", emp.id, emp.name); } }
补充说明
你原来用\n作为分隔符的方案,在员工名字不包含换行符的场景下暂时能用,但一旦出现带换行的名字,就会直接崩溃。所以如果追求稳定性,优先选择方案2;如果一定要用分隔符,就选多字节的特殊序列,尽量降低冲突概率。
内容的提问来源于stack exchange,提问作者Devashish Dixit
相关产品推荐
相关产品推荐

