显示非常大的文本文件的最后10行的最有效方法是什么(此特定文件超过10GB)。我只想写一个简单的C#应用程序,但我不确定如何有效地做到这一点。
答案 0 :(得分:74)
读到文件的末尾,然后向后搜索,直到找到10个换行符,然后在考虑各种编码的情况下向前读取。务必处理文件中行数少于10的情况。下面是一个实现(在C#中标记为此),通用于查找位于numberOfTokens
的{{1}}文件中的最后path
,其中标记分隔符由{{1}表示}};结果以encoding
形式返回(这可以通过返回枚举标记的tokenSeparator
来改进。
string
答案 1 :(得分:22)
我可能只是打开它作为二进制流,寻找到最后,然后备份寻找换行符。备份10(或11取决于最后一行)以找到您的10行,然后只读到最后并在您阅读的内容上使用Encoding.GetString将其转换为字符串格式。根据需要拆分。
答案 2 :(得分:17)
尾? Tail是一个unix命令,它将显示文件的最后几行。 Windows 2003 Server resource kit中有一个Windows版本。
答案 3 :(得分:17)
正如其他人所建议的那样,您可以转到文件的末尾并有效地向后阅读。然而,它有点棘手 - 特别是因为如果你有一个可变长度编码(如UTF-8),你需要狡猾地确保你得到“整个”字符。
答案 4 :(得分:6)
你应该可以使用FileStream.Seek()移动到文件的末尾,然后向后工作,寻找\ n直到你有足够的行。
答案 5 :(得分:6)
我不确定它的效率如何,但在Windows PowerShell中获取文件的最后十行就像
一样简单Get-Content file.txt | Select-Object -last 10
答案 6 :(得分:4)
这就是unix tail命令的作用。见http://en.wikipedia.org/wiki/Tail_(Unix)
互联网上有很多开源实现,这里有一个用于win32:Tail for WIn32
答案 7 :(得分:4)
我认为以下代码将通过重新编码重新编码来解决问题
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();
答案 8 :(得分:2)
您可以使用tail命令的Windows版本,只需将其输出打包到带有&gt;的文本文件中。根据您的需求,在屏幕上显示符号或查看它。
答案 9 :(得分:2)
这是我的版本。 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');
}
答案 10 :(得分:1)
我认为其他海报都表明没有真正的捷径。
您可以使用诸如tail(或powershell)之类的工具,也可以编写一些寻找文件结尾的哑代码,然后回顾n个换行符。
网上有大量的尾部实现 - 看看源代码,看看他们如何做到这一点。 Tail是非常有效的(即使是非常大的文件),所以他们写作时必须正确!
答案 11 :(得分:1)
一种有用的方法是FileInfo.Length
。它以字节为单位给出文件的大小。
您的档案是什么结构?你确定最后10行会接近文件的末尾吗?如果你有一个包含12行文本和10GB 0的文件,那么查看结尾并不会那么快。然后,您可能需要查看整个文件。
如果您确定该文件在新行中包含多个短字符串,请搜索结尾,然后再检查,直到您计算了11行结束。然后你可以向前阅读接下来的10行。
答案 12 :(得分:1)
如果使用FileMode.Append打开文件,它将为您寻找文件的末尾。然后你可以找回你想要的字节数并读取它们。不管你做了什么,它可能都不会很快,因为那是一个非常庞大的档案。
答案 13 :(得分:0)
打开文件并开始阅读行。在你读完10行后,从文件的前面开始打开另一个指针,所以第二个指针滞后于第一行10行。继续阅读,同时移动两个指针,直到第一个到达文件的末尾。然后使用第二个指针读取结果。它适用于任何大小的文件,包括空和短于尾长。并且可以轻松调整任何长度的尾巴。 当然,缺点是您最终会阅读整个文件,这可能正是您要避免的。
答案 14 :(得分:0)
如果您的文件每行具有偶数格式(例如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);
}
答案 15 :(得分:0)
使用Sisutil的答案作为起点,您可以逐行读取文件并将其加载到Queue<String>
。它确实从一开始就读取文件,但它具有不尝试向后读取文件的优点。如果像Jon Skeet所指出的那样,如果你有一个像UTF-8那样的可变字符宽度编码的文件,这可能会非常困难。它也不对线长做任何假设。
我针对一个1.7GB的文件测试了这个(没有一个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);
答案 16 :(得分:0)
我遇到了同样的问题,一个巨大的日志文件应该通过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寻找接近结束的地方,当我们有正确的流位置时,使用通常的StreamReader读到最后。
这并不能真正指定结尾的线条数量,这无论如何都不是一个好主意,因为这些线条可能是任意长的,因此会再次破坏性能。所以我指定了字节数,在我们到达第一个换行符之前读取,并且读到最后。 理论上,您也可以查找CarriageReturn,但在我的情况下,这不是必需的。
如果我们使用此代码,它不会打扰编写器线程:
FileStream fileStream = new FileStream(
filename,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite);
StreamReader streamReader = new StreamReader(fileStream);
答案 17 :(得分:0)
如果您需要从文本文件中反向读取任意数量的行,这里可以使用与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
}
答案 18 :(得分:0)
使用PowerShell,Get-Content big_file_name.txt -Tail 10
,其中10是要检索的底行数。
这没有性能问题。我在超过100 GB的文本文件上运行它,并获得了即时结果。
答案 19 :(得分:0)
我前一段时间将此代码用于一个小型实用程序,希望对您有所帮助!
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 doesn't 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());
}
我为速度附加了一个屏幕
答案 20 :(得分:-11)
为什么不使用返回字符串[]?
的file.readalllines然后你可以获得最后10行(或数组成员),这将是一项微不足道的任务。
这种方法没有考虑任何编码问题,我不确定这种方法的确切效率(完成方法所花费的时间等)。