zoukankan      html  css  js  c++  java
  • 【译】用 Rust 实现 csv 解析-part6

    写入 tab 分隔符的值

    在前面的章节中,我们了解了如何将 CSV 数据输出到标准输出中,如下:

    City,State,Population,Latitude,Longitude
    Davidsons Landing,AK,,65.2419444,-165.2716667
    Kenai,AK,7610,60.5544444,-151.2583333
    Oakman,AL,,33.7133333,-87.3886111
    

    你可能会想:如果都是这么简单地数据,那使用 CSV writer 还有什么意义?CSV writer 的好处是,它能处理所有类型的数据,而不会牺牲数据的完整性。也就是说,它知道何时引用包含特殊 CSV 字符的字段(如逗号或者换行),或在数据中出现的转义字面量引号。CSV writer 还可以方便地配置使用不同地分隔符或引用策略。

    在这一节中,我们将看看如何调整 CSV writer 上的设置。特别地,我们将使用 TSV(“制表符分割值”)来替代 CSV,并且我们要求 CSV writer 引用所有非数字字段。这里有一个例子:

    fn run() -> Result<(), Box<Error>> {
        let mut wtr = csv::WriterBuilder::new()
            .delimiter(b'	')
            .quote_style(csv::QuoteStyle::NonNumeric)
            .from_writer(io::stdout());
    
        wtr.write_record(&["City", "State", "Population", "Latitude", "Longitude"])?;
        wtr.write_record(&["Davidsons Landing", "AK", "", "65.2419444", "-165.2716667"])?;
        wtr.write_record(&["Kenai", "AK", "7610", "60.5544444", "-151.2583333"])?;
        wtr.write_record(&["Oakman", "AL", "", "33.7133333", "-87.3886111"])?;
    
        wtr.flush()?;
        Ok(())
    }
    

    编译并运行这个例子:

    $ cargo build
    $ ./target/debug/csvtutor
    "City"  "State" "Population"    "Latitude"      "Longitude"
    "Davidsons Landing"     "AK"    ""      65.2419444      -165.2716667
    "Kenai" "AK"    7610    60.5544444      -151.2583333
    "Oakman"        "AL"    ""      33.7133333      -87.3886111
    

    在本例中,我们使用了一个新类型 QuoteStyleQuoteStyle 类型表示你可以使用不同的引用策略。默认情况下,只在必要时才向字段值周围添加引号。这可能适用于大多数例子,但你也可以一直在字段两边添加引号,或不添加引号,或者只在非数字两边加引号。

    使用 Serde 写入

    正如 CSV reader 支持将数据自动反序列化为 Rust 类型数据一样,CSV writer 也支持那样使用 Serde 将 Rust 类型数据序列化为 CSV 记录数据。在本节中,我们就学习怎么使用它。

    与读一样,我们先看看如何序列化一个 Rust 元组。

    fn run() -> Result<(), Box<Error>> {
        let mut wtr = csv::Writer::from_writer(io::stdout());
    
        // 我们仍然需要手动地写入头部
        wtr.write_record(&["City", "State", "Population", "Latitude", "Longitude"])?;
    
        // 但现在我们可以通过提供的常用的 Rust 值,写入记录。
        //
        // 注意,奇数列的 `None::<u64>` 预发是必须的,因为 `None` 本身没有具体的类型,但 Serde 需要一个具体的类型以便进行序列化。也就是说,`None` 的类型 `Option<T>`,而 `None::<u64>` 的类型是 `Option<u64>`。
        wtr.serialize(("Davidsons Landing", "AK", None::<u64>, 65.2419444, -165.2716667))?;
        wtr.serialize(("Kenai", "AK", Some(7610), 60.5544444, -151.2583333))?;
        wtr.serialize(("Oakman", "AL", None::<u64>, 33.7133333, -87.3886111))?;
    
        wtr.flush()?;
        Ok(())
    }
    

    编译并运行这个程序,期望的输出如下:

    $ cargo build
    $ ./target/debug/csvtutor
    City,State,Population,Latitude,Longitude
    Davidsons Landing,AK,,65.2419444,-165.2716667
    Kenai,AK,7610,60.5544444,-151.2583333
    Oakman,AL,,33.7133333,-87.3886111
    

    在上面的例子中,需要注意的关键点是,使用 serialize 而不是 write_record 进行写入数据。特别地,write_record 在写入仅包含字符串数据的简单记录时使用。另一方面,当数据包含更复杂的值时,如数字、浮点数、可选值时,则使用 serialize。当然,你总是可以将复杂的值转为字符串,然后再使用 write_record 统一写入,但 Serde 可以为你自动的完成上面那些繁琐的工作。

    和读一样,我们也可以将自定义结构序列化为 CSV 记录。这样的好处是,结构体中的字段将被识别为头记录。

    要将自定义结构写入 CSV 记录,我们需要再次使用 serde_derive crate。正如前面章节的使用 Serde 读取数据 所述,我们需要在我们的 Cargo.toml 中的 [dependencies] 区块下加上两个 crate 依赖声明(如果没有就加上)。

    serde = "1"
    serde_derive = "1"
    

    我们还需要在代码中加上两行外部库的引入代码 extern crate ,如下方所示:

    extern crate csv;
    extern crate serde;
     #[macro_use]
    extern crate serde_derive;
    
    use std::error::Error;
    use std::io;
    use std::process;
    
    // 记住结构体可以派生两个 trait:Serialize 和 Deserialize!
     #[derive(Debug, Serialize)]
     #[serde(rename_all = "PascalCase")]
    struct Record<'a> {
        city: &'a str,
        state: &'a str,
        population: Option<u64>,
        latitude: f64,
        longitude: f64,
    }
    
    fn run() -> Result<(), Box<Error>> {
        let mut wtr = csv::Writer::from_writer(io::stdout());
    
        wtr.serialize(Record {
            city: "Davidsons Landing",
            state: "AK",
            population: None,
            latitude: 65.2419444,
            longitude: -165.2716667,
        })?;
        wtr.serialize(Record {
            city: "Kenai",
            state: "AK",
            population: Some(7610),
            latitude: 60.5544444,
            longitude: -151.2583333,
        })?;
        wtr.serialize(Record {
            city: "Oakman",
            state: "AL",
            population: None,
            latitude: 33.7133333,
            longitude: -87.3886111,
        })?;
    
        wtr.flush()?;
        Ok(())
    }
    
    fn main() {
        if let Err(err) = run() {
            println!("{}", err);
            process::exit(1);
        }
    }
    

    编译并运行这个示例,虽然我们没有显式地写入头部,但输出和上次是一样的。

    $ cargo build
    $ ./target/debug/csvtutor
    City,State,Population,Latitude,Longitude
    Davidsons Landing,AK,,65.2419444,-165.2716667
    Kenai,AK,7610,60.5544444,-151.2583333
    Oakman,AL,,33.7133333,-87.3886111
    

    在这个例子中,可以看到,serialize 方法上被标记上了一个结构体字段名。这样做的话,serialize 将自动写入一个头部(只要其他记录尚未写入),该记录的结构体的字段是按照他们定义的顺序组成。请注意,可以通过 WriterBuilder::has_headers 方法将此行为禁用。

    同样值得指出的是,在 Record 结构体中使用了一个生命周期参数:

    struct Record<'a> {
        city: &'a str,
        state: &'a str,
        population: Option<u64>,
        latitude: f64,
        longitude: f64,
    }
    

    'a 声明周期参数对应于 citystate 字符串切片的生命周期。这表示 Record 结构体包含了 借用 的数据。我们本可以在不借用任何数据的情况下编写结构体,这时也就不需要生命周期参数了:

    struct Record {
        city: String,
        state: String,
        population: Option<u64>,
        latitude: f64,
        longitude: f64,
    }
    

    然而,由于我们使用 String 类型替换 &str 类型,我们现在被迫为我们所写的每条记录中的 citystate 分配一个新的 String 值。这样做本身没有问题,但会有点性能浪费。

    关于序列化的更多示例和详细信息,可以参考 Writer::serialize 方法。

    Pipelining

    • 待续...
  • 相关阅读:
    Windows Server 2003 SP2(32位) 中文版 下载地址 光盘整合方法
    用Recycle()方法对Java对象的重要性
    Lotus中千奇百怪的 $$
    Developing a simple application using steps "User Decision" and "Mail"(1) 沧海
    沟通中的情绪管理(演讲稿) 沧海
    人只有在压力之下,才可能成功,没做一件事,都必须成功,不许言败 沧海
    什么是IDOC,以及IDOC的步骤 沧海
    VS2008 Professional Edition CHS中的deffactory.dat读取错误 沧海
    Including custom text in the step "User Decision" 沧海
    SAP Upgrade Strategy 沧海
  • 原文地址:https://www.cnblogs.com/ishenghuo/p/14321482.html
Copyright © 2011-2022 走看看