read lines
ナイーブなアプローチ
これは、ファイルから行を読み取るための初心者による最初の実装としては、妥当な最初の試みかもしれません。
#![allow(unused)] fn main() { use std::fs::read_to_string; fn read_lines(filename: &str) -> Vec<String> { let mut result = Vec::new(); for line in read_to_string(filename).unwrap().lines() { result.push(line.to_string()) } result } }
lines() メソッドはファイル内の行に対するイテレーターを返すため、インラインで map を実行して結果を collect することもでき、より簡潔で流れるような式になります。
#![allow(unused)] fn main() { use std::fs::read_to_string; fn read_lines(filename: &str) -> Vec<String> { read_to_string(filename) .unwrap() // ファイル読み取りエラーが発生する可能性がある場合はパニックする .lines() // 文字列を文字列スライスのイテレーターに分割する .map(String::from) // 各スライスを文字列にする .collect() // それらをまとめてベクターに集める } }
上記のどちらの例でも、lines() から返される &str 参照を、所有される型である String に変換する必要があることに注意してください。それぞれ .to_string() と String::from を使用しています。
より効率的なアプローチ
ここでは、開いている File の所有権を BufReader 構造体に渡します。BufReader は内部バッファーを使用して中間的なアロケーションを削減します。
また、各行についてメモリ上に新しい String オブジェクトを割り当てるのではなく、イテレーターを返すように read_lines を更新します。
use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; fn main() { // File hosts.txt は現在のパスに存在している必要がある if let Ok(lines) = read_lines("./hosts.txt") { // イテレーターを消費し、(Optional の)String を返す for line in lines.map_while(Result::ok) { println!("{}", line); } } } // 出力は Result でラップされ、エラーに対するマッチングを可能にしている。 // ファイルの行の Reader に対する Iterator を返す。 fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>> where P: AsRef<Path>, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) }
このプログラムを実行すると、単純に各行が個別に出力されます。
$ echo -e "127.0.0.1\n192.168.0.1\n" > hosts.txt
$ rustc read_lines.rs && ./read_lines
127.0.0.1
192.168.0.1
(File::open は引数としてジェネリックな AsRef<Path> を期待するため、where キーワードを使用して、ジェネリックな read_lines() メソッドにも同じジェネリック制約を定義していることに注意してください。)
このプロセスは、ファイルの内容全体を含む String をメモリ上に作成するよりも効率的です。これは、特に大きなファイルを扱う場合にパフォーマンス上の問題を引き起こす可能性があります。