如何读取非UTF8编码的csv文件?
How to read a non-UTF8 encoded csv file?
有了 csv crate 和最新的 Rust 版本 1.31.0,我想用 ANSI (Windows 1252) 编码读取 CSV 文件,就像在 UTF-8 中一样容易。
在 Vec<u8>
:
中阅读整个文件后,我尝试过的事情(没有运气)
的确,在我公司,我们有很多 CSV 文件,ANSI 编码。
此外,如果可能的话,我希望不要将整个文件加载到 Vec<u8>
中,而是逐行读取(CRLF
结尾),因为许多文件都很大( 50 Mb 或更多...)。
在文件Cargo.toml中,我有这个依赖:
[dependencies]
csv = "1"
test.csv由以下内容组成,保存为Windows-1252编码:
Café;au;lait
Café;au;lait
main.rs文件中的代码:
extern crate csv;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::process;
fn example() -> Result<(), Box<Error>> {
let file_name = r"test.csv";
let file_handle = File::open(Path::new(file_name))?;
let reader = BufReader::new(file_handle);
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b';')
.from_reader(reader);
// println!("ANSI");
// for result in rdr.byte_records() {
// let record = result?;
// println!("{:?}", record);
// }
println!("UTF-8");
for result in rdr.records() {
let record = result?;
println!("{:?}", record);
}
Ok(())
}
fn main() {
if let Err(err) = example() {
println!("error running example: {}", err);
process::exit(1);
}
}
输出为:
UTF-8
error running example: CSV parse error: record 0 (line 1, field: 0, byte: 0): invalid utf-8: invalid UTF-8 in field 0 near byte index 3
error: process didn't exit successfully: `target\debug\test-csv.exe` (exit code: 1)
当使用rdr.byte_records()
(取消注释代码的相关部分)时,输出为:
ANSI
ByteRecord(["Caf\xe9", "au", "lait"])
我怀疑这个问题没有具体说明。特别是,不清楚为什么您对 ByteRecord
API 的使用不足。在 csv crate 中,字节记录专门针对这种情况存在,在这种情况下,您的 CSV 数据不是严格的 UTF-8,而是采用与 ASCII 兼容的替代编码,例如 Windows-1252。 (ASCII 兼容编码是一种编码,其中 ASCII 是一个子集。Windows-1252 和 UTF-8 都是 ASCII 兼容的。UTF-16 不是。)上面的代码示例表明您正在使用字节记录, 但没有解释为什么这是不够的。
话虽如此,如果您的目标是将数据转换为 Rust 的字符串数据类型 (String
/&str
),那么您的唯一选择是将 CSV 数据的内容从 Windows-1252 转码为 UTF-8。这是必要的,因为 Rust 的字符串数据类型使用 UTF-8 作为其在内存中的表示。您不能使用 Windows-1252 编码的 Rust String
/&str
,因为 Windows-1252 不是 UTF-8 的子集。
其他评论建议使用 encoding
箱子。但是,我建议使用专门针对网络的 encoding_rs
, if your use case aligns with the same use cases solved by the Encoding Standard。幸运的是,我相信存在这样的对齐方式。
为了满足您以流式方式读取 CSV 数据而不首先将全部内容加载到内存中的标准,您需要使用 encoding_rs
板条箱的包装器来为您实现流式解码。 encoding_rs_io
crate provides this for you. (It's used inside of ripgrep 在搜索 UTF-8 之前进行快速流解码。)
这是一个示例程序,使用 Rust 2018 将上述所有内容放在一起:
use std::fs::File;
use std::process;
use encoding_rs::WINDOWS_1252;
use encoding_rs_io::DecodeReaderBytesBuilder;
fn main() {
if let Err(err) = try_main() {
eprintln!("{}", err);
process::exit(1);
}
}
fn try_main() -> csv::Result<()> {
let file = File::open("test.csv")?;
let transcoded = DecodeReaderBytesBuilder::new()
.encoding(Some(WINDOWS_1252))
.build(file);
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b';')
.from_reader(transcoded);
for result in rdr.records() {
let r = result?;
println!("{:?}", r);
}
Ok(())
}
与 Cargo.toml
:
[package]
name = "so53826986"
version = "0.1.0"
edition = "2018"
[dependencies]
csv = "1"
encoding_rs = "0.8.13"
encoding_rs_io = "0.1.3"
并且输出:
$ cargo run --release
Compiling so53826986 v0.1.0 (/tmp/so53826986)
Finished release [optimized] target(s) in 0.63s
Running `target/release/so53826986`
StringRecord(["Café", "au", "lait"])
特别是,如果您将 rdr.records()
换成 rdr.byte_records()
,那么我们可以更清楚地看到发生了什么:
$ cargo run --release
Compiling so53826986 v0.1.0 (/tmp/so53826986)
Finished release [optimized] target(s) in 0.61s
Running `target/release/so53826986`
ByteRecord(["Caf\xc3\xa9", "au", "lait"])
即,您的输入包含 Caf\xE9
,但字节记录现在包含 Caf\xC3\xA9
。这是将 233
的 Windows-1252 代码点值(编码为其文字字节,\xE9
)转换为 U+00E9 LATIN SMALL LETTER E WITH ACUTE
的结果,后者被 UTF-8 编码为 \xC3\xA9
.
有了 csv crate 和最新的 Rust 版本 1.31.0,我想用 ANSI (Windows 1252) 编码读取 CSV 文件,就像在 UTF-8 中一样容易。
在 Vec<u8>
:
的确,在我公司,我们有很多 CSV 文件,ANSI 编码。
此外,如果可能的话,我希望不要将整个文件加载到 Vec<u8>
中,而是逐行读取(CRLF
结尾),因为许多文件都很大( 50 Mb 或更多...)。
在文件Cargo.toml中,我有这个依赖:
[dependencies]
csv = "1"
test.csv由以下内容组成,保存为Windows-1252编码:
Café;au;lait
Café;au;lait
main.rs文件中的代码:
extern crate csv;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::process;
fn example() -> Result<(), Box<Error>> {
let file_name = r"test.csv";
let file_handle = File::open(Path::new(file_name))?;
let reader = BufReader::new(file_handle);
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b';')
.from_reader(reader);
// println!("ANSI");
// for result in rdr.byte_records() {
// let record = result?;
// println!("{:?}", record);
// }
println!("UTF-8");
for result in rdr.records() {
let record = result?;
println!("{:?}", record);
}
Ok(())
}
fn main() {
if let Err(err) = example() {
println!("error running example: {}", err);
process::exit(1);
}
}
输出为:
UTF-8
error running example: CSV parse error: record 0 (line 1, field: 0, byte: 0): invalid utf-8: invalid UTF-8 in field 0 near byte index 3
error: process didn't exit successfully: `target\debug\test-csv.exe` (exit code: 1)
当使用rdr.byte_records()
(取消注释代码的相关部分)时,输出为:
ANSI
ByteRecord(["Caf\xe9", "au", "lait"])
我怀疑这个问题没有具体说明。特别是,不清楚为什么您对 ByteRecord
API 的使用不足。在 csv crate 中,字节记录专门针对这种情况存在,在这种情况下,您的 CSV 数据不是严格的 UTF-8,而是采用与 ASCII 兼容的替代编码,例如 Windows-1252。 (ASCII 兼容编码是一种编码,其中 ASCII 是一个子集。Windows-1252 和 UTF-8 都是 ASCII 兼容的。UTF-16 不是。)上面的代码示例表明您正在使用字节记录, 但没有解释为什么这是不够的。
话虽如此,如果您的目标是将数据转换为 Rust 的字符串数据类型 (String
/&str
),那么您的唯一选择是将 CSV 数据的内容从 Windows-1252 转码为 UTF-8。这是必要的,因为 Rust 的字符串数据类型使用 UTF-8 作为其在内存中的表示。您不能使用 Windows-1252 编码的 Rust String
/&str
,因为 Windows-1252 不是 UTF-8 的子集。
其他评论建议使用 encoding
箱子。但是,我建议使用专门针对网络的 encoding_rs
, if your use case aligns with the same use cases solved by the Encoding Standard。幸运的是,我相信存在这样的对齐方式。
为了满足您以流式方式读取 CSV 数据而不首先将全部内容加载到内存中的标准,您需要使用 encoding_rs
板条箱的包装器来为您实现流式解码。 encoding_rs_io
crate provides this for you. (It's used inside of ripgrep 在搜索 UTF-8 之前进行快速流解码。)
这是一个示例程序,使用 Rust 2018 将上述所有内容放在一起:
use std::fs::File;
use std::process;
use encoding_rs::WINDOWS_1252;
use encoding_rs_io::DecodeReaderBytesBuilder;
fn main() {
if let Err(err) = try_main() {
eprintln!("{}", err);
process::exit(1);
}
}
fn try_main() -> csv::Result<()> {
let file = File::open("test.csv")?;
let transcoded = DecodeReaderBytesBuilder::new()
.encoding(Some(WINDOWS_1252))
.build(file);
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b';')
.from_reader(transcoded);
for result in rdr.records() {
let r = result?;
println!("{:?}", r);
}
Ok(())
}
与 Cargo.toml
:
[package]
name = "so53826986"
version = "0.1.0"
edition = "2018"
[dependencies]
csv = "1"
encoding_rs = "0.8.13"
encoding_rs_io = "0.1.3"
并且输出:
$ cargo run --release
Compiling so53826986 v0.1.0 (/tmp/so53826986)
Finished release [optimized] target(s) in 0.63s
Running `target/release/so53826986`
StringRecord(["Café", "au", "lait"])
特别是,如果您将 rdr.records()
换成 rdr.byte_records()
,那么我们可以更清楚地看到发生了什么:
$ cargo run --release
Compiling so53826986 v0.1.0 (/tmp/so53826986)
Finished release [optimized] target(s) in 0.61s
Running `target/release/so53826986`
ByteRecord(["Caf\xc3\xa9", "au", "lait"])
即,您的输入包含 Caf\xE9
,但字节记录现在包含 Caf\xC3\xA9
。这是将 233
的 Windows-1252 代码点值(编码为其文字字节,\xE9
)转换为 U+00E9 LATIN SMALL LETTER E WITH ACUTE
的结果,后者被 UTF-8 编码为 \xC3\xA9
.