ストリーム関連クラス 2
文字カウント・ストリーム
ストリームの文字数や、現在の位置は何行目であるかを取得できます。また、指定行へシークすることができます。これも特定の用途では便利でしょう。
Poco::DirectoryIteratorとPoco::CountingOutputStreamクラスを利用し、ちょっとしたサンプルを作ってみました。
#include <iostream> #include "Poco/DirectoryIterator.h" #include "Poco/StreamCopier.h" #include "Poco/CountingStream.h" // 指定されたディレクトリ以下に含まれるファイルを走査して、 // ディレクトリ名とファイルサイズの合計値をストリームに出力、 // ファイルサイズを返す。 Poco::File::FileSize getDirSize( const std::string& path, std::ostream& out ) { Poco::File::FileSize flen = 0; // ファイルサイズの合計 Poco::DirectoryIterator di( path ); while ( !di.name().empty() ) // カレントのファイル名が // 空なら終わり { if ( di->isDirectory() ) { // ディレクトリなら // ( ->でPoco::Fileクラス // として参照可) flen += getDirSize( di->path(), out ); // 再帰呼び出し } else flen += di->getSize(); // ファイルサイズを合算 ++di; // ++オペレータで次のファイルへ } // pathの名前(UTF-8)と、 // path以下に含まれるファイルサイズの合計値を出力。 out << path << "," << (__int64)flen << std::endl; return flen; } int _tmain(int argc, _TCHAR* argv[]) { // 行数をカウントするために Poco::CountingOutputStream ct( std::cout ); getDirSize( "C:\\XXX", ct ); // 走査したサブディレクトリ数を出力。 // getDirSizeの引数で指定したディレクトリを、 // 数に入れないように-1する。 std::cout << "total : " << ct.lines() - 1 << " dir" << std::endl; }
ディレクトリを再帰的に走査し、各ディレクトに含まれるファイルの合計ファイルサイズを列挙するサンプルコードです。getDirSize
という関数で、ディレクトリ内のファイル走査にPoco::DirectoryIteratorを使っています。走査したファイルは、->オペレータを使い、Poco::Fileのポインタとして取り出すことができます。ディレクトリが見つかったら、getDirSize自身を再帰呼び出しをして、そのサブディレクトリのファイルサイズを求めています。ディレクトリの処理には、この例のように再帰呼び出しが定番の処理です。ファイルがディレクトリでなかった場合は、ファイルサイズを単に合算するだけです。
getDirSizeの出力先は、標準出力にリンクしたPoco::CountingOutputStreamを使っています。こうすると、画面の出力と行数のカウントの両方を同時に行うことができます。行数を-1しているのは、最初に指定したディレクトリ分を除くためです。
POCOのクラスのおかげで、かんたんに記述できたのではないでしょうか。
ハードディスクのファイルを整理する際に、どこのディレクトリで容量を消費しているのか知りたいことがありますよね。そんなときに使えそうです。ただ、読み出したまま出力しているので、例えばファイルサイズ順に並べたいと思うかもしれません。いろいろ工夫の余地はありますが何かの参考になれば幸いです。
StreamTokenizer
前回紹介したPoco::StringTokenizerのストリーム版で、ストリームを指定のデリミタでトークンに分割するクラスなのですが、Poco::StringTokenizerに比べて、ちょっと使い方が分かりにくいものになっています。単純にデリミタを指定して分割するのではなく、デリミタでの区切り処理を実装したTokenクラスというのを別に定義してやり、それをPoco::StreamTokenizerに渡すという感じになります。
つまり、用途にあわせてTokenクラスの実装を自分で行う必要があるわけで、便利かどうかは微妙なところです。サンプルコードを紹介しておきますが、けっこうなコード量が必要です。
#include "Poco/StreamTokenizer.h" #include "Poco/Token.h" // カンマ区切り用Tokenクラス class testToken : public Poco::Token { public: testToken(){} ~testToken(){} bool start(char c, std::istream& istr); void finish(std::istream& istr); Class tokenClass() const { return Poco::Token::USER_TOKEN; } }; // 切り出すトークンの最初の文字を探す bool testToken::start(char c, std::istream& istr) { if ( c != ',' ) _value = c; return true; } // トークンの終わりを判断 void testToken::finish(std::istream& istr) { int c = istr.peek(); while ( c != ',' && !istr.eof() ) { istr.get(); _value += (char) c; c = istr.peek(); } istr.get(); } //CSVファイルを読み出し、トークンを出力する。 int _tmain(int argc, _TCHAR* argv[]) { std::fstream fs( "c:\\test.txt" ); Poco::StreamTokenizer st( fs ); // トークンの前の空白を削除 st.addToken( new Poco::WhitespaceToken ); // カンマ区切りトークン st.addToken( new testToken ); // トークンの取り出し // 終了はEOF_TOKENかどうかで判断 for ( const Poco::Token * pt = st.next(); pt->tokenClass() != Poco::Token::EOF_TOKEN; pt = st.next() ) { //「トークン」と出力 std::cout << "「" << pt->tokenString() << "」" << std::endl; } }
簡単なCSVファイル読み込みのサンプルなので、カンマが連続するデータや複数行のデータには対応していません。
その他
上記以外にも次のようなストリームクラスがあります。また、表には記していませんがプラットフォームによって異なるテキストの改行コードを吸収するためのクラスなどがあります。
hexBinary変換ストリーム
BASE64変換ストリームと同じような使い方ができます。BASE64ではなく、hexBinaryに変換します。hexBinaryとは、バイナリデータをそのまま「0~9」「a~f」「A~F」の文字で16進数として表現する形式です。
バイナリデータ操作ストリーム
これもフィルタ的に使用するストリームで、バイナリデータのバイトオーダーが変換できるようになっています。ビッグエンディアン、リトルエンディアンの変換はもちろん、バイトオーダーを示すBOMを出力するwriteBOM
というメンバ関数もあります。ただ、オープン系システムではあまり使う場面はないかもしれません。
まとめ
今回は、サンプルコードを中心に紹介しました。詳しいクラスの定義は、Pocoのドキュメントやヘッダーファイルを参照してください。ファイル関連の処理は実際のプログラム開発でも頻出する処理です。POCOのファイルやストリームのクラスをうまく活用すれば、ソースをとてもシンプルにすることができます。ぜひ試してみてください。