title: Using FileStream.Seek
body: <p>I am trying to work with FileStream.Seek to quickly jump to a line and read it.</p>
  
  <p>However, I am not getting the right results.  I have tried to look at this for a while and can't understand what I am doing wrong.</p>
  
  <p>Environment:<br>
  OS: Windows 7<br>
  Framework: .NET 4.0<br>
  IDE: Visual C# Express 2010    </p>
  
  <p><strong>Sample Data in file location: C:\Temp\Temp.txt</strong></p>
  
  <pre>
  0001|100!2500
  0002|100!2500
  0003|100!2500
  0004|100!2500
  0005|100!2500
  0006|100!2500
  0007|100!2500
  0008|100!2500
  0009|100!2500
  0010|100!2500
  </pre>
  
  <p><strong>The code:</strong></p>
  
  <pre><code>class PaddedFileSearch
  {
      private int LineLength { get; set; }
      private string FileName { get; set; }
  
      public PaddedFileSearch()
      {
          FileName = @"C:\Temp\Temp.txt";     // This is a padded file.  All lines are of the same length.
  
          FindLineLength();
          Debug.Print("File Line length: {0}", LineLength);
  
          // TODO: This purely for testing.  Move this code out.
          SeekMethod(new int[] { 5, 3, 4 });
          /*  Expected Results:
           *  Line No     Position        Line
           *  -------     --------        -----------------
           *  3           30              0003|100!2500
           *  4           15              0004|100!2500
           *  5           15              0005|100!2500 -- This was updated after the initial request.
           */
  
          /* THIS DOES NOT GIVE THE EXPECTED RESULTS */
          SeekMethod(new int[] { 5, 3 });
          /*  Expected Results:
           *  Line No     Position        Line
           *  -------     --------        -----------------
           *  3           30              0003|100!2500
           *  5           30              0005|100!2500
           */
      }
  
      private void FindLineLength()
      {
          string line;
  
          // Add check for FileExists
  
          using (StreamReader reader = new StreamReader(FileName))
          {
              if ((line = reader.ReadLine()) != null)
              {
                  LineLength = line.Length + 2;
                  // The 2 is for NewLine(\r\n)
              }
          }
  
      }
  
      public void SeekMethod(int[] lineNos)
      {
          long position = 0;
          string line = null;
  
          Array.Sort(lineNos);
  
          Debug.Print("");
          Debug.Print("Line No\t\tPosition\t\tLine");
          Debug.Print("-------\t\t--------\t\t-----------------");
  
          using (FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.None))
          {
              using (StreamReader reader = new StreamReader(fs))
              {
                  foreach (int lineNo in lineNos)
                  {
                      position = (lineNo - 1) * LineLength - position;
                      fs.Seek(position, SeekOrigin.Current);
  
                      if ((line = reader.ReadLine()) != null)
                      {
                          Debug.Print("{0}\t\t\t{1}\t\t\t\t{2}", lineNo, position, line);
                      }
                  }
              }
          }
      }
  }
  </code></pre>
  
  <p><strong>The output I get:</strong>  </p>
  
  <pre>
  File Line length: 15
  
  Line No     Position        Line
  -------     --------        -----------------
  3           30              0003|100!2500
  4           15              0004|100!2500
  5           45              0005|100!2500
  
  Line No     Position        Line
  -------     --------        -----------------
  3           30              0003|100!2500
  5           30              0004|100!2500
  </pre>
  
  <p><strong>My problem is with the following output:</strong></p>
  
  <pre>
  Line No     Position        Line
  -------     --------        -----------------
  5           30              0004|100!2500
  </pre>
  
  <p>The output for Line should be: <strong>0005|100!2500</strong></p>
  
  <p>I don't understand why this is happening. </p>
  
  <p>Am I doing something wrong?
  Is there a workaround?
  Also are there faster ways to do this using something like seek?<br>
  (I am looking for code based options and <strong><em>NOT</em></strong> Oracle or SQL Server.  For the sake of argument lets also say that the file size 1 GB.)</p>
  
  <p>Any help is greatly appreciated.</p>
  
  <p>Thanks.     </p>
  
  <p><strong>UPDATE:</strong><br>
  I found 4 great answers here.  Thanks a lot.</p>
  
  <p>Sample Timings:<br>
  Based on a few runs the following are the methods from best to good.  Even the good is very close to best.<br>
  In a file that contains 10K lines, 2.28 MB.  I searched for same 5000 random lines using all the options.</p>
  
  <ol>
  <li>Seek4: Time elapsed: 00:00:00.0398530 ms    -- Ritch Melton </li>
  <li>Seek3: Time elapsed: 00:00:00.0446072 ms    -- Valentin Kuzub </li>
  <li>Seek1: Time elapsed: 00:00:00.0538210 ms    -- Jake</li>
  <li>Seek2: Time elapsed: 00:00:00.0889589 ms    -- bitxwise</li>
  </ol>
  
  <p>Shown below is the code.  After saving the code you can simply call it by typing <code>TestPaddedFileSeek.CallPaddedFileSeek();</code>.  You will also have to specify the namespace and the "using references".</p>
  
  <p>`</p>
  
  <pre><code>/// &lt;summary&gt;
  /// This class multiple options of reading a by line number in a padded file (all lines are the same length).
  /// The idea is to quick jump to the file.
  /// Details about the discussions is available at: http://stackoverflow.com/questions/5201414/having-a-problem-while-using-filestream-seek-in-c-solved
  /// &lt;/summary&gt;
  class PaddedFileSeek
  {
      public FileInfo File {get; private set;}
      public int LineLength { get; private set; }
  
      #region Private methods
      private static int FindLineLength(FileInfo fileInfo)
      {
          using (StreamReader reader = new StreamReader(fileInfo.FullName))
          {
              string line;
              if ((line = reader.ReadLine()) != null)
              {
                  int length = line.Length + 2;   // The 2 is for NewLine(\r\n)
                  return length;
              }
          }
          return 0;
      }
  
      private static void PrintHeader()
      {
         /*
          Debug.Print("");
          Debug.Print("Line No\t\tLine");
          Debug.Print("-------\t\t--------------------------");
         */ 
      }
  
      private static void PrintLine(int lineNo, string line)
      {
          //Debug.Print("{0}\t\t\t{1}", lineNo, line);
      }
  
      private static void PrintElapsedTime(TimeSpan elapsed)
      {
          Debug.WriteLine("Time elapsed: {0} ms", elapsed);
      }
      #endregion
  
      public PaddedFileSeek(FileInfo fileInfo)
      {
          // Possibly might have to check for FileExists
          int length = FindLineLength(fileInfo);
          //if (length == 0) throw new PaddedProgramException();
          LineLength = length;
          File = fileInfo;
      }
  
      public void CallAll(int[] lineNoArray, List&lt;int&gt; lineNoList)
      {
          Stopwatch sw = new Stopwatch();
  
          #region Seek1
          // Create new stopwatch
          sw.Start();
  
          Debug.Write("Seek1: ");
          // Print Header
          PrintHeader();
  
          Seek1(lineNoArray);
  
          // Stop timing
          sw.Stop();
  
          // Print Elapsed Time
          PrintElapsedTime(sw.Elapsed);
  
          sw.Reset();
          #endregion
  
          #region Seek2
          // Create new stopwatch
          sw.Start();
  
          Debug.Write("Seek2: ");
          // Print Header
          PrintHeader();
  
          Seek2(lineNoArray);
  
          // Stop timing
          sw.Stop();
  
          // Print Elapsed Time
          PrintElapsedTime(sw.Elapsed);
  
          sw.Reset();
          #endregion
  
          #region Seek3
          // Create new stopwatch
          sw.Start();
  
          Debug.Write("Seek3: ");
          // Print Header
          PrintHeader();
  
          Seek3(lineNoArray);
  
          // Stop timing
          sw.Stop();
  
          // Print Elapsed Time
          PrintElapsedTime(sw.Elapsed);
  
          sw.Reset();
          #endregion
  
          #region Seek4
          // Create new stopwatch
          sw.Start();
  
          Debug.Write("Seek4: ");
  
          // Print Header
          PrintHeader();
  
          Seek4(lineNoList);
  
          // Stop timing
          sw.Stop();
  
          // Print Elapsed Time
          PrintElapsedTime(sw.Elapsed);
  
          sw.Reset();
          #endregion
  
      }
  
      /// &lt;summary&gt;
      /// Option by Jake
      /// &lt;/summary&gt;
      /// &lt;param name="lineNoArray"&gt;&lt;/param&gt;
      public void Seek1(int[] lineNoArray)
      {
          long position = 0;
          string line = null;
  
          Array.Sort(lineNoArray);
  
          using (FileStream fs = new FileStream(File.FullName, FileMode.Open, FileAccess.Read, FileShare.None))
          {
              using (StreamReader reader = new StreamReader(fs))
              {
                  foreach (int lineNo in lineNoArray)
                  {
                      position = (lineNo - 1) * LineLength;
                      fs.Seek(position, SeekOrigin.Begin);
  
                      if ((line = reader.ReadLine()) != null)
                      {
                          PrintLine(lineNo, line);
                      }
  
                      reader.DiscardBufferedData();
                  }
              }
          }
  
      }
  
      /// &lt;summary&gt;
      /// option by bitxwise
      /// &lt;/summary&gt;
      public void Seek2(int[] lineNoArray)
      {
          string line = null;
          long step = 0;
  
          Array.Sort(lineNoArray);
  
          using (FileStream fs = new FileStream(File.FullName, FileMode.Open, FileAccess.Read, FileShare.None))
          {
              // using (StreamReader reader = new StreamReader(fs))
              // If you put "using" here you will get WRONG results.
              // I would like to understand why this is.
              {
                  foreach (int lineNo in lineNoArray)
                  {
                      StreamReader reader = new StreamReader(fs);
                      step = (lineNo - 1) * LineLength - fs.Position;
                      fs.Position += step;
  
                      if ((line = reader.ReadLine()) != null)
                      {
                          PrintLine(lineNo, line);
                      }
                  }
              }
          }
      }
  
      /// &lt;summary&gt;
      /// Option by Valentin Kuzub
      /// &lt;/summary&gt;
      /// &lt;param name="lineNoArray"&gt;&lt;/param&gt;
      #region Seek3
      public void Seek3(int[] lineNoArray)
      {
          long position = 0; // totalPosition = 0;
          string line = null;
          int oldLineNo = 0;
  
          Array.Sort(lineNoArray);
  
          using (FileStream fs = new FileStream(File.FullName, FileMode.Open, FileAccess.Read, FileShare.None))
          {
              using (StreamReader reader = new StreamReader(fs))
              {
                  foreach (int lineNo in lineNoArray)
                  {
                      position = (lineNo - oldLineNo - 1) * LineLength;
                      fs.Seek(position, SeekOrigin.Current);
                      line = ReadLine(fs, LineLength);
                      PrintLine(lineNo, line);
                      oldLineNo = lineNo;
  
                  }
              }
          }
  
      }
  
      #region Required Private methods
      /// &lt;summary&gt;
      /// Currently only used by Seek3
      /// &lt;/summary&gt;
      /// &lt;param name="stream"&gt;&lt;/param&gt;
      /// &lt;param name="length"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      private static string ReadLine(FileStream stream, int length)
      {
          byte[] bytes = new byte[length];
          stream.Read(bytes, 0, length);
          return new string(Encoding.UTF8.GetChars(bytes));
      }
      #endregion
      #endregion
  
      /// &lt;summary&gt;
      /// Option by Ritch Melton
      /// &lt;/summary&gt;
      /// &lt;param name="lineNoArray"&gt;&lt;/param&gt;
      #region Seek4
      public void Seek4(List&lt;int&gt; lineNoList)
      {
          lineNoList.Sort();
  
          using (var fs = new FileStream(File.FullName, FileMode.Open))
          {
              lineNoList.ForEach(ln =&gt; OutputData(fs, ln));
          }
  
      }
  
      #region Required Private methods
      private void OutputData(FileStream fs, int lineNumber)
      {
          var offset = (lineNumber - 1) * LineLength;
  
          fs.Seek(offset, SeekOrigin.Begin);
  
          var data = new byte[LineLength];
          fs.Read(data, 0, LineLength);
  
          var text = DecodeData(data);
          PrintLine(lineNumber, text);
      }
  
      private static string DecodeData(byte[] data)
      {
          var encoding = new UTF8Encoding();
          return encoding.GetString(data);
      }
  
      #endregion
  
      #endregion
  }
  
  
  
  static class TestPaddedFileSeek
  {
      public static void CallPaddedFileSeek()
      {
          const int arrayLenght = 5000;
          int[] lineNoArray = new int[arrayLenght];
          List&lt;int&gt; lineNoList = new List&lt;int&gt;();
          Random random = new Random();
          int lineNo;
          string fileName;
  
  
          fileName = @"C:\Temp\Temp.txt";
  
          PaddedFileSeek seeker = new PaddedFileSeek(new FileInfo(fileName));
  
          for (int n = 0; n &lt; 25; n++)
          {
              Debug.Print("Loop no: {0}", n + 1);
  
              for (int i = 0; i &lt; arrayLenght; i++)
              {
                  lineNo = random.Next(1, arrayLenght);
  
                  lineNoArray[i] = lineNo;
                  lineNoList.Add(lineNo);
              }
  
              seeker.CallAll(lineNoArray, lineNoList);
  
              lineNoList.Clear();
  
              Debug.Print("");
          }
      }
  }
  </code></pre>
  
  <p>`</p>