Файл с отображением в памяти для чтения конца файла? - PullRequest
0 голосов
/ 09 декабря 2010

У меня есть файл 6 ГБ, а последние 20 строк плохие.Я хотел бы использовать отображенный в памяти файл с .NET 4, чтобы прочитать последние несколько строк и отобразить их в console.writelines, а затем перейти на последние 20 строк и заменить их на String.Empty.Какой классный способ сделать это, используя отображенный в памяти файл / поток с примером C #?

Спасибо.

Ответы [ 4 ]

3 голосов
/ 13 января 2011

Файлы с отображением в памяти могут быть проблемой для больших файлов (обычно это файлы, размер которых эквивалентен или превышает размер ОЗУ), в случае, если вы в конечном итоге отобразите файл целиком.Если вы отображаете только конец, это не должно быть реальной проблемой.

В любом случае, здесь есть реализация C #, которая не использует Memory Mapped File, а обычный FileStream.Он основан на реализации ReverseStreamReader (код также включен).Мне было бы любопытно увидеть его по сравнению с другими MMF-решениями с точки зрения производительности и потребления памяти.

public static void OverwriteEndLines(string filePath, int linesToStrip)
{
    if (filePath == null)
        throw new ArgumentNullException("filePath");

    if (linesToStrip <= 0)
        return;

    using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
    {
        using (ReverseStreamReader reader = new ReverseStreamReader(file))
        {
            int count = 0;
            do
            {
                string line = reader.ReadLine();
                if (line == null) // end of file
                    break;

                count++;
                if (count == linesToStrip)
                {
                    // write CR LF
                    for (int i = 0; i < linesToStrip; i++)
                    {
                        file.WriteByte((byte)'\r');
                        file.WriteByte((byte)'\n');
                    }

                    // truncate file to current stream position
                    file.SetLength(file.Position);
                    break;
                }
            }
            while (true);
        }
    }
}

// NOTE: we have not implemented all ReadXXX methods
public class ReverseStreamReader : StreamReader
{
    private bool _returnEmptyLine;

    public ReverseStreamReader(Stream stream)
        : base(stream)
    {
        BaseStream.Seek(0, SeekOrigin.End);
    }

    public override int Read()
    {
        if (BaseStream.Position == 0)
            return -1;

        BaseStream.Seek(-1, SeekOrigin.Current);
        int i = BaseStream.ReadByte();
        BaseStream.Seek(-1, SeekOrigin.Current);
        return i;
    }

    public override string ReadLine()
    {
        if (BaseStream.Position == 0)
        {
            if (_returnEmptyLine)
            {
                _returnEmptyLine = false;
                return string.Empty;
            }
            return null;
        }

        int read;
        StringBuilder sb = new StringBuilder();
        while((read = Read()) >= 0)
        {
            if (read == '\n')
            {
                read = Read();
                // supports windows & unix format
                if ((read > 0) && (read != '\r'))
                {
                    BaseStream.Position++;
                }
                else if (BaseStream.Position == 0)
                {
                   // handle the special empty first line case
                    _returnEmptyLine = true;
                }
                break;
            }
            sb.Append((char)read);
        }

        // reverse string. Note this is optional if we don't really need string content
        if (sb.Length > 1)
        {
            char[] array = new char[sb.Length];
            sb.CopyTo(0, array, 0, array.Length);
            Array.Reverse(array);
            return new string(array);
        }
        return sb.ToString();
    }
}
1 голос
/ 09 января 2011

Судя по вопросу, вам нужен файл Memory Mapped.Однако есть способ сделать это без использования файла, отображенного в памяти.

Откройте файл как обычно, затем переместите указатель файла в конец файла.Когда вы закончите, читайте файл в обратном порядке (уменьшайте указатель файла после каждого чтения), пока не получите желаемое количество символов.

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

Исправьте массив, затем напишитеих обратно.Закрыть, Промыть, Завершить!

0 голосов
/ 15 января 2011

Я ничего не знаю о ReverseStreamReaders.Решение [по существу] простое:

  • Поиск в конце файла
  • Чтение строк в обратном порядке. Подсчет символов каквы идете.
  • Когда вы накопили 20 строк, все готово: установите длину файла в потоке, уменьшив количество символов, содержащихся в 20 строках, и закройтефайл.

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

  1. Вы не можете искать в StreamReader, только в потоке.
  2. Последняя строка файла можетили не могут быть завершены парой CRLF.
  3. Классы ввода / вывода платформы .Net на самом деле не делают различий между CR, LF или CRLF в качестве ограничителей строки.Они просто воспользовались этим соглашением.
  4. В зависимости от кодировки, используемой для хранения файла, чтение назад очень проблематично.Вы не знаете, что представляет собой конкретный октет / байт: он вполне может быть частью последовательности многобайтовой кодировки.Характер! = Байт в этом современном веке.Единственный способ обезопасить себя - это если вы знаете, что файл использует однобайтовую кодировку или, если это UTF-8, он не содержит символов с кодовой точкой больше 0x7F.Я не уверен, что есть хорошее, простое решение, помимо очевидного: последовательно читать файл и не записывать последние двадцать строк.
0 голосов
/ 13 января 2011

Решение состоит из двух частей. Для первой части вам нужно прочитать карту памяти в обратном направлении, чтобы захватить строки, пока вы не прочитаете нужное количество строк (в данном случае 20).

Для второй части вы хотите обрезать файл до последних двадцати строк (установив их в string.Empty). Я не уверен, что вы можете сделать это с картой памяти. Возможно, вам придется где-то сделать копию файла и перезаписать оригинал исходными данными, за исключением последних байтов xxx (которые представляют последние двадцать строк)

Приведенный ниже код извлечет последние двадцать строк и отобразит его.

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

ОБНОВЛЕНИЕ: Чтобы усечь файл, вызовите FileStream.SetLength (lastBytePos)

Я не был уверен, что вы имели в виду, что последние 20 строк плохие. Если диск физически поврежден и данные не могут быть прочитаны, я добавил список badPositions , который содержит позиции, в которых на карте памяти возникли проблемы с чтением данных.

У меня нет файла + 2GB для тестирования, но он должен работать (скрестив пальцы).

using System;
using System.Collections.Generic;
using System.Text;
using System.IO.MemoryMappedFiles;
using System.IO;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            string filename = "textfile1.txt";
            long fileLen = new FileInfo(filename).Length;
            List<long> badPositions = new List<long>();
            List<byte> currentLine = new List<byte>();
            List<string> lines = new List<string>();
            bool lastReadByteWasLF = false;
            int linesToRead = 20;
            int linesRead = 0;
            long lastBytePos = fileLen;

            MemoryMappedFile mapFile = MemoryMappedFile.CreateFromFile(filename, FileMode.Open);

            using (mapFile)
            {
                var view = mapFile.CreateViewAccessor();

                for (long i = fileLen - 1; i >= 0; i--) //iterate backwards
                {

                    try
                    {
                        byte b = view.ReadByte(i);
                        lastBytePos = i;

                        switch (b)
                        {
                            case 13: //CR
                                if (lastReadByteWasLF)
                                {
                                    {
                                        //A line has been read
                                        var bArray = currentLine.ToArray();
                                        if (bArray.LongLength > 1)
                                        {
                                            //Add line string to lines collection
                                            lines.Insert(0, Encoding.UTF8.GetString(bArray, 1, bArray.Length - 1));

                                            //Clear current line list
                                            currentLine.Clear();

                                            //Add CRLF to currentLine -- comment this out if you don't want CRLFs in lines
                                            currentLine.Add(13);
                                            currentLine.Add(10);

                                            linesRead++;
                                        }
                                    }
                                }
                                lastReadByteWasLF = false;

                                break;
                            case 10: //LF
                                lastReadByteWasLF = true;
                                currentLine.Insert(0, b);
                                break;
                            default:
                                lastReadByteWasLF = false;
                                currentLine.Insert(0, b);
                                break;
                        }

                        if (linesToRead == linesRead)
                        {
                            break;
                        }


                    }
                    catch
                    {
                        lastReadByteWasLF = false;
                        currentLine.Insert(0, (byte) '?');
                        badPositions.Insert(0, i);
                    }
                }

            }

            if (linesToRead > linesRead)
            {
                //Read last line
                {
                    var bArray = currentLine.ToArray();
                    if (bArray.LongLength > 1)
                    {
                        //Add line string to lines collection
                        lines.Insert(0, Encoding.UTF8.GetString(bArray));
                        linesRead++;
                    }
                }
            }

            //Print results
            lines.ForEach( o => Console.WriteLine(o));
            Console.ReadKey();
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...