Using FileStream.Seek

一曲冷凌霜 提交于 2019-11-29 12:24:52

I'm confused by your expected positions, Line 5 at position 30 and 45, with Line 4 at 15, and 3 at 30?

Here's the core of the read logic:

    var offset = (lineNumber - 1) * LineLength;

    fs.Seek(offset, SeekOrigin.Begin);

    var data = new byte[LineLength];
    fs.Read(data, 0, LineLength);

    var text = DecodeData(data);
    Debug.Print("{0,-12}{1,-16}{2}", lineNumber, offset, text);

The full sample is here:

class PaddedFileSearch
{
    public int LineLength { get; private set; }
    public FileInfo File { get; private set; }

    public PaddedFileSearch(FileInfo fileInfo)
    {
        var length = FindLineLength(fileInfo);
        //if (length == 0) throw new PaddedProgramException();
        LineLength = length;
        File = fileInfo;
    }

    private static int FindLineLength(FileInfo fileInfo)
    {
        using (var reader = new StreamReader(fileInfo.FullName))
        {
            string line;
            if ((line = reader.ReadLine()) != null)
            {
                var length = line.Length + 2;
                return length;
            }
        }

        return 0;
    }

    public void SeekMethod(List<int> lineNumbers)
    {

        Debug.Print("");
        Debug.Print("Line No\t\tPosition\t\tLine");
        Debug.Print("-------\t\t--------\t\t-----------------");

        lineNumbers.Sort();

        using (var fs = new FileStream(File.FullName, FileMode.Open))
        {
            lineNumbers.ForEach(ln => OutputData(fs, ln));
        }
    }

    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);
        Debug.Print("{0,-12}{1,-16}{2}", lineNumber, offset, text);
    }

    private static string DecodeData(byte[] data)
    {
        var encoding = new UTF8Encoding();
        return encoding.GetString(data);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var seeker = new PaddedFileSearch(new FileInfo(@"D:\Desktop\Test.txt"));

        Debug.Print("File Line length: {0}", seeker.LineLength);

        seeker.SeekMethod(new List<int> { 5, 3, 4 });
        seeker.SeekMethod(new List<int> { 5, 3 });
    }
}
Jake

Place this in the inner loop of SeekMethod(int[] lineNos):

position = (lineNo - 1) * LineLength;
fs.Seek(position, SeekOrigin.Begin);
reader.DiscardBufferedData();

The problem is that your position variable changes based on its previous value, and StreamReader maintains a buffer so you need to clear out buffered data when you alter the stream position.

You got pretty sick mix of position being absolute for first lineno and relative for further lineno's

Look closely here and to actual results your getting

position = (lineNo - 1) * LineLength - position;
fs.Seek(position, SeekOrigin.Current);

For values 3,4,5 you get numbers 30, 15, 45, while its obvious that if your using relative position it should be 30,15,15 since line length is 15 OR 30,0,0 if your read method performs SEEK as side effect, like filestream.Read does. And your test output is ACCIDENTLY correct (only for string values though, not positions) , you should have used not a sequence for test and look at position value more closely to see that there is no connection with string displayed and position value.

Actually your StreamReader is ignoring further fs.Seek calls and is simply reading line by line =)

Here is results for 3 5 9 input :)

Line No         Position                Line
-------         --------                -----------------
3                       30                              0003|100!2500
5                       30                              0004|100!2500
9                       90                              0005|100!2500

I believe following is closest to what your trying to achieve, a new function

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));  
        }

And the new loop code

int oldLine = 0;
    using (FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.None))
    {
            foreach (int lineNo in lineNos)
            {
                position = (lineNo - oldLine -1) * LineLength;
                fs.Seek(position, SeekOrigin.Current);
                line = ReadLine(fs, LineLength);
                Console.WriteLine("{0}\t\t\t{1}\t\t\t\t{2}", lineNo, position, line);
                oldLine = lineNo;
            }
    }

Notice that now stream.Read function is equivalent to additional stream.Seek (Length)

New correct output and logical position changes

Line No         Position                Line
-------         --------                -----------------
3                       30                              0003|100!2500    
4                       0                               0004|100!2500    
5                       0                               0005|100!2500

Line No         Position                Line
-------         --------                -----------------
3                       30                              0003|100!2500  
5                       15                              0005|100!2500

PS: its so odd you think that 001: line is 1st line not 0th .. that whole -1 could be removed if you used programmer count method =)

I wouldn't say the problem is your effort to manually manage the position value, but rather that StreamReader.ReadLine changes the stream's Position value. If you step through your code and monitor your local values, you'll see the stream's position changes after each ReadLine call (to 148 after the first).

EDIT

It would be better to just change the stream's position directly rather than use Seek

public void SeekMethod(int[] lineNos)
{
    string line = null;
    long step;

    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))
    {
        foreach (int lineNo in lineNos)
        {
            StreamReader reader = new StreamReader(fs);
            step = (lineNo - 1) * LineLength - fs.Position;
            fs.Position += step;

            if ((line = reader.ReadLine()) != null) {
                Debug.Print("{0}\t\t\t{1}\t\t\t\t{2}", lineNo, step, line);
            }
        }
    }
}

The problem is you're tracking the position manually, but not accounting for the fact that the actual file position is going to be one line farther in after you've read that line. So you need to subtract out that additional read --- but only if it actually happened.

If you really want to do it this way, then instead of keeping position around, get the actual file position; or calculate the absolute file position from the given line number ad seek there directly instead of from the current file offset.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!