1个不稳定版本
0.1.0 | 2023年1月5日 |
---|
#312 在 内存管理
23KB
288 行
Rust中的非分配CSV解析器
本库的关键特性。
- 遵循 RFC 4180。
- 不在堆上分配任何内存。
- 不会抛出异常。
快速示例
fn test_record() {
let str =
"aa,bb,cc,dd\r\n\
ee,ff,gg,hh\r\n";
let mut parser = rcsv::Parser::new();
parser.parse::<10>(str.as_bytes(), |index, fields| {
assert!(index < 2);
if index == 0 {
assert!(fields[0] == "aa".as_bytes());
assert!(fields[3] == "dd".as_bytes());
} else {
assert!(fields[0] == "ee".as_bytes());
assert!(fields[3] == "hh".as_bytes());
}
});
}
Parser::parse::<10>()
调用为每行分配足够的空间以容纳10个字段。任何额外的字段将被丢弃,不会产生任何错误。
用户指南
解析字符串
Parser::parse()
方法解析以无符号字节数组形式提供的CSV数据。
您需要估计每行期望的字段数量。这是在编译时静态分配空间所需的。您始终可以采取谨慎的态度。例如,如果您期望每行有4个字段,您可以将解析器配置为10个字段。
fn test_record() {
let str =
"aa,bb,cc,dd\r\n\
ee,ff,gg,hh\r\n";
let mut parser = rcsv::Parser::new();
parser.parse::<10>(str.as_bytes(), |index, fields| {
println!("Record no: {}", index);
println!("Field count: {}", fields.len());
});
}
应该打印
Record no: 0
Field count: 4
Record no: 1
Field count: 4
parse()
方法接收两个参数。
- 要解析的数据。
- 一个闭包,它对每个记录(CSV中的行)进行调用。
闭包接收两个参数
- 记录的索引。第一行的索引为0。
- 字段数组。每个字段都是一个无符号字节数组
&[u8]
。
如果记录的字段数多于解析器配置的字段数,则多余的字段将被丢弃,并且不会报告给lambda。
fn test_uneven() {
let str =
"aa,bb,cc,dd\r\n\
ee,ff,gg\r\n\
hh,ii\r\n";
let mut parser = rcsv::Parser::new();
parser.parse::<3>(str.as_bytes(), |index, fields| {
assert!(index < 3);
if index == 0 {
assert!(fields.len() == 3);
assert!(fields[0] == "aa".as_bytes());
assert!(fields[2] == "cc".as_bytes());
} else if index == 1 {
assert!(fields.len() == 3);
assert!(fields[0] == "ee".as_bytes());
assert!(fields[1] == "ff".as_bytes());
} else {
assert!(fields.len() == 2);
assert!(fields[0] == "hh".as_bytes());
assert!(fields[1] == "ii".as_bytes());
}
});
}
解析CSV文件
内存映射用于从CSV文件中读取。
假设您有一个名为 test.csv
的文件,如下所示。
aa,bb,cc
dd,ee,ff
gg,hh,ii
我们可以这样读取文件。
fn test_memory_map_reader() {
let mapper = match rcsv::mmap::FileMapper::new("test.csv") {
Ok(r) => r,
Err(e) => {
panic!("{}", e);
}
};
let data = mapper.get_bytes();
let mut parser = rcsv::Parser::new();
parser.parse::<3>(data, |index, fields| {
assert!(index < 3);
if index == 0 {
assert!(fields.len() == 3);
assert!(fields[0] == "aa".as_bytes());
assert!(fields[2] == "cc".as_bytes());
} else if index == 1 {
assert!(fields.len() == 3);
assert!(fields[0] == "dd".as_bytes());
assert!(fields[1] == "ee".as_bytes());
} else {
assert!(fields.len() == 3);
assert!(fields[0] == "gg".as_bytes());
assert!(fields[1] == "hh".as_bytes());
}
});
}
标准一致性
该库符合RFC 4180。它稍微放宽了标准以使其更灵活。这些偏差将在下面讨论。
UNIX换行符
RFC 4180要求每行以CRLF(\r\n
)结束。在Linux和macOS中,文件通常以仅LF结尾。库可以容忍这样的文件。
空格在转义字段周围
RFC明确指出空格是字段的一部分。它们不应被忽略。然而,不清楚转义字段的引号前后是否有空格。ABNF语法似乎表明不应该有空格。解析器会丢弃转义字段引号前后空格。
在下面的示例中,未转义的字段 aa
和 cc
周围有空格。这些空格被保留。然而,对于像 "bb"
这样的转义字段,引号之外的空间将被忽略。
aa, "bb", cc ,
" dd " , " ee "
fn test_space() {
let str = r#" aa, "bb", cc ,
" dd ", " ee "
"#;
let mut parser = rcsv::Parser::new();
parser.parse::<10>(str.as_bytes(), |index, fields| {
assert!(index < 2);
if index == 0 {
assert!(fields[0] == " aa".as_bytes());
assert!(fields[1] == "bb".as_bytes());
assert!(fields[2] == " cc ".as_bytes());
} else {
assert!(fields[0] == " dd ".as_bytes());
assert!(fields[1] == " ee ".as_bytes());
}
});
}
取消转义双引号
解析器不会取消转义转义的双引号。我没有找到不分配就能简单做到这一点的方法。在下面的示例中,字段 "b""b"
是未经取消转义双引号报告给lambda的。
aa,"b""b",cc,"d,d"
ee,ff,"g
g",hh
fn test_basic_escape() {
let str = r#"aa,"b""b",cc,"d,d"
"ee",ff,"g
g",hh
"#;
let mut parser = rcsv::Parser::new();
parser.parse::<10>(str.as_bytes(), |index, fields| {
assert!(index < 2);
if index == 0 {
assert!(fields[1] == "b\"\"b".as_bytes());
assert!(fields[3] == "d,d".as_bytes());
} else {
assert!(fields[0] == "ee".as_bytes());
assert!(fields[2] == "g\ng".as_bytes());
assert!(fields[3] == "hh".as_bytes());
}
});
}
内存安全
在Rust中,数组索引运算符 [index]
进行边界检查。切片运算符 [start..stop]
也进行相同的检查。在该方面,库应该是内存安全的。
依赖关系
~215KB