Получить последние 10 строк очень большого текстового файлa> 10 ГБ

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

Ответ 1

Прочитайте до конца файла, затем отложите назад, пока не найдете десять строк новой строки, а затем прочитайте до конца, принимая во внимание различные кодировки. Обязательно обрабатывайте случаи, когда количество строк в файле меньше десяти. Ниже приведена реализация (в С#, помеченная тегом), обобщенная, чтобы найти последний numberOfTokens в файле, расположенном в path, закодированном в encoding, где разделитель токенов представлен tokenSeparator; результат возвращается как string (это можно улучшить, возвращая IEnumerable<string>, который перечисляет токены).

public static string ReadEndTokens(string path, Int64 numberOfTokens, Encoding encoding, string tokenSeparator) {

    int sizeOfChar = encoding.GetByteCount("\n");
    byte[] buffer = encoding.GetBytes(tokenSeparator);


    using (FileStream fs = new FileStream(path, FileMode.Open)) {
        Int64 tokenCount = 0;
        Int64 endPosition = fs.Length / sizeOfChar;

        for (Int64 position = sizeOfChar; position < endPosition; position += sizeOfChar) {
            fs.Seek(-position, SeekOrigin.End);
            fs.Read(buffer, 0, buffer.Length);

            if (encoding.GetString(buffer) == tokenSeparator) {
                tokenCount++;
                if (tokenCount == numberOfTokens) {
                    byte[] returnBuffer = new byte[fs.Length - fs.Position];
                    fs.Read(returnBuffer, 0, returnBuffer.Length);
                    return encoding.GetString(returnBuffer);
                }
            }
        }

        // handle case where number of tokens in file is less than numberOfTokens
        fs.Seek(0, SeekOrigin.Begin);
        buffer = new byte[fs.Length];
        fs.Read(buffer, 0, buffer.Length);
        return encoding.GetString(buffer);
    }
}

Ответ 2

Я, скорее всего, просто открою его как двоичный поток, попытаюсь до конца, а затем вернусь искать разрывы строк. Создайте резервную копию 10 (или 11 в зависимости от этой последней строки), чтобы найти 10 строк, а затем просто прочитайте до конца и используйте Encoding.GetString на том, что вы читаете, чтобы получить его в строковый формат. Разделите по желанию.

Ответ 3

Tail? Tail - это команда unix, которая отображает последние несколько строк файла. Существует версия Windows в ресурсе Windows 2003 Server.

Ответ 4

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

Ответ 5

Вы можете использовать FileStream.Seek() для перемещения в конец файла, а затем работать в обратном порядке, глядя для \n, пока не будет достаточно строк.

Ответ 6

Я не уверен, насколько это будет эффективно, но в Windows PowerShell получить последние десять строк файла так же просто, как

Get-Content file.txt | Select-Object -last 10

Ответ 7

Вот что делает команда хвоста unix. См. http://en.wikipedia.org/wiki/Tail_(Unix)

Существует множество реализаций с открытым исходным кодом в Интернете, и вот один для win32: Tail for WIn32

Ответ 8

Я думаю, что следующий код разрешит prblem с тонкими изменениями, исправляющими кодировку

StreamReader reader = new StreamReader(@"c:\test.txt"); //pick appropriate Encoding
reader.BaseStream.Seek(0, SeekOrigin.End);
int count = 0;
while ((count < 10) && (reader.BaseStream.Position > 0))
{
    reader.BaseStream.Position--;
    int c = reader.BaseStream.ReadByte();
    if (reader.BaseStream.Position > 0)
        reader.BaseStream.Position--;
    if (c == Convert.ToInt32('\n'))
    {
        ++count;
    }
}
string str = reader.ReadToEnd();
string[] arr = str.Replace("\r", "").Split('\n');
reader.Close();

Ответ 9

Вы можете использовать версию Windows tail и просто вывести ее в текстовый файл с помощью символa > или вида это на экране в зависимости от ваших потребностей.

Ответ 10

вот моя версия. НТН

using (StreamReader sr = new StreamReader(path))
{
  sr.BaseStream.Seek(0, SeekOrigin.End);

  int c;
  int count = 0;
  long pos = -1;

  while(count < 10)
  {
    sr.BaseStream.Seek(pos, SeekOrigin.End);
    c = sr.Read();
    sr.DiscardBufferedData();

    if(c == Convert.ToInt32('\n'))
      ++count;
    --pos;
  }

  sr.BaseStream.Seek(pos, SeekOrigin.End);
  string str = sr.ReadToEnd();
  string[] arr = str.Split('\n');
}

Ответ 11

Если вы откроете файл с помощью FileMode.Append, он будет искать в конце файла для вас. Затем вы можете запросить количество требуемых байтов и прочитать их. Это может быть не так быстро, хотя независимо от того, что вы делаете с этого довольно массивного файла.

Ответ 12

Один полезный метод - FileInfo.Length. Он задает размер файла в байтах.

Какова структура вашего файла? Вы уверены, что последние 10 строк будут ближе к концу файла? Если у вас есть файл с 12 строками текста и 10 ГБ 0, то смотреть в конец на самом деле не так быстро. Опять же, вам, возможно, придется просмотреть весь файл.

Если вы уверены, что файл содержит множество коротких строк в каждой строке, попробуйте до конца, а затем проверьте, пока вы не подсчитали 11 строк. Затем вы можете читать вперед для следующих 10 строк.

Ответ 13

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

Вы можете использовать такой инструмент, как tail (или powershell), или вы можете написать некоторый немой код, который ищет конец файла, а затем оглядывается назад на n строк новой строки.

Существует множество реализаций хвоста в Интернете - посмотрите исходный код, чтобы увидеть, как это делают они. Хвост довольно эффективен (даже на очень больших файлах), и поэтому они, должно быть, правильно поняли, когда они его написали!

Ответ 14

Откройте файл и начните чтение строк. После того, как вы прочитали 10 строк, откройте другой указатель, начиная с начала файла, поэтому второй указатель отстает от первого на 10 строк. Продолжайте читать, перемещая два указателя в унисон, пока первый не достигнет конца файла. Затем используйте второй указатель, чтобы прочитать результат. Он работает с любым размером файла, включая пустые и короче длины хвоста. И это легко настроить для любой длины хвоста. Недостатком, конечно же, является то, что вы в конечном итоге читаете весь файл, и это может быть именно то, чего вы пытаетесь избежать.

Ответ 15

Если у вас есть файл с четным форматом на строку (например, система daq), вы просто используете streamreader для получения длины файла, затем возьмите одну из строк (readline()).

Разделить общую длину по длине строки. Теперь у вас есть общее длинное число, чтобы представить количество строк в файле.

Ключ состоит в том, что вы используете readline() до получения данных для своего массива или чего-то еще. Это гарантирует, что вы начнете в начале новой строки и не получите оставшихся данных из предыдущего.

StreamReader leader = new StreamReader(GetReadFile);
leader.BaseStream.Position = 0;
StreamReader follower = new StreamReader(GetReadFile);

int count = 0;
string tmper = null;
while (count <= 12)
{
    tmper = leader.ReadLine();
    count++;
}

long total = follower.BaseStream.Length; // get total length of file
long step = tmper.Length; // get length of 1 line
long size = total / step; // divide to get number of lines
long go = step * (size - 12); // get the bit location

long cut = follower.BaseStream.Seek(go, SeekOrigin.Begin); // Go to that location
follower.BaseStream.Position = go;

string led = null;
string[] lead = null ;
List<string[]> samples = new List<string[]>();

follower.ReadLine();

while (!follower.EndOfStream)
{
    led = follower.ReadLine();
    lead = Tokenize(led);
    samples.Add(lead);
}

Ответ 16

Используя Sisutil в качестве отправной точки, вы можете прочитать файл по строкам и загрузить их в Queue<String>. Он читает файл с самого начала, но имеет силу не пытаться прочитать файл назад. Это может быть очень сложно, если у вас есть файл с кодировкой ширины переменной ширины, такой как UTF-8, как указал Джон Скит. Он также не делает никаких предположений о длине строки.

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

int numberOfLines = 10;
string fullFilePath = @"C:\Your\Large\File\BigFile.txt";
var queue = new Queue<string>(numberOfLines);

using (FileStream fs = File.Open(fullFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) 
using (BufferedStream bs = new BufferedStream(fs))  // May not make much difference.
using (StreamReader sr = new StreamReader(bs)) {
    while (!sr.EndOfStream) {
        if (queue.Count == numberOfLines) {
            queue.Dequeue();
        }

        queue.Enqueue(sr.ReadLine());
    }
}

// The queue now has our set of lines. So print to console, save to another file, etc.
do {
    Console.WriteLine(queue.Dequeue());
} while (queue.Count > 0);    

Ответ 17

У меня была одна и та же проблема - огромный файл журнала, к которому нужно получить доступ через интерфейс REST. Конечно, загрузить его в любую память и отправить его через http не было решением.

Как отметил Джон, у этого решения есть очень специфическая утилита. В моем случае, я точно знаю (и проверяю), что кодировка является utf-8 (с BOM!) И, таким образом, может извлечь выгоду из всех благ UTF. Это, конечно, не решение общей цели.

Вот что сработало для меня очень хорошо и быстро (я забыл закрыть поток - теперь исправлено):

    private string tail(StreamReader streamReader, long numberOfBytesFromEnd)
    {
        Stream stream = streamReader.BaseStream;
        long length = streamReader.BaseStream.Length;
        if (length < numberOfBytesFromEnd)
            numberOfBytesFromEnd = length;
        stream.Seek(numberOfBytesFromEnd * -1, SeekOrigin.End);

        int LF = '\n';
        int CR = '\r';
        bool found = false;

        while (!found) {
            int c = stream.ReadByte();
            if (c == LF)
                found = true;
        }

        string readToEnd = streamReader.ReadToEnd();
        streamReader.Close();
        return readToEnd;
    }

Сначала мы ищем где-то ближе к концу с BaseStream, и когда у нас есть правильный поток positon, читайте до конца с помощью обычного StreamReader.

Это не позволяет указать количество строк, составляющих конец, что в любом случае не является хорошей идеей, так как строки могут быть произвольно длинными и, таким образом, снова убивать производительность. Поэтому я указываю количество байтов, считанных до тех пор, пока мы не доберемся до первой строки Newline и с комфортом прочитаем до конца. Теоретически вы также можете искать CarriageReturn, но в моем случае это было необязательно.

Если мы используем этот код, это не помешает нити записи:

        FileStream fileStream = new FileStream(
            filename,
            FileMode.Open,
            FileAccess.Read,
            FileShare.ReadWrite);

        StreamReader streamReader = new StreamReader(fileStream);

Ответ 18

Если вам нужно прочитать любое количество строк в обратном направлении от текстового файла, используйте LINQ-совместимый класс, который вы можете использовать. Он фокусируется на производительности и поддержке больших файлов. Вы можете прочитать несколько строк и вызвать Обратный(), чтобы получить последние несколько строк в прямом порядке:

Использование

var reader = new ReverseTextReader(@"C:\Temp\ReverseTest.txt");
while (!reader.EndOfStream)
    Console.WriteLine(reader.ReadLine());

Класс ReverseTextReader:

/// <summary>
/// Reads a text file backwards, line-by-line.
/// </summary>
/// <remarks>This class uses file seeking to read a text file of any size in reverse order.  This
/// is useful for needs such as reading a log file newest-entries first.</remarks>
public sealed class ReverseTextReader : IEnumerable<string>
{
    private const int BufferSize = 16384;   // The number of bytes read from the uderlying stream.
    private readonly Stream _stream;        // Stores the stream feeding data into this reader
    private readonly Encoding _encoding;    // Stores the encoding used to process the file
    private byte[] _leftoverBuffer;         // Stores the leftover partial line after processing a buffer
    private readonly Queue<string> _lines;  // Stores the lines parsed from the buffer

    #region Constructors

    /// <summary>
    /// Creates a reader for the specified file.
    /// </summary>
    /// <param name="filePath"></param>
    public ReverseTextReader(string filePath)
        : this(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.Default)
    { }

    /// <summary>
    /// Creates a reader using the specified stream.
    /// </summary>
    /// <param name="stream"></param>
    public ReverseTextReader(Stream stream)
        : this(stream, Encoding.Default)
    { }

    /// <summary>
    /// Creates a reader using the specified path and encoding.
    /// </summary>
    /// <param name="filePath"></param>
    /// <param name="encoding"></param>
    public ReverseTextReader(string filePath, Encoding encoding)
        : this(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read), encoding)
    { }

    /// <summary>
    /// Creates a reader using the specified stream and encoding.
    /// </summary>
    /// <param name="stream"></param>
    /// <param name="encoding"></param>
    public ReverseTextReader(Stream stream, Encoding encoding)
    {          
        _stream = stream;
        _encoding = encoding;
        _lines = new Queue<string>(128);            
        // The stream needs to support seeking for this to work
        if(!_stream.CanSeek)
            throw new InvalidOperationException("The specified stream needs to support seeking to be read backwards.");
        if (!_stream.CanRead)
            throw new InvalidOperationException("The specified stream needs to support reading to be read backwards.");
        // Set the current position to the end of the file
        _stream.Position = _stream.Length;
        _leftoverBuffer = new byte[0];
    }

    #endregion

    #region Overrides

    /// <summary>
    /// Reads the next previous line from the underlying stream.
    /// </summary>
    /// <returns></returns>
    public string ReadLine()
    {
        // Are there lines left to read? If so, return the next one
        if (_lines.Count != 0) return _lines.Dequeue();
        // Are we at the beginning of the stream? If so, we're done
        if (_stream.Position == 0) return null;

        #region Read and Process the Next Chunk

        // Remember the current position
        var currentPosition = _stream.Position;
        var newPosition = currentPosition - BufferSize;
        // Are we before the beginning of the stream?
        if (newPosition < 0) newPosition = 0;
        // Calculate the buffer size to read
        var count = (int)(currentPosition - newPosition);
        // Set the new position
        _stream.Position = newPosition;
        // Make a new buffer but append the previous leftovers
        var buffer = new byte[count + _leftoverBuffer.Length];
        // Read the next buffer
        _stream.Read(buffer, 0, count);
        // Move the position of the stream back
        _stream.Position = newPosition;
        // And copy in the leftovers from the last buffer
        if (_leftoverBuffer.Length != 0)
            Array.Copy(_leftoverBuffer, 0, buffer, count, _leftoverBuffer.Length);
        // Look for CrLf delimiters
        var end = buffer.Length - 1;
        var start = buffer.Length - 2;
        // Search backwards for a line feed
        while (start >= 0)
        {
            // Is it a line feed?
            if (buffer[start] == 10)
            {
                // Yes.  Extract a line and queue it (but exclude the \r\n)
                _lines.Enqueue(_encoding.GetString(buffer, start + 1, end - start - 2));
                // And reset the end
                end = start;
            }
            // Move to the previous character
            start--;
        }
        // What left over is a portion of a line. Save it for later.
        _leftoverBuffer = new byte[end + 1];
        Array.Copy(buffer, 0, _leftoverBuffer, 0, end + 1);
        // Are we at the beginning of the stream?
        if (_stream.Position == 0)
            // Yes.  Add the last line.
            _lines.Enqueue(_encoding.GetString(_leftoverBuffer, 0, end - 1));

        #endregion

        // If we have something in the queue, return it
        return _lines.Count == 0 ? null : _lines.Dequeue();
    }

    #endregion

    #region IEnumerator<string> Interface

    public IEnumerator<string> GetEnumerator()
    {
        string line;
        // So long as the next line isn't null...
        while ((line = ReadLine()) != null)
            // Read and return it.
            yield return line;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    #endregion
}

Ответ 19

Используя PowerShell, Get-Content big_file_name.txt -Tail 10 где 10 - количество нижних строк для извлечения.

Это не имеет проблем с производительностью. Я запустил его в текстовом файле размером более 100 ГБ и получил мгновенный результат.

Ответ 20

Я использовал этот код для небольшой утилиты некоторое время назад, надеюсь, она вам поможет!

private string ReadRows(int offset)     /*offset: how many lines it reads from the end (10 in your case)*/
{
    /*no lines to read*/
    if (offset == 0)
        return result;

    using (FileStream fs = new FileStream(FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 2048, true))
    {
        List<char> charBuilder = new List<char>(); /*StringBuilder does not work with Encoding: example char 𐍈 */
        StringBuilder sb = new StringBuilder();

        int count = 0;

        /*tested with utf8 file encoded by notepad-pp; other encoding may not work*/

        var decoder = ReaderEncoding.GetDecoder();
        byte[] buffer;
        int bufferLength;

        fs.Seek(0, SeekOrigin.End);

        while (true)
        {
            bufferLength = 1;
            buffer = new byte[1];

            /*for encoding with variable byte size, every time I read a byte that is part of the character and not an entire character the decoder returns '�' (invalid character) */

            char[] chars = { '�' }; //� 65533
            int iteration = 0;

            while (chars.Contains('�'))
            {
                /*at every iteration that does not produce character, buffer get bigger, up to 4 byte*/
                if (iteration > 0)
                {
                    bufferLength = buffer.Length + 1;

                    byte[] newBuffer = new byte[bufferLength];

                    Array.Copy(buffer, newBuffer, bufferLength - 1);

                    buffer = newBuffer;
                }

                /*there are no characters with more than 4 bytes in utf-8*/
                if (iteration > 4)
                    throw new Exception();


                /*if all is ok, the last seek return IOError with chars = empty*/
                try
                {
                    fs.Seek(-(bufferLength), SeekOrigin.Current);
                }
                catch
                {
                    chars = new char[] { '\0' };
                    break;
                }

                fs.Read(buffer, 0, bufferLength);

                var charCount = decoder.GetCharCount(buffer, 0, bufferLength);
                chars = new char[charCount];

                decoder.GetChars(buffer, 0, bufferLength, chars, 0);

                ++iteration;
            }

            /*when i get a char*/
            charBuilder.InsertRange(0, chars);

            if (chars.Length > 0 && chars[0] == '\n')
                ++count;

            /*exit when i get the correctly number of line (*last row is in interval)*/
            if (count == offset + 1)
                break;

            /*the first search goes back, the reading goes on then we come back again, except the last */
            try
            {
                fs.Seek(-(bufferLength), SeekOrigin.Current);
            }
            catch (Exception)
            {
                break;
            }

        }
    }

    /*everithing must be reversed, but not \0*/
    charBuilder.RemoveAt(0);

    /*yuppi!*/
    return new string(charBuilder.ToArray());
}

я прикрепляю экран для скорости

enter image description here

Ответ 21

Почему бы не использовать file.readalllines, который возвращает строку []?

Затем вы можете получить последние 10 строк (или членов массива), которые были бы тривиальной задачей.

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