この記事はWanoアドベントカレンダーの3日目の記事です。
TL;DR
- どんなAPIがあるの?
- 注意事項
- 標準の入出力APIのうち最もレイヤが低いAPIはバッファリングさえも除かれている
- この事を把握せずに自分で頑張ろうとすると性能が出ない事が多い
- BufReader/BufWriterを使えばバッファリングされるので基本はこれを使う
- 標準の入出力APIのうち最もレイヤが低いAPIはバッファリングさえも除かれている
- ユースケース別やり方
- ファイルの内容全体を文字列として一気に読み込み
-
std::fs::read_to_string
を使う -
std::io::Read
traitのread_to_string
メソッドを使う
-
- 1行ずつ文字列を読み込みたい
-
std::io::BufRead
traitのread_line
メソッドかlines
メソッドを使う -
BufRead
traitはstd::io::BufReader
型が実装している
-
- ファイルの内容全体をバイト列として一気に読み込み
-
std::io::Read
traitのread_to_end
メソッドを使う
-
- バイト列をバッファに読み込みながら処理したい
-
std::io::BufReader
を使う -
BufReader
相当の事を自分でやるならstd::io::Read
traitのread
メソッドを使う
-
- 面倒な事は一切しないで1バイトずつ処理したい
-
std::io::Read
traitのbytes
メソッドを使う
-
- 文字列/バイト列を一度に書き出す
- 自分で
String
型やVec<u8>
型の変数に溜めてからstd::io::Write
traitのwrite_all
メソッドで一度に書き出す - 文字列の場合は
write!
もしくはwriteln!
マクロがprintln!
の様に使う事ができる。
- 自分で
- 文字列/バイト列を少しずつ複数回に分けて書き出す
-
std::io::BufWriter
を使う事以外は一度に書き出す場合と同じ
-
- ファイルの内容全体を文字列として一気に読み込み
どんなAPIがあるの?
この2つが標準の同期APIです。
std::io
にはRead/Write/Seek/BufReadといった入出力の為のtraitや標準入出力の型(Stdin/Stdout/Stderr)等があります。
std::fs
にはFile操作の為のFile型やディレクトリ操作の為の関数や型等があります。
注意事項
Read
traitや Write
traitの最もプリミティブな操作であるread
やwrite
メソッドは多くの場合に実装が低レイヤー過ぎる為に、他の言語と同様の感覚で使用するとパフォーマンスが出ないという問題が起こります。
Read
やWrite
traitを実装している型(例えばFile
やStdin
やStdout
)を、BufReader
やBufWriter
型で包んであげる事でread
やwrite
でバッファリングが行われるようになり、他の言語と同様のパフォーマンスを出す事が出来るようになります。
ユースケース別やり方
ファイルの内容全体を文字列として一気に読み込み
Rust v1.26.0以降に追加されたstd::fs::read_to_stringを使用すると簡単です。
use std::fs;
fn main() -> Result<(), Box<std::error::Error>> {
let content = fs::read_to_string("path/to/file")?;
println!("{}", content);
Ok(())
}
他にはRead
traitのread_to_string
メソッドを使ったやり方もあります。
use std::io::{self, Read};
fn get_content<R: Read>(r: &mut R) -> io::Result<String> {
let mut buf = String::new();
let _ = r.read_to_string(&mut buf)?;
Ok(buf)
}
fn main() -> Result<(), Box<std::error::Error>> {
let content = get_content(&mut io::stdin())?;
println!("{}", content);
Ok(())
}
read_to_string
は引数に読み込んだデータを保存するバッファを要求するので、1つのString変数を使い回すことでメモリアロケーション回数を減らす事も出来ます。
1行ずつ文字列を読み込みたい
BufReader
が実装しているBufRead
traitのlines
メソッドを使ってIterator
として処理します。
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn main() -> Result<(), Box<std::error::Error>> {
for result in BufReader::new(File::open("path/to/file")?).lines() {
let l = result?;
println!("{}", l);
}
Ok(())
}
BufReader
が実装しているBufRead
traitのread_line
メソッドを使って細かく制御していく事も出来ます。
use std::io::{self, BufRead, BufReader};
fn main() -> Result<(), Box<std::error::Error>> {
let mut reader = BufReader::new(io::stdin());
let mut buf = String::new();
while reader.read_line(&mut buf)? > 0 {
println!("{}", buf);
buf.clear();
}
Ok(())
}
read_line
を用いると改行文字もbuf
に入るので注意して下さい。
ファイルの内容全体をバイト列として一気に読み込み
Read
traitのread_to_end
メソッドを使います。
use std::fs::File;
use std::io::Read;
fn main() -> Result<(), Box<std::error::Error>> {
let mut file = File::open("path/to/file")?;
let mut buf = Vec::new();
let _ = file.read_to_end(&mut buf)?;
println!("{:?}", buf);
Ok(())
}
バイト列をバッファに読み込みながら処理したい
BufReader
を使って読み込みます。
use std::fs::File;
use std::io::{Read, BufReader};
fn main() -> Result<(), Box<std::error::Error>> {
let mut reader = BufReader::new(File::open("path/to/file")?);
let mut buf = [0; 4];
loop {
match reader.read(&mut buf)? {
0 => break,
n => {
let buf = &buf[..n];
println!("{:?}", buf);
}
}
}
Ok(())
}
何らかの理由でBufReaderのバッファリングが邪魔な場合はRead
traitのread
メソッドを自分で呼び出して読み込んでいきます。
use std::fs::File;
use std::io::Read;
fn main() -> Result<(), Box<std::error::Error>> {
let mut file = File::open("path/to/file")?;
let mut buf = [0; 4];
loop {
match file.read(&mut buf)? {
0 => break,
n => {
let buf = &buf[..n];
println!("{:?}", buf);
}
}
}
Ok(())
}
面倒な事は一切しないで1バイトずつ処理したい
BufReader
を通してRead
traitのbytes
メソッドを呼びます。
use std::fs::File;
use std::io::{Read, BufReader};
fn main() -> Result<(), Box<std::error::Error>> {
for result in BufReader::new(File::open("path/to/file")?).bytes() {
let byte = result?;
println!("{:x}", byte);
}
Ok(())
}
文字列/バイト列を一度に書き出す
自分でバッファリングしてWrite
traitのwrite_all
メソッドを使って書き出します。
書き込み可能なファイルハンドルはFile
型のcreate
メソッドで作成します。
use std::fs::File;
use std::io::{self, Read, Write, BufReader};
fn main() -> Result<(), Box<std::error::Error>> {
let mut file = File::create("path/to/file")?;
let buf = BufReader::new(io::stdin()).bytes().collect::<io::Result<Vec<u8>>>()?;
file.write_all(&buf)?;
file.flush()?;
Ok(())
}
バイト列ではなく文字列の場合はwrite!
またはwriteln!
マクロを使う事でprintln!
と同じ様に書きだす事ができます。
use std::fs::File;
use std::io::{self, BufRead, Write, BufReader};
fn main() -> Result<(), Box<std::error::Error>> {
let mut file = File::create("path/to/file")?;
let buf = BufReader::new(io::stdin()).lines().collect::<io::Result<Vec<String>>>()?.join("\n");
write!(file, "{}", buf)?;
file.flush()?;
Ok(())
}
文字列/バイト列を少しずつ複数回に分けて書き出す
BufWriter
を使う事以外は一度に書き出す場合と基本は同じです。
use std::fs::File;
use std::io::{self, Read, Write, BufReader, BufWriter};
fn main() -> Result<(), Box<std::error::Error>> {
let mut writer = BufWriter::new(File::create("path/to/file")?);
for result in BufReader::new(io::stdin()).bytes() {
let byte = result?;
writer.write_all(&[byte])?;
}
file.flush()?;
Ok(())
}