69

Given a text file, how would I go about reading an arbitrary line and nothing else in the file?

Say, I have a file test.txt. How would I go about reading line number 15 in the file?

All I've seen is stuff involving storing the entire text file as a String array and then using the value of the line number as the number of the String to use from the array... but there are some complications: The text file is enormously huge and the machine that the application I'm coding isn't exactly a top-notch system. Speed isn't the top priority, but it is definitely a major issue.

Are there any ways to ONLY read a specific line of a text file and store the result as a string?

Thanks for your responses: The file is KINDA structured. It's got 25 lines of info and then X lines of numbers but line 17 of the first 25 has the value of X.

But then, there's 1 blank line and it repeats itself all over as a second record in the file and X can have a different value for each record.

What I want to do is read and store the first 25 lines as independent values and then store the next X (usually around 250) lines as an array. Then I'm going to store it in an SQL database and repeat with the NEXT record until I reach the Yth record (the number of records in the file is in line 3)

EDIT 2: Alright, I think I've gotten to a solution based on a combination of your alls' responses.

I'm going to read the first 25 lines and store it as an array. I'll copy the pertinent contents of the array to local variables then I'll delete the first 25 lines. Then, I can use the info to store the next X lines (the value of item 13 in the array) as an array, serialize it, store it in a database then delete the lines that I just read.

I could then repeat the process for each subsequent record.

Of course, this relies on one assumption I'm making, which to be honest, I'm not sure is true. Is it possible to delete the first n lines from a text file from within C# without having to read the entire thing and re-write it without the first n lines?

Filburt
  • 17,626
  • 12
  • 64
  • 115
ankushg
  • 1,632
  • 4
  • 17
  • 22
  • Are the lines themselves fixed length (in bytes) or not. That makes a big diference on the types of solutions available – RB Davidson Aug 11 '09 at 21:41
  • "It's got 25 lines of info" : I think this contradicts the fixed line assumptions mentioned below, you'll just have to read all lines and grab what you want along the way. – H H Aug 11 '09 at 21:41
  • What you really need is not a system to get the i-th line directly. You just need to process the file in one-pass and perform some operations in the way. You can use the code snippet in my answer (`ReadLine` method) to read one line at a time. – Mehrdad Afshari Aug 11 '09 at 21:48
  • Thanks guys, I think I reached a solution * **if** * I can the first *n* lines efficiently. – ankushg Aug 11 '09 at 22:27
  • I've no idea why you need to delete the lines? Can't you just go forward and read the next lines you need? – Mehrdad Afshari Aug 11 '09 at 22:29
  • Well, since the files that this program will parse aren't standard, I have no way of knowing how many records there are, how long the file is etc so I only have 1 set of variables for a file that can have potentially 100 records requiring 100 sets of the same variables... if I HAD that many, I could do it all in one go, but the workstation this would run on already has limited resources, so I'm just going to repeat the process for each record by deleting the old ones... if its possible. – ankushg Aug 11 '09 at 22:32
  • Requirements are not clear. – paparazzo Apr 30 '18 at 16:30

13 Answers13

116

.NET 4.0 edit

Since .NET 4.0, it is possible to access a single line of a file directly. For instance, to access line 15:

string line = File.ReadLines(FileName).Skip(14).Take(1).First();

This will return only the line required


Since you can't predict the location (can you?) of the i-th line in the file, you'll have to read all previous lines too. If the line number is small, this can be more efficient than the ReadAllLines method.

string GetLine(string fileName, int line)
{
   using (var sr = new StreamReader(fileName)) {
       for (int i = 1; i < line; i++)
          sr.ReadLine();
       return sr.ReadLine();
   }
}
vexe
  • 5,433
  • 12
  • 52
  • 81
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • 4
    Unless your text file is structured with a fixed line length. – Eric J. Aug 11 '09 at 21:20
  • Mehrdad, could you explain? Reading line 1234 with UTF-8 ? – H H Aug 11 '09 at 21:26
  • @Mehrdad: Very true - predictable without looking at any data in the file though. – Jon Skeet Aug 11 '09 at 21:26
  • @Henk: Supposing each line didn't have the same length, but used one more byte than the line before (with the first line taking 3 bytes, say). Then you could easily predict where line 1234 started, even though each line had a different length. – Jon Skeet Aug 11 '09 at 21:28
  • 3
    @Merhdad: UTF16 is not fixed-length, but its predecessor UCS-2 is (I only got to know this difference recently myself). See http://en.wikipedia.org/wiki/UTF-16/UCS-2 – Dirk Vollmar Aug 11 '09 at 21:36
  • Jon, you are right but such a format is very rare. But I thought Mehrdad had some fancy way to predict escape sequences. – H H Aug 11 '09 at 21:38
  • @Henk: what I had in mind from being predictable is, say (assume a fixed length encoding) line `i` always has `i` characters. I'll be pretty easy to calculate the start position of line `k`. – Mehrdad Afshari Aug 11 '09 at 21:43
  • @divo: By the way, do you know which one .NET `System.String` uses? I'd expected it to be a fixed-length encoding... so is it UCS-2 or really UTF16 (documentation says UTF16, of course) – Mehrdad Afshari Aug 11 '09 at 21:45
  • @Mehrdad: The C# spec says: "Character and string processing in C# uses Unicode encoding. The char type represents a UTF-16 code unit, and the string type represents a sequence of UTF-16 code units." although it might surprising that there is no fixed-length encoding used. – Dirk Vollmar Aug 11 '09 at 22:05
  • @Mehrdad: `string line = File.ReadLines(FileName).Skip(14).Take(1).ToString();` `Take(n)` actually returns an `IEnumerable` converting it to a string directly won't work. I had to do a `First()` on it, to get the actual `T` which is a string - No need for `.ToString()` then. Just `string line = File.ReadLines(FileName).Skip(14).Take(1).First();` – vexe Oct 30 '13 at 15:32
  • @vexe: That line is not mine. That's an edit by Sylverdrag. Please go ahead and correct it as you see fit. – Mehrdad Afshari Oct 31 '13 at 05:30
  • Does the first code really read all lines and then skip 14 or is it a efficient way that reads only 14th line? – Mohammad Jafar Mashhadi Dec 12 '13 at 06:48
  • @MJafarMash. No and no, not really, you'll have to read the first 15 lines (but you don't have to read to the end of the file, and this code is efficient enough not to do that). – Mehrdad Afshari Dec 12 '13 at 07:32
  • 2
    Side-note: if you want to read only a single line `ElementAt(OrDefault)` is more appropriate: `string line = File.ReadLines(FileName).ElementAtOrDefault(14);` – Tim Schmelter Dec 09 '16 at 12:50
  • RealLines is not good for "The text file is enormously huge and the machine that the application .." – paparazzo Apr 30 '18 at 16:27
  • @paparazzo Apparently someone edited and added that into the answer. Please feel free to edit as you see fit. – Mehrdad Afshari May 01 '18 at 16:02
  • @MehrdadAfshari I am not positive so leaving it alone. – paparazzo May 01 '18 at 16:09
  • Wouldn't this read all lines File.ReadLines(FileName) to the end of the file then call the method to skip 14? Thus modifying the value returned but not the fact that all lines were read? – Douglas Plumley Jun 20 '18 at 12:37
12

If each line is a fixed length then you can open a Stream around it, seek (bytes per line) * n into the file and read your line from there.

using( Stream stream = File.Open(fileName, FileMode.Open) )
{
    stream.Seek(bytesPerLine * (myLine - 1), SeekOrigin.Begin);
    using( StreamReader reader = new StreamReader(stream) )
    {
        string line = reader.ReadLine();
    }
}

Alternatively you could just use the StreamReader to read lines until you found the one you wanted. That way's slower but still an improvement over reading every single line.

using( Stream stream = File.Open(fileName, FileMode.Open) )
{
    using( StreamReader reader = new StreamReader(fileStream) )
    {
        string line = null;
        for( int i = 0; i < myLineNumber; ++i )
        {
            line = reader.ReadLine();
        }
    }
}
Dave D
  • 8,472
  • 4
  • 33
  • 45
8

No unfortunately there is not. At the raw level files do not work on a line number basis. Instead they work at a position / offset basis. The root filesystem has no concept of lines. It's a concept added by higher level components.

So there is no way to tell the operating system, please open file at line blah. Instead you have to open the file and skip around counting new lines until you've passed the specified number. Then store the next set of bytes into an array until you hit the next new line.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • This is absolutely true. An alternative approach to get to a certain point in a text file (not by line though) is `byte`-offset. https://stackoverflow.com/a/44786265/3873799 – alelom Feb 17 '23 at 15:16
3

Unless you have fixed sized lines, you need to read every line until you reach the line you want. Although, you don't need to store each line, just discard it if it's not the line you desire.

Edit:

As mentioned, it would also be possible to seek in the file if the line lengths were predictable -- that is to say you could apply some deterministic function to transform a line number into a file position.

Ron Warholic
  • 9,994
  • 31
  • 47
2

As Mehrdad said, you cannot just seek to the n-th line without reading the file. However, you don't need to store the entire file in memory - just discard the data you don't need.

string line;

using (var sr = new StreamReader(path))
    for (int i = 0; i < 15; i++)
    {
       line = sr.ReadLine();
       if (line == null) 
           break; // there are less than 15 lines in the file
    }
AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
VladV
  • 10,093
  • 3
  • 32
  • 48
2

Tried and tested. It's as simple as follows:

string line = File.ReadLines(filePath).ElementAt(actualLineNumber - 1);

As long as you have a text file, this should work. Later, depending upon what data you expect to read, you can cast the string accordingly and use it.

  • 2
    You are expected to provide some explanation, not just a piece of code that should be trusted blindly. –  Nov 18 '17 at 22:36
1

If the lines are all of a fixed length you can use the Seek method of a stream to move to the correct starting positiion.

If the lines are of a variable length your options are more limited.

If this is a file you will be only using once and then discarding, then you are best off reading it in and working with it in memeory.

If this is a file you will keeping and will be reading from more than writing to, you can create a custom index file that contains the starting positions of each line. Then use that index to get your Seek position. The process of creating the index file is resource intensive. Everytime you add a new line to the file you will need to update the index, so maintenance becomes a non-trivial issue.

RB Davidson
  • 528
  • 6
  • 17
1

If your file contains lines with different lengths and you need to read lines often and you need to read it quickly you can make an index of the file by reading it once, saving position of each new line and then when you need to read a line, you just lookup the position of the line in your index, seek there and then you read the line.

If you add new lines to the file you can just add index of new lines and you don't need to reindex it all. Though if your file changes somewhere in a line you have already indexed then you have to reindex.

1

Read five lines each time, just put your statement in if statement , thats it

        String str1 = @"C:\Users\TEMP\Desktop\StaN.txt";   

        System.IO.StreamReader file = new System.IO.StreamReader(str1);

        line = file.ReadLine();

        Int32 ctn=0;

        try
        {

            while ((line = file.ReadLine()) != null)
            {

                    if (Counter == ctn)
                    {
                        MessageBox.Show("I am here");
                        ctn=ctn+5;
                        continue;
                    }
                    else
                    {
                        Counter++;
                        //MessageBox.Show(Counter.ToString());
                        MessageBox.Show(line.ToString());
                    } 
                }

            file.Close();
        }
        catch (Exception er)
        {

        }
user1075940
  • 1,086
  • 2
  • 22
  • 46
Alexander Zaldostanov
  • 2,907
  • 4
  • 30
  • 39
0

You could read line by line so you don't have to read the entire all at once (probably at all)

int i=0
while(!stream.eof() && i!=lineNum)
    stream.readLine()
    i++
line = stream.readLine()
Samuel
  • 16,923
  • 6
  • 62
  • 75
  • The problem with reading line by line is you will have a latency and seek with each read. If the file has a LOT of lines the performance will go through the floor. Reading large blocks (say 64k or more) of data and looking for the line breaks in memory will have MUCH better performance. – RB Davidson Aug 11 '09 at 21:33
  • RB Davidson: If the stream is buffered, that would be a non-issue. – Mehrdad Afshari Aug 11 '09 at 21:53
  • Not all streams are buffered. You can force a stream to be buffered, but nothing in your example implies this is being done. In any event, I have encountered performace issues when reading really big files using buffered streams and reading line by line. I was able to significantly increased performance by forcing the stream to read larger blocks of data than single lines, then splitting out the lines in memory. – RB Davidson Aug 12 '09 at 13:09
0

While you can't seek an N'th line directly in a non-symmetrical file without reading data in the file (because you need to count how many lines you have progressed into the file) you can count line breaks until you get to the line you want which takes the least amount of memory and probably has the best performance.

This is going to be more memory efficient than reading everything to an array, since it will only read into the file until it hits the end of the file or the line number (whichever comes first). It is far from perfect, but will probably suit your needs:

string line15 = ReadLine(@"C:\File.csv", 15);

public string ReadLine(string FilePath, int LineNumber){
    string result = "";
    try{
    if( File.Exists(FilePath) ){
        using (StreamReader _StreamReader = new StreamReader(FilePath)){
        for (int a = 0; a < LineNumber; a++) {
            result = _StreamReader.ReadLine();
        }
        }
    }
    }catch{}
    return result;
}
Dave
  • 1,823
  • 2
  • 16
  • 26
0

A variation. Produces an error if line number is greater than number of lines.

string GetLine(string fileName, int lineNum)
{
    using (StreamReader sr = new StreamReader(fileName))
    {
        string line;
        int count = 1;
        while ((line = sr.ReadLine()) != null)
        {
            if(count == lineNum)
            {
                return line;
            }
            count++;
        }
    }
    return "line number is bigger than number of lines";  
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176
-1
        if (File.Exists(fpath))
        {

            var data = File.ReadLines(fpath);
            Console.WriteLine(data.ToArray()[14]);
        }
user610961
  • 181
  • 1
  • 10