У меня есть текстовый файл, в котором есть несколько «записей». Каждая запись содержит имя и набор чисел в качестве данных.
Я пытаюсь создать класс, который будет читать файл, представлять только имена всех записей, а затем позволять пользователю выбирать, какие данные записи он хочет.
В первый раз, когда я просматриваю файл, я читаю только имена заголовков, но могу отслеживать «позицию» в файле, где находится заголовок. Мне нужен произвольный доступ к текстовому файлу, чтобы перейти к началу каждой записи после того, как пользователь об этом попросит.
Я должен сделать это таким образом, потому что файл слишком велик, чтобы его можно было полностью прочитать в памяти (1 ГБ +) с другими требованиями к памяти приложения.
Я пробовал использовать для этого класс .NET StreamReader (который обеспечивает очень простую в использовании функциональность ReadLine, но нет способа зафиксировать истинную позицию файла (позиция в свойстве BaseStream смещена из-за буфер, который использует класс).
Нет ли простого способа сделать это в .NET?





Вы можете использовать System.IO.FileStream вместо StreamReader. Если вы точно знаете, какой файл содержит (например, кодировку), вы можете выполнять все операции, как с StreamReader.
Вы уверены, что файл «слишком большой»? Вы пробовали это сделать, и это вызвало проблемы?
Если вы выделяете большой объем памяти и сейчас не используете ее, Windows просто выгружает ее на диск. Следовательно, обратившись к нему из «памяти», вы получите то, что хотите - произвольный доступ к файлу на диске.
FileStream имеет метод seek ().
Это бесполезно, когда мы не знаем, куда искать.
Может быть, мы используем разные определения произвольного доступа. Я (как и Джейсон, по-видимому) считаю, что это означает файл записей с определенным размером в байтах, поэтому начало записи (recnum - 1) * recsize
Что еще более важно, OP предполагает, что они могут записывать индексы потока, с которых начинаются отдельные записи, поэтому знание того, где искать, является решенной проблемой в этом случае.
@Jon: «В первый раз, когда я просматриваю файл, я читаю только имена заголовков, но я могу отслеживать« позицию »в файле, где находится заголовок. Мне нужен произвольный доступ к текстовому файлу, чтобы перейти к началу каждой записи после того, как пользователь об этом попросит ". Похоже, мы знаем, куда искать.
«позиция в свойстве BaseStream смещена из-за буфера, используемого классом». Похоже, мы, не, знаем, где искать.
Кодировка фиксированного размера (например, 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 номера строки?
Если размер файла превышает 1 ГБ и вы используете 32-разрядную версию, у вас, вероятно, закончится адресное пространство, даже если Windows выкинет свое маленькое сердце.