Подтвердить что ты не робот

.NET С# - Случайный доступ в текстовых файлах - нелегкий путь?

У меня есть текстовый файл, содержащий несколько "записей" внутри него. Каждая запись содержит имя и набор чисел в качестве данных.

Я пытаюсь создать класс, который будет читать файл, представлять только имена всех записей, а затем разрешать пользователю выбирать, какие данные записи он/она хочет.

В первый раз, когда я просматриваю файл, я только читаю имена заголовков, но я могу отслеживать "позицию" в файле, где находится заголовок. Мне нужен произвольный доступ к текстовому файлу, чтобы искать начало каждой записи после того, как пользователь запросит ее.

Мне нужно сделать это так, потому что файл слишком велик для полного чтения в памяти (1 ГБ +) с другими требованиями к памяти приложения.

Я попытался использовать класс .NET StreamReader для выполнения этого (что обеспечивает очень удобную функциональность "ReadLine", но невозможно зафиксировать истинную позицию файла (позиция в свойстве BaseStream перекошена из-за буфера, который использует класс).

Нет ли простого способа сделать это в .NET?

4b9b3361

Ответ 1

Есть несколько хороших ответов, но я не мог найти исходный код, который бы работал в моем очень упрощенном случае. Вот он, надеясь, что он спасет кого-то еще в тот час, который я потратил на поиски.

"Очень упрощенный случай", который я имею в виду: текстовое кодирование имеет фиксированную ширину, а символы окончания строки одинаковы во всем файле. Этот код хорошо работает в моем случае (где я разбираю файл журнала, и мне когда-нибудь нужно искать в файле, а затем вернуться. Я выполнил достаточно, чтобы сделать то, что мне нужно было сделать (например: только один конструктор, и только переопределить ReadLine()), поэтому, скорее всего, вам нужно будет добавить код... но я думаю, что это разумная отправная точка.

public class PositionableStreamReader : StreamReader
{
    public PositionableStreamReader(string path)
        :base(path)
        {}

    private int myLineEndingCharacterLength = Environment.NewLine.Length;
    public int LineEndingCharacterLength
    {
        get { return myLineEndingCharacterLength; }
        set { myLineEndingCharacterLength = value; }
    }

    public override string ReadLine()
    {
        string line = base.ReadLine();
        if (null != line)
            myStreamPosition += line.Length + myLineEndingCharacterLength;
        return line;
    }

    private long myStreamPosition = 0;
    public long Position
    {
        get { return myStreamPosition; }
        set
        {
            myStreamPosition = value;
            this.BaseStream.Position = value;
            this.DiscardBufferedData();
        }
    }
}

Вот пример использования PositionableStreamReader:

PositionableStreamReader sr = new PositionableStreamReader("somepath.txt");

// read some lines
while (something)
    sr.ReadLine();

// bookmark the current position
long streamPosition = sr.Position;

// read some lines
while (something)
    sr.ReadLine();

// go back to the bookmarked position
sr.Position = streamPosition;

// read some lines
while (something)
    sr.ReadLine();

Ответ 2

FileStream имеет метод seek().

Ответ 3

Вместо StreamReader вы можете использовать System.IO.FileStream. Если вы точно знаете, какой файл содержит (например, кодировку), вы можете выполнять все операции, например, с помощью StreamReader.

Ответ 4

Если вы гибки в том, как записывается файл данных, и не против, чтобы он был немного менее удобным для редактирования текста, вы могли записать свои записи с помощью BinaryWriter:

using (BinaryWriter writer = 
    new BinaryWriter(File.Open("data.txt", FileMode.Create)))
{
    writer.Write("one,1,1,1,1");
    writer.Write("two,2,2,2,2");
    writer.Write("three,3,3,3,3");
}

Затем сначала чтение каждой записи прост, потому что вы можете использовать метод ReadString BinaryReader:

using (BinaryReader reader = new BinaryReader(File.OpenRead("data.txt")))
{
    string line = null;
    long position = reader.BaseStream.Position;
    while (reader.PeekChar() > -1)
    {
        line = reader.ReadString();

        //parse the name out of the line here...

        Console.WriteLine("{0},{1}", position, line);
        position = reader.BaseStream.Position;
    }
}

BinaryReader не буферизируется, поэтому вы получаете правильную позицию для хранения и использования позже. Единственная проблема состоит в анализе имени из строки, что может быть связано с StreamReader.

Ответ 5

Является ли кодирование фиксированным размером (например, ASCII или UCS-2)? Если это так, вы можете отслеживать индекс символа (в зависимости от количества символов, которые вы видели) и находить на нем двоичный индекс.

В противном случае нет - вам в принципе нужно написать собственную реализацию StreamReader, которая позволит вам заглянуть в двоичный индекс. Позор, который StreamReader не реализует, согласен.

Ответ 7

Несколько вопросов, которые могут представлять интерес.

1) Если строки являются фиксированным набором символов в длину, это необязательно полезная информация, если набор символов имеет переменные размеры (например, UTF-8). Поэтому проверьте свой набор символов.

2) Вы можете определить точное положение курсора с помощью StreamReader с помощью значения BaseStream.Position IF. Сначала вы будите() буферов (что заставит текущую позицию быть там, где начнется следующее чтение - по одному байту после последнего байта).

3) Если вы заранее знаете, что точная длина каждой записи будет одинакового количества символов, а набор символов использует символы фиксированной ширины (поэтому каждая строка имеет такое же количество байтов), вы можете использовать FileStream с фиксированным размером буфера, чтобы соответствовать размеру строки, и позиция курсора в конце каждого чтения будет, perforce, началом следующей строки.

4) Есть ли какая-то особая причина, почему, если строки имеют одинаковую длину (в байтах здесь), вы не просто используете номера строк и вычисляете смещение байта в файле на основе номера строки x строки

Ответ 8

Вы уверены, что файл слишком большой? Вы пробовали это таким образом, и это вызвало проблему?

Если вы выделяете большой объем памяти, и вы не используете его прямо сейчас, Windows просто заменит его на диск. Следовательно, обратившись к нему из "памяти", вы достигнете того, что хотите - произвольного доступа к файлу на диске.

Ответ 9

Этот точный вопрос был задан в 2006 году здесь: http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic40275.aspx

Резюме:

"Проблема заключается в том, что StreamReader буферизует данные, поэтому значение, возвращаемое в Свойство BaseStream.Position всегда опережает фактическую обработанную строку.

Однако, "если файл закодирован в текстовой кодировке с фиксированной шириной, вы можете отслеживать, сколько текста было прочитано и умножить на ширину"

а если нет, вы можете просто использовать FileStream и читать char за раз, а затем свойство BaseStream.Position должно быть правильным