我想只阅读文本文件的最后一行(我在UNIX上,可以使用Boost)。我所知道的所有方法都需要扫描整个文件以获得最后一行,这根本没有效率。有没有一种有效的方法来获得最后一行?
此外,我需要它足够健壮,即使有问题的文本文件经常被另一个进程附加,它也能正常工作。
答案 0 :(得分:17)
使用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;
}
以下是测试文件。它在文本文件中以空,一行和多行数据成功。
This is the first line.
Some stuff.
Some stuff.
Some stuff.
This is the last line.
答案 1 :(得分:4)
跳转然后结束,然后开始向后读取块,直到找到任何行的标准为止。如果最后一个块没有“结束”一行,你可能也需要尝试向前扫描(假设一个主动附加到文件中的一个非常长的行)。
答案 2 :(得分:2)
最初这是为了读取最后一个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;
}
答案 3 :(得分:1)
你可以使用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
答案 4 :(得分:1)
虽然derpface的答案肯定是正确的,但它通常会返回意外的结果。这样做的原因是,至少在我的操作系统(Mac OSX 10.9.5)上,许多文本编辑器使用“结束行”字符终止其文件。
例如,当我打开vim时,只键入单个字符'a'(不返回),然后保存,该文件现在将包含(以十六进制):
61 0A
其中61是字母'a',0A是行尾字符。
这意味着derpface的代码将在这样的文本编辑器创建的所有文件上返回一个空字符串。
虽然我当然可以想象一个以'end line'结尾的文件应该返回空字符串的情况,但我认为在处理常规文本文件时忽略最后一个'end line'字符会更合适;如果文件被'结束行'字符终止,我们会正确地忽略它,如果文件没有被'结束行'字符终止,我们不需要检查它。
我忽略输入文件最后一个字符的代码是:
#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'文件。
编辑:如果文件太大(&gt; 2GB),行if((int)fin.tellg() <= 0){
实际上会导致问题,因为tellg不会只返回文件开头的字符数(tellg() function give wrong size of file?) 。最好分别测试文件fin.tellg()==tellgValueForStartOfFile
的开头和错误fin.tellg()==-1
的开头。 tellgValueForStartOfFile
可能为0,但更好的确定方法可能是:
fin.seekg (0, is.beg);
tellgValueForStartOfFile = fin.tellg();
答案 5 :(得分:0)
我也在努力解决这个问题,因为我运行了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;
}
答案 6 :(得分: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;
}