.NET C # - произвольный доступ к текстовым файлам - нелегкий путь? - PullRequest
21 голосов
/ 05 ноября 2008

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

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

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

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

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

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

Ответы [ 9 ]

13 голосов
/ 28 мая 2009

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

Я имею в виду «очень упрощенный случай»: кодировка текста имеет фиксированную ширину, а символы конца строки одинаковы во всем файле. Этот код хорошо работает в моем случае (когда я анализирую файл журнала, и мне иногда приходится искать в этом файле, а затем возвращаться. Я реализовал достаточно для выполнения того, что мне нужно было сделать (например, только один конструктор) и переопределяет только 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();
7 голосов
/ 05 ноября 2008

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

5 голосов
/ 06 ноября 2008

Если вы гибко относитесь к тому, как пишется файл данных, и не возражаете против того, чтобы он был немного менее удобен для редактирования текстов, вы можете написать свои записи с помощью 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 голосов
/ 05 ноября 2008

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

2 голосов
/ 05 ноября 2008

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

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

1 голос
/ 29 марта 2010

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

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

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

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

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

1 голос
/ 05 ноября 2008

Я думаю, что функция записи времени выполнения библиотеки FileHelpers может помочь вам. http://filehelpers.sourceforge.net/runtime_classes.html

0 голосов
/ 05 ноября 2008

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

Резюме:

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

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

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

0 голосов
/ 05 ноября 2008

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

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

...