2016-05-18 231 views
4

我正在嘗試高效地解析CSV文件,不需要不必要的內存分配。如何指定切片時在Rust中迭代Vec而不復制?

既然無法索引拉斯特串,我的想法是創建一個已被佔用行中的字符Vec<char>和代表,將需要的字段是Vec位置數&[char]片每行一個struct進一步處理。

我只支持英文,所以不需要Unicode字形。

我抓住從BufReader每一行,收集到我的Vec<char>,然後遍歷字符注意到每個字段切片正確的偏移:

let mut r_line: String; 
let mut char_count: usize; 
let mut comma_count: usize; 
let mut payload_start: usize; 
for stored in &ms7_files { 
    let reader = BufReader::new(File::open(&stored.as_path()).unwrap()); 
    for line in reader.lines() { 
     r_line = line.unwrap().to_string(); 
     let r_chars: Vec<char> = r_line.chars().collect(); 
     char_count = 0; 
     comma_count = 0; 
     payload_start = 0; 
     for chara in r_chars { 
      char_count += 1; 
      if chara == ',' { 
       comma_count += 1; 
       if comma_count == 1 { 
        let r_itemid = &r_chars[0..char_count - 1]; 
        payload_start = char_count + 1; 
       } else if comma_count == 2 { 
        let r_date = &r_chars[payload_start..char_count - 1]; 
        let r_payload = & r_chars[payload_start..r_line.len() - 1]; 
       } 
      } 
     } 
     // Code omitted here to initialize a struct described in my 
     // text above and add it to a Vec for later processing 
    } 
} 

所有順順當當進入到內部if測試的代碼comma_count我試圖在Vec中創建char片。當我嘗試編譯時,對於創建片的每次嘗試,我都會遇到可怕的:

proc_sales.rs:87:23: 87:30 error: use of moved value: `r_chars` [E0382] 
proc_sales.rs:87      let r_itemid = &r_chars[0..char_count - 1]; 
                 ^~~~~~ 
proc_sales.rs:87:23: 87:30 help: run `rustc --explain E0382` to see a detailed explanation 
proc_sales.rs:82:17: 82:24 note: `r_chars` moved here because it has type `collections::vec::Vec<char>`, which is non-copyable 
proc_sales.rs:82   for chara in r_chars { 
            ^~~~~~~ 

。我基本可以理解編譯器爲什麼抱怨。我試圖找出一個更好的策略來收集和處理這些數據,而不需要大量的複製和克隆。哎呀,如果我可以保留BufReader擁有的原始String(對於每個文件行),並且堅持切片,我會!

請隨時對修正上述代碼發表評論以及針對此問題的其他方法的建議,以限制不必要的複製。

+1

我無法測試,因爲我在火車上..但我敢打賭,因爲你的循環實際上是'r_chars.into_iter()'的語法糖,它將取得所有權。如果你明確地在'r_chars.iter()'中顯式使用'chara,那麼它會返回引用。 –

+0

**絕對**使用[csv crate](https://github.com/BurntSushi/rust-csv)。 「在最低級別,解析器可以以大約200 MB /秒的速率解碼CSV。」這需要零分配。 – Shepmaster

+0

*由於我們無法索引Rust中的字符串,* - 確定您可以。您只需使用位於UTF-8字符邊界上的字節偏移量。甚至有像['char_indices']這樣的迭代器(http://doc.rust-lang.org/std/primitive.str.html#method.char_indices)。 – Shepmaster

回答

3

BufReader::lines返回產生Result<String>項目的迭代器。當unwrap被稱爲這樣的項目時,它將始終分配一個新的String(請注意,在line.unwrap().to_string()中,to_string()是多餘的)。

如果你想最小化分配,你可以使用BufReader::read_line

要分割CSV行的字段,您可以使用str::split

這裏是你的代碼的簡化版本:

use std::io::{BufRead, BufReader}; 
use std::fs::File; 

fn main() { 
    let mut line = String::new(); 
    let ms7_files = ["file1.cvs", "file2.cvs"]; 
    for stored in &ms7_files { 
     let mut reader = BufReader::new(File::open(stored).unwrap()); 
     while reader.read_line(&mut line).unwrap() > 0 { 
      // creates a scope to the iterator, so we can call line.clear() 
      { 
       // does not allocate 
       let mut it = line.split(','); 
       // item_id, date and payload are string slices, that is &str 
       let item_id = it.next().expect("no item_id fied"); 
       let date = it.next().expect("no date field"); 
       let payload = it.next().expect("no payload field"); 
       // process fields 
      } 
      // sets len of line to 0, but does not deallocate 
      line.clear() 
     } 
    } 
} 

你也可能想看看various板條箱的CSV文件的工作。

+0

嘿,我很喜歡這個解決方案。清理已分配的字符串完全符合我的問題的意圖。我知道str :: split,但是我只是在試驗低級處理。對於生產,我確定我會使用拆分。 – ezekiel68

1

對於你的問題,正如@Simon Whitehead回答的那樣,r_chars的所有權已經轉移到建立IntoIterator,因此你不能使用它。

修改使用

for chara in &r_chars 
// equivalent to 
// for chara in r_chars.iter() 

和複印*chara你的代碼,只要你想(便宜)將可能修復它。

對於@ malbarbo的回答,如果您的csv包含文本列(本身可能包含換行符),我建議不要使用BufReader::lines

看着crates.io我反而建議使用測試的戰鬥csv或者如果你需要稍微更多的性能,但準備少得多測試一個quick-csv

+0

感謝您修復我的原始設計缺陷的代碼。我可以控制CSV佈局,該佈局不包括每條記錄的換行符,所以我將繼續使用@malbarbo提供的解決方案 – ezekiel68