モチベーション
バイナリファイルの一部分を別ファイルにコピーする簡潔な手段が欲しい.
問題設定
// "in.dat"の[first, last)を"out.dat"へコピーしたい.(first, lastは先頭からのoffset) size_t first, last; ifstream ifs("in.dat", ios::in | ios::binary); ofstream ofs("out.dat", ios::out | ios::trunc | ios::binary); ..... ifs.seekg(first); size_t sz = last - first; // ここの部分をどうすれば良いか?
解法
とりあえず思いついた解法3つ.入力ストリームがsz読むよりも前にEOFに達した場合は例外とし,また出力ストリームがEOFに達した場合も例外とする.
解法その1:get & put
getとputを使って単純に実装してみる.
bool is_in_good, is_out_good; char ch; for(size_t count = 0; count < sz && (is_in_good = !!ifs.get(ch)) && (is_out_good = !!ofs.put(ch)); ++count); if(!is_in_good || !is_out_good){ throw exception(""); }
(エラーフラグを2つにしているのは入力・出力どちらに例外があったのかを見分けるため)
解法その2:ストリームバッファを直接触る
basic_ostream::operator<<(basic_streambuf *)の実装(VC++7.1)をまねして,ストリームバッファを直接触るコードを書いてみる.
typedef char_traits<char> traits_t; traits_t::int_type eof = traits_t::eof(); traits_t::int_type meta; streambuf *isb = ifs.rdbuf(); streambuf *osb = ofs.rdbuf(); meta = isb->sgetc(); if((meta = isb->sgetc()) == eof){ throw exception(""); } if(osb->sputc(traits_t::to_char_type(meta)) == eof){ throw exception(""); } for(size_t count = 1; count < sz; ++count){ if((meta = isb->snextc()) == eof){ throw exception(""); } if(osb->sputc(traits_t::to_char_type(meta)) == eof){ throw exception(""); } }
解法その3:istreambuf_iterator & ostreambuf_iterator
istreambuf_iteratorとostreambuf_iteratorの組み合わせ.
bool is_in_good, is_out_good; istreambuf_iterator<char> iit(ifs); ostreambuf_iterator<char> oit(ofs); for(size_t count = 0; count < sz && (is_in_good = (iit != istreambuf_iterator<char>())) && (is_out_good = !oit.failed()); ++count, ++iit, ++oit){ *oit = *iit; } if(!is_in_good || !is_out_good){ throw exception(""); }
比較実験
以下のような環境で上3つのコードを比較してみる.
- 18ファイル(合計約178MB)の各々に埋め込まれたPCMデータを抽出(何のデータかは聞かないで('A`))
- 実験機はCeleron 1GHz,メモリ256MB(新しいの( ゜д゜)ホスィ…)
- コンパイラはMicrosoft Visual C++ Toolkit 2003, Release Configuration, /Ox付き
- ストリームクラスはboost::filesystem::ifstream及びboost::filesystem::ofstream
- boost::filesystemのライブラリをリンク
以上のような条件における各コードの実行時間を計測.結果は以下.
get & put | ストリームバッファを直接触る | istreambuf_iterator & ostreambuf_iterator |
---|---|---|
137s | 60s | 62s |
余談
get & putが遅い理由が見当たらない・・・.実装読んだ限りでは他2つとそう変わりないはずなのに・・・.
っていうかこんなことしてるから貴重な休日が潰れるんですよ!ヽ(`Д´)ノ ウワァァン!!
後,某所のアドバイスに感謝.