2008-11-05 3 views
19

У меня есть текстовый файл, содержащий несколько «записей» внутри него. Каждая запись содержит имя и набор чисел в качестве данных..NET C# - Случайный доступ в текстовых файлах - нелегкий путь?

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

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

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

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

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

ответ

5

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

0

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

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

+1

Если размер файла превышает 1 ГБ, и вы работаете на 32-битном уровне, вы, вероятно, исчерпаете адресное пространство, даже если Windows поменяет свое маленькое сердце. – 2008-11-05 16:17:42

6

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

+0

Это не полезно, когда мы не знаем, где искать. – 2008-11-05 16:16:14

+0

Возможно, мы используем разные определения случайного доступа. Я (как и Джейсон) полагаю, что это означает файл записей с определенным размером в байтах, таким образом, начало записи (recnum - 1) * recsize – Powerlord 2008-11-05 16:20:45

2

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

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

0

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

Резюме:

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

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

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

5

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

11

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

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

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

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

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

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

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