Получить последние 10 строк очень большого текстового файла> 10 ГБ
каков наиболее эффективный способ отображения последних 10 строк очень большого текстового файла (этот конкретный файл превышает 10 ГБ). Я думал просто написать простое приложение на C#, но я не уверен, как это сделать эффективно.
19 ответов:
читать до конца файла, затем искать в обратном направлении, пока не найдете десять новых строк, а затем читать вперед до конца с учетом различных кодировок. Обязательно обрабатывайте случаи, когда количество строк в файле меньше десяти. Ниже приведена реализация (в C# , как вы отметили это), обобщенная, чтобы найти последний
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); } }
Я бы, вероятно, просто открыл его как двоичный поток, искал до конца, а затем вернулся к поиску разрывов строк. Создайте резервную копию 10 (или 11 в зависимости от последней строки), чтобы найти свои 10 строк, а затем просто прочитайте до конца и используйте кодировку.GetString на то, что Вы читаете, чтобы получить его в строковый формат. Разделить по желанию.
хвост? Tail - это команда unix, которая отображает последние несколько строк файла. Существует версия Windows в Windows 2003 Server resource kit.
Как и другие предложили, вы можете перейти к концу файла и читать назад, эффективно. Однако это немного сложно - особенно потому, что если у вас есть кодировка переменной длины (например, UTF-8), вам нужно быть хитрым, чтобы убедиться, что вы получаете "целые" символы.
вы должны быть в состоянии использовать FileStream.Искать () чтобы перейти к концу файла, а затем работать в обратном направлении, ища \n, пока у вас не будет достаточно строк.
Я не уверен, насколько это будет эффективно, но в Windows PowerShell получение последних десяти строк файла так же просто, как
Get-Content file.txt | Select-Object -last 10
Это то, что делает хвостовая команда unix. См.http://en.wikipedia.org/wiki/Tail_ (Unix)
существует много реализаций с открытым исходным кодом в интернете, и вот один для win32:хвост для WIn32
Я думаю, что следующий код будет решать 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();
вы можете использовать версию windows хвост команда и просто введите ее вывод в текстовый файл с символом > или просмотрите его на экране в зависимости от ваших потребностей.
вот моя версия. ХТ
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'); }
Если вы открываете файл с помощью FileMode.Добавить его будет стремиться к концу файла для вас. Затем вы можете найти нужное количество байтов и прочитать их. Это может быть не быстро, хотя независимо от того, что вы делаете, так как это довольно массивный файл.
один полезный метод
FileInfo.Length. Это дает размер файла в байтах.какова структура вашего файла? Вы уверены, что последние 10 строк будет ближе к концу файла? Если у вас есть файл с 12 строками текста и 10 ГБ 0s, то глядя на конец не будет действительно так быстро. С другой стороны, вам, возможно, придется просмотреть весь файл.
Если вы уверены, что файл содержит множество коротких строк каждая на новой строке, искать до конца, а затем проверить до тех пор, пока вы насчитали 11 концов строк. Затем вы можете прочитать на следующие 10 строк.
Я думаю, что другие плакаты все показали, что нет никакого реального ярлыка.
вы можете использовать такой инструмент, как хвост (или powershell), или вы можете написать какой-то тупой код, который ищет конец файла, а затем оглядывается на N новых строк.
есть много реализаций хвоста там в интернете - взгляните на исходный код, чтобы увидеть, как они сделать это. Хвост довольно эффективен (даже на очень очень больших файлах), и поэтому они должны были получить его правильно, когда они это написали!
откройте файл и начните читать строки. После того, как вы прочитали 10 строк, откройте другой указатель, начиная с передней части файла, поэтому второй указатель отстает от первого на 10 строк. Продолжайте читать, перемещая два указателя в унисон, пока первый не достигнет конца файла. Затем используйте второй указатель, чтобы прочитать результат. Он работает с любым размером файла, включая пустой и короче, чем длина хвоста. И это легко настроить для любой длины хвоста. Недостатком, конечно, является то, что вы в конечном итоге читая весь файл и это может быть именно то, что вы пытаетесь избежать.
Если у вас есть файл, который имеет четный формат на строку (например, система 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); }
используя ответ 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);
у меня просто была такая же проблема, огромный файл журнала, который должен быть доступен через интерфейс REST. Конечно, загрузка его в любую память и отправка его полностью через http не было решением.
Как отметил Джон, это решение имеет очень специфический usecase. В моем случае я точно знаю (и проверяю), что кодировка 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, и когда у нас есть правильное положение потока, прочитайте до конца с помощью обычного StreamReader.
это на самом деле не позволяет указать количество строк в конце, что в любом случае не очень хорошая идея, так как линии могут быть сколь угодно длинными и, таким образом, снова убивают производительность. Поэтому я указываю количество байтов, читаю, пока мы не получим до первой новой строки и удобно читать до конца. Теоретически можно было бы также поискать и сам вагон, но в моем случае в этом не было необходимости.
Если мы используем этот код, он не будет мешать поток записи:
FileStream fileStream = new FileStream( filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); StreamReader streamReader = new StreamReader(fileStream);
Если вам нужно прочитать любое количество строк в обратном порядке из текстового файла, вот 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's 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 }
Почему бы не использовать файл.readalllines, который возвращает string[]?
тогда вы можете получить последние 10 строк (или членов массива), которые были бы тривиальной задачей.
этот подход не учитывает никаких проблем с кодировкой, и я не уверен в точной эффективности этого подхода (время, необходимое для завершения метода и т. д.).
Comments