我有一个100GB的文本文件,它是来自数据库的BCP转储文件。当我试图用BULK INSERT
导入它时,在第219506324行出现一个隐藏错误。在解决这个问题之前,我想看看这句台词,可惜我最喜欢的方法是
import linecache
print linecache.getline(filename, linenumber)
正在抛出一个MemoryError
。有趣的是the manual says“这个函数永远不会抛出异常。”在这个大文件上,当我试图读取第1行时,它抛出了一个异常,而我有大约6GB的可用内存。。。
我想知道到那条无法到达的线的最优雅的方法是什么。可用的工具有Python 2、Python 3和C 4(Visual Studio 2010)。是的,我知道我可以做一些
var line = 0;
using (var stream = new StreamReader(File.OpenRead(@"s:\source\transactions.dat")))
{
while (++line < 219506324) stream.ReadLine(); //waste some cycles
Console.WriteLine(stream.ReadLine());
}
但我怀疑这是最优雅的方式。
编辑:我正在等待关闭此线程,因为包含该文件的硬盘驱动器正在被另一个进程使用。我将测试建议的方法和报告时间。谢谢大家的建议和意见。
结果在我实现了Gabes和Alexes方法,以查看哪个更快。如果我做错了什么,一定要说出来。我将使用Gabe建议的方法在100GB文件中查找第1000万行,然后使用Alex建议的方法,我将其松散地转换为C#。。。我唯一要做的是,首先将一个300 MB的文件读入内存,以便清除HDD缓存。
const string file = @"x:\....dat"; // 100 GB file
const string otherFile = @"x:\....dat"; // 300 MB file
const int linenumber = 10000000;
ClearHDDCache(otherFile);
GabeMethod(file, linenumber); //Gabe's method
ClearHDDCache(otherFile);
AlexMethod(file, linenumber); //Alex's method
// Results
// Gabe's method: 8290 (ms)
// Alex's method: 13455 (ms)
gabe方法的实施如下:
var gabe = new Stopwatch();
gabe.Start();
var data = File.ReadLines(file).ElementAt(linenumber - 1);
gabe.Stop();
Console.WriteLine("Gabe's method: {0} (ms)", gabe.ElapsedMilliseconds);
虽然亚历克斯的方法有点诡异:
var alex = new Stopwatch();
alex.Start();
const int buffersize = 100 * 1024; //bytes
var buffer = new byte[buffersize];
var counter = 0;
using (var filestream = File.OpenRead(file))
{
while (true) // Cutting corners here...
{
filestream.Read(buffer, 0, buffersize);
//At this point we could probably launch an async read into the next chunk...
var linesread = buffer.Count(b => b == 10); //10 is ASCII linebreak.
if (counter + linesread >= linenumber) break;
counter += linesread;
}
}
//The downside of this method is that we have to assume that the line fit into the buffer, or do something clever...er
var data = new ASCIIEncoding().GetString(buffer).Split('\n').ElementAt(linenumber - counter - 1);
alex.Stop();
Console.WriteLine("Alex's method: {0} (ms)", alex.ElapsedMilliseconds);
所以除非亚历克斯愿意发表评论,否则我会把加布的解决方案标记为被接受。
好吧,内存可以在任何时间异步和不可预测地耗尽——这就是为什么“从不例外”的承诺并没有真正应用于此(就像,比如说,在Java中,每个方法都必须指定它可以引发哪些异常,一些异常被从这个规则中免除,因为几乎任何方法,都是不可预测的,由于资源短缺或其他系统范围的问题,可以随时提出这些问题)。
linecache
尝试读取整个文件。你唯一简单的选择(希望你不着急)是从一开始就一行一行地读…:这里,
linenum
是基于0的(如果您不喜欢,并且您的Python是2.6或更高版本,请将1
的起始值传递给enumerate
),返回值是无效行号的空字符串。稍微快一点(还有一个更复杂的方法)是一次读取100 MB(二进制模式)到一个缓冲区中;计算缓冲区中的行尾数(只需对缓冲区字符串对象进行一次
.count('\n')
调用);一旦行尾的运行总数超过您要查找的行尾数,就可以找到当前缓冲区中的第n个行尾(其中N
是linenum
之间的区别,这里是基于1的,与之前运行的行结束总数不同),如果N+1
st行结束不在缓冲区中(因为这是行结束的点),则读取更多内容,提取相关子字符串。不仅仅是两行with
的网络,而且返回异常情况…;-)。编辑:由于操作注释怀疑按缓冲区而不是按行读取会影响性能,所以我打开了一段旧代码,在这里我正在测量两种方法,以执行一些相关的任务——用缓冲区方法计算行数,行上循环,或者一口气读取内存中的整个文件(通过
readlines
)。目标文件是kjv.txt
,詹姆斯国王版本圣经的标准英文文本,每节一行,ASCII:平台是MacOS Pro笔记本电脑,OSX 10.5.8,Intel Core 2 Duo,2.4 GHz,Python 2.6.5。
测试的模块,
readkjv.py
:print
只是为了确认课程的正确性(和do;-)。当然,在经过几次测试之后,为了确保每个人都能从操作系统、磁盘控制器和文件系统的预读功能(如果有的话)中获得平等的收益,需要进行以下测量:
这些数字是可以重复的。如你所见,即使在这么小的文件上(小于5 MB!),逐行方法比基于缓冲区的方法慢——只是太浪费精力了!
为了检查可伸缩性,我接下来使用了一个4倍大的文件,如下所示:
而且,正如所预测的,按缓冲区的方法几乎是线性的。外推(当然,这总是一项冒险的工作;-),每秒小于200MB似乎是可预测的性能——称之为每GB 6秒,或者对于100GB可能10分钟。
当然,这个小程序所做的只是行计数,但是(一旦有足够的I/O来分摊恒定的开销;-)一个读取特定行的程序应该有类似的性能(即使它在找到感兴趣的“缓冲区”后需要更多的处理,对于给定大小——假设重复对半缓冲区以识别足够小的一部分,然后一点点努力线性地乘以对半“缓冲区余数”的大小。
优雅?不是真的。。。但是,为了速度,很难打败!-)
您可以尝试使用sed一行:
sed '42q;d'
来获取第42行。它不是用Python或C#编写的,但我想您已经在Mac上使用了sed。这是我的优雅版C#:
或更一般的:
当然,您可能需要在给定行之前和之后显示一些上下文:
或者更流利地说:
顺便说一句,
linecache
模块对大文件没有任何巧妙的处理。它只是把整件事都记下来。它捕获的唯一异常是与I/O相关的(无法访问文件、找不到文件等)。下面是代码的重要部分:换句话说,它试图将整个100GB文件放入6GB的RAM!手册应该说的是“如果这个函数不能访问文件,它将永远不会抛出异常”
相关问题 更多 >
编程相关推荐