テキストファイルの最後の行だけを読みたい(UNIXを使用しているので、Boostを使用できます)。私が知っているすべての方法では、ファイル全体をスキャンして最後の行を取得する必要がありますが、これはまったく効率的ではありません。最後の行だけを取得する効率的な方法はありますか?
また、問題のテキストファイルが別のプロセスによって絶えず追加されている場合でも機能するように、これは十分に堅牢である必要があります。
Seekgを使用してファイルの最後にジャンプし、最初の改行が見つかるまで読み返します。以下は、MSVCを使用した頭のてっぺんからのサンプルコードです。
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
int main()
{
string filename = "test.txt";
ifstream fin;
fin.open(filename);
if(fin.is_open()) {
fin.seekg(-1,ios_base::end); // go to one spot before the EOF
bool keepLooping = true;
while(keepLooping) {
char ch;
fin.get(ch); // Get current byte's data
if((int)fin.tellg() <= 1) { // If the data was at or before the 0th byte
fin.seekg(0); // The first line is the last line
keepLooping = false; // So stop there
}
else if(ch == '\n') { // If the data was a newline
keepLooping = false; // Stop at the current position.
}
else { // If the data was neither a newline nor at the 0 byte
fin.seekg(-2,ios_base::cur); // Move to the front of that data, then to the front of the data before it
}
}
string lastLine;
getline(fin,lastLine); // Read the current line
cout << "Result: " << lastLine << '\n'; // Display it
fin.close();
}
return 0;
}
そして、以下はテストファイルです。テキストファイル内の空の1行、および複数行のデータで成功します。
This is the first line.
Some stuff.
Some stuff.
Some stuff.
This is the last line.
ジャンプして終了し、行の基準が見つかるまでブロックを逆方向に読み始めます。最後のブロックが行で「終了」しない場合は、おそらく前方にスキャンする必要があります(ファイルにアクティブに追加された非常に長い行を想定)。
当初、これは最後のsyslogエントリを読み取るように設計されていました。 EOFの前の最後の文字が'\n'
であるとすると、次の'\n'
の出現を探して、その行を文字列に格納します。
#include <fstream>
#include <iostream>
int main()
{
const std::string filename = "test.txt";
std::ifstream fs;
fs.open(filename.c_str(), std::fstream::in);
if(fs.is_open())
{
//Got to the last character before EOF
fs.seekg(-1, std::ios_base::end);
if(fs.peek() == '\n')
{
//Start searching for \n occurrences
fs.seekg(-1, std::ios_base::cur);
int i = fs.tellg();
for(i;i > 0; i--)
{
if(fs.peek() == '\n')
{
//Found
fs.get();
break;
}
//Move one character back
fs.seekg(i, std::ios_base::beg);
}
}
std::string lastline;
getline(fs, lastline);
std::cout << lastline << std::endl;
}
else
{
std::cout << "Could not find end line character" << std::endl;
}
return 0;
}
Derpfaceによる答えは間違いなく正しいですが、予期しない結果が返されることがよくあります。この理由は、少なくとも私のオペレーティングシステム(Mac OSX 10.9.5)では、多くのテキストエディタがファイルを「終了行」文字で終了するためです。
たとえば、vimを開いて、1文字の「a」(戻り値なし)だけを入力して保存すると、ファイルには次の文字が含まれます(16進数)。
_61 0A
_
ここで、61は文字「a」であり、0Aは行末文字です。
これは、derpfaceによるコードが、そのようなテキストエディタによって作成されたすべてのファイルで空の文字列を返すことを意味します。
'end line'で終了するファイルが空の文字列を返す場合は確かに想像できますが、通常のテキストファイルを処理する場合は、最後の 'endline'文字を無視する方が適切だと思います。ファイルが「終了行」文字で終了している場合は、それを適切に無視します。ファイルが「終了行」文字で終了していない場合は、チェックする必要はありません。
入力ファイルの最後の文字を無視するための私のコードは次のとおりです。
_#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
int main() {
std::string result = "";
std::ifstream fin("test.txt");
if(fin.is_open()) {
fin.seekg(0,std::ios_base::end); //Start at end of file
char ch = ' '; //Init ch not equal to '\n'
while(ch != '\n'){
fin.seekg(-2,std::ios_base::cur); //Two steps back, this means we
//will NOT check the last character
if((int)fin.tellg() <= 0){ //If passed the start of the file,
fin.seekg(0); //this is the start of the line
break;
}
fin.get(ch); //Check the next character
}
std::getline(fin,result);
fin.close();
std::cout << "final line length: " << result.size() <<std::endl;
std::cout << "final line character codes: ";
for(size_t i =0; i<result.size(); i++){
std::cout << std::hex << (int)result[i] << " ";
}
std::cout << std::endl;
std::cout << "final line: " << result <<std::endl;
}
return 0;
}
_
出力されます:
_final line length: 1
final line character codes: 61
final line: a
_
単一の「a」ファイル。
編集:ファイルが大きすぎる(> 2GB)場合、行if((int)fin.tellg() <= 0){
は実際に問題を引き起こします。これは、tellgがファイルの先頭からの文字数を返すだけではないためです( tellg()関数)ファイルのサイズを間違えましたか? )。ファイルの開始fin.tellg()==tellgValueForStartOfFile
とエラーfin.tellg()==-1
を別々にテストする方がよい場合があります。 tellgValueForStartOfFile
はおそらく0ですが、確認するためのより良い方法はおそらく次のとおりです。
_fin.seekg (0, is.beg);
tellgValueForStartOfFile = fin.tellg();
_
Seekg()を使用してファイルの終わりにジャンプし、逆方向に読み取ることができます。擬似コードは次のようになります。
ifstream fs
fs.seekg(ios_base::end)
bytecount = fs.tellg()
index = 1
while true
fs.seekg(bytecount - step * index, ios_base::beg)
fs.read(buf, step)
if endlinecharacter in buf
get endlinecharacter's index, said ei
fs.seekg(bytecount - step*index + ei)
fs.read(lastline, step*index - ei)
break
++index
また、uberwuluのコードを実行し、空白行が表示されたため、この問題に苦労していました。これが私が見つけたものです。例として次の.csvファイルを使用しています。
date test1 test2
20140908 1 2
20140908 11 22
20140908 111 235
コード内のコマンドを理解するには、次の場所とそれに対応する文字に注意してください。 (Loc、char):...(63、 '3')、(64、 '5')、(65、-)、(66、 '\ n')、(EOF、-)。
#include<iostream>
#include<string>
#include<fstream>
using namespace std;
int main()
{
std::string line;
std::ifstream infile;
std::string filename = "C:/projects/MyC++Practice/Test/testInput.csv";
infile.open(filename);
if(infile.is_open())
{
char ch;
infile.seekg(-1, std::ios::end); // move to location 65
infile.get(ch); // get next char at loc 66
if (ch == '\n')
{
infile.seekg(-2, std::ios::cur); // move to loc 64 for get() to read loc 65
infile.seekg(-1, std::ios::cur); // move to loc 63 to avoid reading loc 65
infile.get(ch); // get the char at loc 64 ('5')
while(ch != '\n') // read each char backward till the next '\n'
{
infile.seekg(-2, std::ios::cur);
infile.get(ch);
}
string lastLine;
std::getline(infile,lastLine);
cout << "The last line : " << lastLine << '\n';
}
else
throw std::exception("check .csv file format");
}
std::cin.get();
return 0;
}
私はアレクサンドロスの解決策を取り、それを少し整えました
bool moveToStartOfLine(std::ifstream& fs)
{
fs.seekg(-1, std::ios_base::cur);
for(long i = fs.tellg(); i > 0; i--)
{
if(fs.peek() == '\n')
{
fs.get();
return true;
}
fs.seekg(i, std::ios_base::beg);
}
return false;
}
std::string getLastLineInFile(std::ifstream& fs)
{
// Go to the last character before EOF
fs.seekg(-1, std::ios_base::end);
if (!moveToStartOfLine(fs))
return "";
std::string lastline = "";
getline(fs, lastline);
return lastline;
}
int main()
{
const std::string filename = "test.txt";
std::ifstream fs;
fs.open(filename.c_str(), std::fstream::in);
if(!fs.is_open())
{
std::cout << "Could not open file" << std::endl;
return -1;
}
std::cout << getLastLineInFile(fs) << std::endl;
return 0;
}