.NET C# - произвольный доступ к текстовым файлам - нелегко?

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

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

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

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
21
0
19 327
9

Ответы 9

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

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

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

Если размер файла превышает 1 ГБ и вы используете 32-разрядную версию, у вас, вероятно, закончится адресное пространство, даже если Windows выкинет свое маленькое сердце.

Roger Lipscombe 05.11.2008 19:17

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

Это бесполезно, когда мы не знаем, куда искать.

Jon Skeet 05.11.2008 19:16

Может быть, мы используем разные определения произвольного доступа. Я (как и Джейсон, по-видимому) считаю, что это означает файл записей с определенным размером в байтах, поэтому начало записи (recnum - 1) * recsize

Powerlord 05.11.2008 19:20

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

Mike Burton 05.11.2008 20:08

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

LeppyR64 06.11.2008 20:39

«позиция в свойстве BaseStream смещена из-за буфера, используемого классом». Похоже, мы, не, знаем, где искать.

Kcats 18.01.2010 17:59

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

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

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

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

Резюме:

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по теме