2

I have a read file method that reads the date from the start of the file and then the date at the end of the file. The problem is the file is huge and it takes a long time. Is there way I can read the first line, ignore all but the last and then read the last line?

//Time consuming current method
int counter = 0;
DateTime begDate = new DateTime();
DateTime endDate = new DateTime();
while ((read = s.ReadLine()) != null)
{
    if (counter != 0) //skip first line
    {

        string[] Currentline = read.Split(comma);
        DateTime ThisBarsDate = DateTime.ParseExact(Currentline[1] + Currentline[2], "yyyyMMddHHmmss", new CultureInfo("en-US"));
        if (counter == 1) begDate = ThisBarsDate;

        endDate = ThisBarsDate; //will be correct at end of loop
    }
    counter++;
}
s.Close();
splattne
  • 102,760
  • 52
  • 202
  • 249
stormist
  • 5,709
  • 12
  • 46
  • 65

6 Answers6

3

If you have a FileStream, you can use the Seek method to seek to an offset from the end of the file and start reading from there.

 var file = new FileStream(...);
 var reader = new StreamReader(file);
 long bytesToSkip =  ...number of bytes in last line...
 file.Seek( bytesToSkip, SeekOrigin.End );
 var endDate = reader.ReadToEnd();

Obviously, this isn't a complete solution but it should give you enough to get started. Essentially, you want to seek to the end of the file -- this should skip all the blocks in the file except the last one, which it will have to read to get to the end, then navigate backwards some number of bytes. The number should be enough to cover the last line. If you know its exact length, that's even better. This is the example I've shown. If not, skip enough to be sure you've gone back past the start of the last line and then start reading lines and doing your conversion.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
3

There is no inherently easy way to do this through an existing API. One option though is to seek to the end of the file and then start walking backwards looking for the first new line pair.

Here is a quick sample. It's not a very robust solution but it provides the basic framework you're looking for.

public string GetLastLine(Stream stream, Encoding enc) {
  const int64 range = 100;
  var found = false;
  var index = stream.Length;
  var data = new byte[range];
  var builder = new StringBuilder();
  while ( true ) { 
    index = Math.Max(0, index -= range); 
    var count = stream.Read(data, 0, data.Length);
    if ( count == 0 ) {
      break;
    }
    var text = enc.GetString(data, 0, count);
    var newLineIndex = text.Index(Environment.NewLine);
    if ( newLineIndex >= 0 ) {
      builder.Insert(text.SubString(newLineIndex+Environment.NewLine.Length),0);
      break; 
    } else { 
      builder.Insert(text, 0);
    }
  }
  return builder.ToString();
}
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
2

Check out FileStream.Seek() with SeekOrigin of End.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
Greg D
  • 43,259
  • 14
  • 84
  • 117
2

See this related stackoverflow question:

Get the last 10 lines of a very large text file

Community
  • 1
  • 1
Ash
  • 60,973
  • 31
  • 151
  • 169
2

As others have said, there's no good, easy, built-in way to do this. You may even need to be careful seeking to the end of the file and counting backwards, in case there might be multi-byte characters.

However, you can still improve on things significantly. The problem with your current code is that it takes the time to parse out every line, even though you only care about the first and the last one. Try this instead:

//Time consuming current method
DateTime begDate = new DateTime();
DateTime endDate = new DateTime();

s.ReadLine(); //skip first line - assume there is at least the header and the first record
string[] record = s.ReadLine().Split(comma);
begDate = DateTime.ParseExact(record[1] + record[2], "yyyyMMddHHmmss", new CultureInfo("en-US"));

string prev, read;
while ((read = s.ReadLine()) != null)
{
    prev = read;
}

record = prev.Split(comma);
endDate = DateTime.ParseExact(record[1] + record[2], "yyyyMMddHHmmss", new CultureInfo("en-US"));

Note that the loop does almost no work; it just copies a reference. Also note that I didn't show the .Close() call. That's because of course you opened the file with a using statement to ensure the file is closed, even if an exception is thrown. You did open the file with a using statement, right?

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Every time in the while loop it will assign new string to the prev, which is type of string, and String is immutable i.e. Strings cannot be altered. When you alter a string (by adding to it for example), you are actually creating a new string. – Asim Sajjad Sep 23 '09 at 09:29
  • @Asim: you are wrong. Yes, strings are immutable and altering them creates a new string. But this assignment doesn't make any changes to the string, and so all that happens is that it copies the reference, which is very fast. – Joel Coehoorn Sep 23 '09 at 12:09
  • That said, you do have a point that the program will ultimately have to allocate and de-allocate a single string for each line. It won't do two string as you claim, but one is bad enough. So we might do even better by reading individual characters and just remembering the offset to the last newline, so we only ever need allocate the one string. – Joel Coehoorn Sep 23 '09 at 16:00
-3

Here is the code you can use to get last line

StreamReader streamReader = new StreamReader("C:\\Text.txt");
ArrayList lines = new ArrayList();

string t =streamReader.ReadToEnd() ;

streamReader.Close();
Console.Write((t.Substring(t.LastIndexOf(System.Environment.NewLine))));
Console.ReadKey(); 

Hope that will help.

Asim Sajjad
  • 2,526
  • 10
  • 36
  • 73
  • 1
    Not only does this create an ArrayList (which is evil by itself- use a generic list instead), but then it never uses it. Plus it forces loading the entire file into memory. Seeking through a stream should be much faster. – Joel Coehoorn Sep 23 '09 at 02:51
  • I knew that this could load whole file, but it is good as you don't need to loop through entire file. As other option are there to read the file – Asim Sajjad Sep 23 '09 at 03:56
  • I think you missed the point about creating an un-used ArrayList(). You could at least remove that part from your post or use an array and call .ReadAllLines(). Oh, and any time you're calling StreamReader.Close(), you're doing it wrong. A `using` statement is the better way to go. – Joel Coehoorn Sep 23 '09 at 12:11