Reputation: 17268
Is there a way to read a file backwards, line by line, without having to go through the file from the beginning to start reading backwards?
Upvotes: 20
Views: 39759
Reputation: 1
this might help.
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
ifstream myFile("my.txt");
int count;
cout << "Enter the number of lines u want to print ";
cin >> count;
char c;
string str = "";
for (int i = 1; i <= 10000; i++)
{
myFile.seekg(-i, std::ios::end);
myFile.get(c);
str += c;
if (c == '\n')
{
reverse(str.begin(), str.end());
count--;
cout << str;
str.clear();
}
if (count == 0)
{
break;
}
}
cout << endl;
return 0;
}
Upvotes: -1
Reputation: 484
My answer is similar to ones that use a vector
to store the lines of the file, but I would instead use a list
.
Imagine you have the following text in a file called input.txt
:
hello
there
friend
I would read the file line-by-line, pushing each line not to the back of my list
but to its front. Using this rather than push_back
has the same effect as reading the contents of the file line-by-line into a vector
and then reversing it or iterating through it backwards.
#include <iostream>
#include <fstream>
#include <list>
#include <string>
#include <iterator>
#include <algorithm>
int main(void) {
std::ifstream file;
file.open("input.txt");
// Make sure the file opened properly
std::list<std::string> list;
std::string buffer;
while (std::getline(file, buffer)) {
list.push_front(buffer);
}
file.close();
std::copy(
list.begin(),
list.end(),
std::ostream_iterator<std::string>(std::cout, "\n")
);
return 0;
}
(Note that the bit at the bottom with std::copy
is just to print the contents of the list with a newline character as a delimiter between elements.)
This then prints:
friend
there
hello
Upvotes: 0
Reputation: 470
Slightly improved version will be this:-
1)Seek to the last-1 position
2)Get the last-1 position
3)Read a char and print it;
4)seek 2 pos back;
5)repeat 3 &4 for last-1
times;
ifstream in;
in.open("file.txt");
char ch;
int pos;
in.seekg(-1,ios::end);
pos=in.tellg();
for(int i=0;i<pos;i++)
{
ch=in.get();
cout<<ch;
in.seekg(-2,ios::cur);
}
in.close();
Upvotes: 6
Reputation: 1944
Open the file for read, call fseek()
to seek to the end of the file, then call ftell()
to get the length of the file. Alternatively you can get the file length by calling stat()
or fstat()
Allocate a buffer pointer to the file size obtained in #1, above.
Read the entire file into that buffer -- you can probably use fread()
to read the file all in one shot (assuming the file is small enough).
Use another char pointer to transverse the file from end to beginning of the buffer.
Upvotes: 4
Reputation: 20818
The short answer would be no. However, you can use the seek() function to move your pointer to where you want to go. Then read() some data from that point. If you know well how to manage buffers, then it should be pretty quick because you can read and cache the data and then search for the previous newline character(s). Have fun with \r\n which will be inverted...
-- Update: some elaboration on the possible algorithm --
This is not valid code, but it should give you an idea of what I'm trying to say here
File reads:
int fpos = in.size() - BUFSIZ;
char buf[BUFSIZ];
in.seek(fpos);
in.read(buf, BUFSIZ);
fpos -= BUFSIZ; // repeat until fpos < 0, although think of size % BUFSIZ != 0
// now buf has characters... reset buffer position
int bpos = BUFSIZ - 1;
Getting string:
// first time you need to call the read
if(bpos == -1) do_a_read();
// getting string
std::string s;
while(bpos >= 0 && buf[bpos] != '\n') {
s.insert(0, 1, buf[bpos]);
--bpos;
}
// if bpos == -1 and buf[0] != '\n' then you need to read another BUFSIZ chars
// and repeat the previous loop...
// before leaving, skip all '\n'
while(bpos >= 0 && buf[bpos] == '\n') {
--bpos;
}
return s;
To ease with '\r', you could have a first pass that transforms all '\r' to '\n'. Otherwise, all the tests of '\n' need to also test for '\r'.
Upvotes: 4
Reputation: 122001
As per comment, a possible (quite simple) alternative would be read the lines into a vector
. For example:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
int main()
{
std::ifstream in("main.cpp");
if (in.is_open())
{
std::vector<std::string> lines_in_reverse;
std::string line;
while (std::getline(in, line))
{
// Store the lines in reverse order.
lines_in_reverse.insert(lines_in_reverse.begin(), line);
}
}
}
EDIT:
As per jrok's and Loki Astari's comments, push_back()
would be more efficient but the lines would be in file order, so reverse iteration (reverse_iterator
) or std::reverse()
would be necessary:
std::vector<std::string> lines_in_order;
std::string line;
while (std::getline(in, line))
{
lines_in_order.push_back(line);
}
Upvotes: 9
Reputation: 91925
Use a memory-mapped file and walk backwards. The OS will page in the needed parts of the file in reverse order.
Upvotes: 15