40

In native C#, how can I read from the end of a file?

This is pertinent because I need to read a log file, and it doesn't make sense to read 10k, to read the last 3 lines.

C. Ross
  • 31,137
  • 42
  • 147
  • 238

4 Answers4

55

To read the last 1024 bytes:

using (var reader = new StreamReader("foo.txt"))
{
    if (reader.BaseStream.Length > 1024)
    {
        reader.BaseStream.Seek(-1024, SeekOrigin.End);
    }
    string line;
    while ((line = reader.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 3
    Will this work with character sets (such as UTF-8) that can use multiple bytes to encode a character? It seems you could end up starting from the middle of a multi-byte sequence. – Eric J. Feb 04 '16 at 23:52
  • You would have to test what I'm about to say but, if you know the character size (2 bytes for UTF8) of your string and you use a seek offset that is divisible by that number, that _**should**_ prevent you from reading anything i the middle of a character. I've never seen a stream return anything but file contents (i.e. you never get an end of file marker or other characters that aren't a part of the file). – Andrew Bonsall Jun 29 '16 at 15:02
  • This didn't quite read the last line for me, but instead read a number of lines up – Bassie Jul 04 '16 at 15:29
  • 1
    @AndrewBonsall UTF8 is not 2-bytes per character, it's variable bytes per character 1-4 for each character. UTF16 is 2-bytes fixed per character. – TessellatingHeckler Nov 28 '18 at 08:36
11

Maybe something like this will work for you:

using (var fs = File.OpenRead(filePath))
{
    fs.Seek(0, SeekOrigin.End);

    int newLines = 0;
    while (newLines < 3)
    {
        fs.Seek(-1, SeekOrigin.Current);
        newLines += fs.ReadByte() == 13 ? 1 : 0; // look for \r
        fs.Seek(-1, SeekOrigin.Current);
    }

    byte[] data = new byte[fs.Length - fs.Position];
    fs.Read(data, 0, data.Length);
}

Take note that this assumes \r\n.

ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
  • 1
    If formulated by doubts into this question: http://stackoverflow.com/questions/4369278/filestream-seek-vs-buffered-reading – VVS Dec 06 '10 at 17:44
  • +1 To show how one can scan for lines. Side note: checking for `13` byte-by-byte assumes ASCII/UTF8 encoding. If you don't know what encoding of the file is this may not work correctly. – Alexei Levenkov Aug 29 '14 at 19:56
5

The code below uses a random-access FileStream to seed a StreamReader at an offset near the end of the file, discarding the first read line since it is most likely only partial.

FileStream stream = new FileStream(@"c:\temp\build.txt", 
    FileMode.Open, FileAccess.Read);

stream.Seek(-1024, SeekOrigin.End);     // rewind enough for > 1 line

StreamReader reader = new StreamReader(stream);
reader.ReadLine();      // discard partial line

while (!reader.EndOfStream)
{
    string nextLine = reader.ReadLine();
    Console.WriteLine(nextLine);
}
Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
1

Take a look at this related question's answer to read a text file in reverse. There is a lot of complexity to reading a file backward correctly because of stuff like encoding.

Elaskanator
  • 1,135
  • 10
  • 28