Получить последние 10 строк очень большого текстового файла> 10GB
каков наиболее эффективный способ отображения последних 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.Seek () чтобы перейти к концу файла, а затем работать в обратном направлении, ища \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 хвост command и просто pype выводится в текстовый файл с символом > или просматривается на экране в зависимости от ваших потребностей.
вот моя версия. HTH
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 строками текста и 10GB 0s, то просмотр конца не будет таким быстрым. С другой стороны, вам, возможно, придется просмотреть весь файл.
Если вы уверены, что файл содержит множество коротких строк в каждой новой строке, найдите до конца, а затем проверьте, пока вы насчитали 11 концов строк. Затем вы можете прочитать на следующие 10 строк.
Я думаю, что другие плакаты показали, что нет реального ярлыка.
вы можете использовать такой инструмент, как tail (или powershell), или вы можете написать тупой код, который ищет конец файла, а затем оглядывается на n новых строк.
в интернете есть много реализаций tail-взгляните на исходный код, чтобы увидеть, как они сделать это. Хвост довольно эффективен (даже на очень очень больших файлах), и поэтому они должны были получить его правильно, когда они написали это!
откройте файл и начните читать строки. После того, как вы прочитали 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 GB (не было 10Gb один удобный), и это ушло около 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, и когда у нас есть правильный поток positon, прочитайте до конца с обычным StreamReader.
это действительно не позволяет указать количество строк в конце, что в любом случае не является хорошей идеей, поскольку строки могут быть произвольно длинными и, таким образом, снова убивая производительность. Поэтому я указываю количество байтов, читаю, пока не получим до первой строчки и до конца. Теоретически, можно было бы также поискать и карьер, но в моем случае в этом не было необходимости.
Если мы используем этот код, он не будет мешать поток записи:
FileStream fileStream = new FileStream(
filename,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite);
StreamReader streamReader = new StreamReader(fileStream);
Если вам нужно прочитать любое количество строк в обратном порядке из текстового файла, вот LINQ-совместимый класс, который вы можете использовать. Он фокусируется на производительности и поддержке больших файлов. Вы можете прочитать несколько строк и позвонить Reverse() чтобы получить последние несколько строк в прямом порядке:
использование:
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 строк (или членов массива), которые были бы тривиальной задачей.
этот подход не учитывает никаких проблем с кодированием, и я не уверен в точной эффективности этого подхода (время, необходимое для завершения метода и т. д.).