C++で循環ファイルを書く必要があります。プログラムはファイルに行を書き込む必要があり、コードが最大行数に達すると、ファイルの先頭の行を上書きする必要があります。
誰か考えがありますか?
残念ながら、ファイル全体を書き直さずに、ファイルの先頭の行を切り捨てたり上書きしたりすることはできません。
私はちょうどあなたのためにトリックをするかもしれない新しいアプローチを考えました...
次の構造を持つ小さなヘッダーをファイルに含めることができます。
編集:ごみ、循環バッファの変形について説明しました!
ヘッダーフィールド
Bytes 00 - 07 (long)
-ファイルに書き込まれた合計(現在の)行数。Bytes 08 - 15 (long)
-ファイルの「実際の」最初の行の先頭へのポインタ。これは、最初はヘッダーが終了した後のバイトになりますが、後でデータがオーバーライドされると変更されます。`Bytes 16 - 23 (long)
-ファイルの「終了セクション」の長さ。繰り返しますが、これは最初はゼロになりますが、後でデータがオーバーライドされると変更されます。読み取りアルゴリズム(擬似コード)
ファイル全体を読み取ります。
「実際の」最初の行の先頭を指すヘッダーフィールドを読み取ります 「終了セクション」の長さを指定するヘッダーフィールドを読み取ります ファイルの終わりまですべての行を読みます ヘッダーの終わりの直後のバイトをシークします 「終了セクション」が完全に読まれるまで、すべての行を読んでください
書き込みアルゴリズム(擬似コード)
任意の数の新しい行をファイルに書き込みます。
合計数を含むヘッダーフィールドを読み取ります。ファイル内の行数 If(行数)+(新しい行数)<=(最大行数)Then ファイルの終わりに新しい行を追加します 行数のヘッダーフィールドを(ne行の数)だけインクリメントします そうしないと ファイルの終わりにできるだけ多くの行を(最大まで)追加します (ヘッダーフィールドの)最初の行へのポインターから始めて、まだ書き込む必要がある数の行を読み取ります 読み取ったばかりの行の合計バイト数を見つけます ストリームの最初の行を指すヘッダーフィールドを次のバイトに設定します 残りの行のバイト数がファイルの先頭の行のバイト数より少なくなるまで、ファイルの最後に新しい行を一度に1つずつ書き込み続けます(この条件がすぐに当てはまる可能性があります) 、その場合、これ以上書く必要はありません) 残りの新しい行をファイルの先頭に書き込みます(ヘッダーの後のバイトから開始します) ファイルの「終了セクション」の長さを含むヘッダーフィールドを、ヘッダーの直後に書き込まれたバイト数に設定します。
それほど単純なアルゴリズムではありません、私は完全に認めます!それでも、ある意味でかなりエレガントだと思います。もちろん、そのいずれかが明確でない場合はお知らせください。うまくいけば、それはあなたが今望むことを正確に行うべきです。
これで、行が一定の長さ(バイト単位)であることが保証されている場合は、適切なポイントに戻って既存のデータを上書きするだけで簡単にできます。ただし、これはかなりありそうもない状況のように思われます。行の長さを最大にする必要があるという制限を課し、さらにこの最大長に書き込む各行をパディングすることを気にしない場合は、問題が簡単になる可能性があります。それでも、特定の状況下でファイルサイズが大幅に大きくなるなどの欠点があります(つまり、ほとんどの行が最大長よりも大幅に短くなります)。これが許容できるかどうかは、状況によって異なります。
最後に、正確な目的に応じて、代わりに既存のロギングシステムの利用を検討することもできます。
サイズが爆発しないログを処理する通常の方法は、ローリングログファイルを使用し、それらを1日1回程度ロールし、最新のN個のファイルのみを保持することです。
たとえば、毎日、ファイル名が「application_2009_05_20.log」の新しいログファイルを作成し、書き込みを開始して、常に追加します。
14日分のログファイルができたら、最も古いログファイルの削除を開始します。
ファイルはバイト指向であり、行指向のサービスが必要なため、2つの選択肢があります。
ファイルの周りに行指向のラッパーを実装する
回線指向のデバイスに切り替えます。私の頭の中で:SQLiteにはいくつかの素晴らしいC++ラッパーがあります。
循環バッファを使用し、追加ごとにバッファをファイルに書き込みます。
これは、小さくて単純なコードサイズのソリューションです。これは文字列の単純な循環バッファであり、文字列を追加するたびに、文字列のバッファ全体がファイルに書き込まれます(もちろん、1回の追加操作ですべての文字列を書き込むにはかなりのコストがかかります。したがって、これは次の場合にのみ適しています。文字列の数が少ない)。
ファイルへの出力を伴う循環バッファの単純な実装:
// GLOBALS ( final implementation should not use globals )
#define MAX_CHARS_PER_LINE (1024)
#define MAX_ITEMS_IN_CIRCULARBUF (4) // must be power of two
char lineCircBuf[MAX_ITEMS_IN_CIRCULARBUF][MAX_CHARS_PER_LINE];
int lineCircBuf_add = 0;
int lineCircBuf_rmv = 0; // not being used right now
uint32_t lineCircBuf_mask = MAX_ITEMS_IN_CIRCULARBUF-1;
char FILENAME[] = "lineCircBuf.txt";
FILE * ofp = NULL;
int addLine(char * str) {
int i;
// Error checking
if( strlen(str) > MAX_CHARS_PER_LINE ) {
return -1; // failure
}
if( ofp != NULL) {
fclose(ofp);
}
// Copy string into circular buffer
strncpy( &(lineCircBuf[lineCircBuf_add][0]),
str,
MAX_CHARS_PER_LINE );
lineCircBuf_add = ( lineCircBuf_add + 1 ) & lineCircBuf_mask;
// Write to file
ofp = fopen(FILENAME,"w");
for( i = 0; i < MAX_ITEMS_IN_CIRCULARBUF-1; i++ ) {
fprintf( ofp, "%s\n", lineCircBuf[i] );
}
fprintf( ofp, "%s", lineCircBuf[i] ); // do not add a newline to the last line b/c we only want N lines in the file
return 0; // success
}
int removeLine(int index) {
// not implemented yet
}
void unitTest() {
int i;
// Dummy text to demonstrate adding string lines
char lines[5][MAX_CHARS_PER_LINE] = {
"Hello world.",
"Hello world AGAIN.",
"The world is interesting so far!",
"The world is not interesting anymore...",
"Goodbye world."
};
// Add lines to circular buffer
for( i = 0; i < sizeof(lines)/sizeof(lines[0]); i++ ) {
addLine(&(lines[i][0]));
}
}
int main() {
unitTest();
return 0;
}
したがって、上記の例では、5行の入力があり、バッファーの長さはわずか4行でした。したがって、出力は4行のみで、最初の行は最後の行「Goodbyeworld」で上書きされます。案の定、出力の最初の行に「さようならの世界」があることを確認します。
Goodbye world.
Hello world AGAIN.
The world is interesting so far!
The world is not interesting anymore...
ファイルI/Oは、行ではなく、ストレージの基礎となる単位としてバイトを使用するため、これは注意が必要です。
つまり、fseek()を最初に戻して、以前のデータを壊してしまう可能性がありますが、私にはあなたが望んでいるものではない予感があります。
簡単な解決策:
このソリューションは、ファイル内の行数が一定ではなく、ファイルの長さが一定になるように設計されています。行数は、長さによって時間とともに変化します。このソリューションでは、特定の行番号をすばやく探すのが難しくなりますが、ファイルの上部または下部にインジケーターデータを貼り付けて、これを簡単にすることができます。
「賢い」ソリューション(上記のソリューションのバリエーション):
dequesに時々使用されるのと同じトリックを使用してください。ファイルの最初から最後まですばやくラップアラウンドしますが、ファイルの最初/最後がどこにあるかを追跡します。このファイルをサポートしていないプログラムで読みたい場合は、アンラップユーティリティを作成してこのファイルを標準ファイルに変換できます。このソリューションは本当に簡単に実装できますが、上記のバージョンの方が好きです。
醜い解決策:
行を追加するときは、追加するすべての行に適度な量のパディングを追加します。
新しい行を追加するたびに、次のようにします。
行の長さがかなり一貫していない限り、これはかなりうまく機能しないことに注意してください。より簡単な解決策は、線が一定の長さであることを保証することです(ただし、その長さを超える場合に備えて、何らかの方法で複数行の「線」を作成します。
ファイルの現在の書き込み位置をどこかに保持することで、これが行われるのを見てきました。行を追加する必要がある場合は、位置をシークし、行を書き込み、アトミックな方法で位置を更新します。オーバーフローした場合は、行を書き込む前にゼロを探します。現在、サイズに制約のある循環ログ ファイルに対してこれを行っています。行制約ベースで行うのは少し奇妙ですが、おそらく同様の方法で行うことができます。書き込みループは次のようになります。
logFile.lockForWrite();
currentPosition = logFile.getWritePosition();
logFile.seek(currentPosition);
for each line in lineBuffer {
if ((currentPosition+line.length()) > logFile.getMaxSize()) {
currentPosition = 0;
logFile.seek(0);
}
logFile.write(line);
currentPosition += line.length();
}
logFile.setWritePosition(currentPosition);
logFile.unlock();
注意が必要なのは、現在の書き込み位置を維持し、アプリケーションがファイルに書き込みを行っている間にファイルの読み取りを調整する方法を見つけることです (たとえば、tail
ユーティリティを使用)。リーダー ユーティリティは書き込み位置も追跡する必要があるため、読み取りループは次のようになります。
lastPosition = logFile.getWritePosition();
while (!killed) {
logFile.wait();
logFile.lockForRead();
newPosition = logFile.getWritePosition();
logFile.seek(lastPosition);
newLine = logFile.readFrom(lastPosition, (newPosition-lastPosition));
lastPosition = newPosition;
logFile.unlock();
}
これは特定の言語ではなく、単なる疑似コードですが、アイデアはそこにあります。もちろん、興味深いエッジ ケースの処理はすべて読者に任せました。
そうは言っても... 私は他の意見に同意します。本当に正当な理由がない限り、これを行わないでください。素晴らしいアイデアのように聞こえますが、次のようになります。
grep
、tail
、perl
、 などの既存のツールを使用したログ処理が困難になります。全体として、構成可能なログ ファイル管理を可能にする既存のパッケージ ログ パッケージを使用する方がよいでしょう。Apache の log4cxxまたはPoco のPoco::Logger
を見てください。
ファイルがテキスト ファイルである必要がある場合:
これは、さまざまな行の長さで非常に問題になります。最初の 2 行はそれぞれ 80 文字ですが、それを 100 文字の行でどのように上書きしますか?
新しい行が最初の行を置き換える必要がある場合、これは非常にコストのかかる操作であるファイルの挿入になります (基本的に、ファイルの残りの部分全体を読み書きする必要があります)。最小限の量のデータを除いて、すべてのデータに対してこれを行うことは本当に望ましくありません。
これがログ目的の場合は、rollng ログ ファイルを使用します。さらに簡単にしました。ファイル サイズが制限を超えると、古いファイルの名前が .bak に変更され (古い .bak は削除されます)、新たに開始されます。1MB の制限がある場合、たとえば最後の 1MB が保持され、2MB を超えることはありません。
2 つ以上のファイルで同様のメカニズムを使用できます。基本的に、「ロールオーバー」を行ではなくファイルに移動します。
ファイルが独自の形式である可能性がある場合:
基本的な DB エンジン (提案されている SQLite など)、または別の構造化ストレージ メカニズムを使用します。
を使用log4cxx
して、RollingFileAppender
この情報をログ ファイルに書き込むことができます。RollingFileAppender
ログ ファイルが特定のサイズに達すると、ログ ファイルのロールオーバーが処理されます。私はそれがまさにあなたが望んでいるものだとは思いませんが、それはかなり単純です - 多分それはうまくいくでしょう.
必要なサイズのファイルのマッピング (CreateFileMapping または mmap) を作成し、バッファーに行を書き込み、最大数に達したら最初からやり直します。
別のアプリケーションに入力するためにこのファイルを生成する場合は、リレーショナルデータベース(SQL Server、MySQLなど)に直接ログを記録し、ログに記録されたデータから必要に応じて定期的にそのファイルを生成するのが最善の策だと思います。 。
可変サイズの問題を回避するには、おそらく間接参照と割り当てスキームを使用することになります。これは、ファイルへの固定数の「ポインター」と、Nをラップアラウンドする1つの「次に書き込まれる」ポインターを持つ間接ブロックで構成されます。
ただし、主なトリックは、間接参照を追加することです。
簡単な回避策: