中国开发网: 论坛: 程序员情感CBD: 贴子 639898
haitao
问题:如何快速高效的读一个很大的文本文件 ( 积分:0, 回复:44, 阅读:5691 )
科班出身多——这个恐怕不一定了,呵呵
杠多——偏执狂才能发现、解决问题
跑题远——这样才能顺便发现、解决相关问题,举一反三嘛


http://www.delphibbs.com/delphibbs/dispq.asp?lid=3212410

问题:如何快速高效的读一个很大的文本文件 ( 积分:0, 回复:44, 阅读:5691 )
分类:专家门诊 ( 版主:soul, )
来自:sdcx, 时间:2005-9-20 16:46:00, ID:3212410 [显示:小字体 | 大字体]

文件有几百M,需要按照一个规则n个字节n个字节的去读,如何快速的
读一遍?我用nmap感觉效果也不太好啊。


来自:lzh7735, 时间:2005-9-20 18:18:40, ID:3212588
用MapViewOfFile,把文件映射进内存空间,像读取内存一样操作。
可以分几次每次映射一段,操作员完再映射下一段,也可以一次全部映射完,随你了。



来自:刘麻子, 时间:2005-9-20 22:09:16, ID:3212799
申请一小块内存,一部分一部分得读出来


来自:kinneng, 时间:2005-9-21 0:42:28, ID:3212925
当然是用映射,设置一个视图,n个字节大,移动这个视图,无论文件有多大,速度就像读n个字节的文件一样。


来自:刘麻子, 时间:2005-9-21 8:09:09, ID:3212988
请不要神话“内存映射”,跟那个没太大的关系,它最终也要ReadFile()的,无论如何,大量数据从磁盘到内存所需的时间是免不了的,怎么可能“速度就像读n个字节的文件一样”?“内存映射”的主要目的只是操作方便,可以直接象访问内存一样的访问文件,而由系统负责分配内存、读写磁盘,但是不能说速度肯定比我们自己直接读要快。


来自:sdcx, 时间:2005-9-21 10:53:19, ID:3213249
是啊,本身我就用内存映射了,效率还是不好、
详细情况是这样,一个大文本文件几百M以上吧,我每次读n个字节,这个n个字节是
根据一定规律得出的,一个循环吧整个文本文件读一遍。
现在读100多M文件速度就比较慢,读更大效率更加差。不知道有没有好办法。
一些语句如下
映射:
MARCFile := TMemMapFile.Create(FFileName, fmOpenRead, 0, True);
取字符串:
MarcRecore := copy(PChar(MARCFile.Data), 1, FRecordLeng);
移动:
function TMemMapFile.nextstep(Size: Longint): integer;
begin
Inc(FData, Size);
end;





来自:kinneng, 时间:2005-9-21 11:12:59, ID:3213284
说清楚点,无论多大的文件,如果一次读n个字节,那么速度跟读n个字节的小文件差不多,还有什么要说的。


来自:kinneng, 时间:2005-9-21 11:17:05, ID:3213293
用映射方式,连续读盘,仍感到慢,你可以看看硬盘灯,如果是长亮的,要升级硬件,如果是闪亮的,就要检查程序。


来自:snavy, 时间:2005-9-21 11:27:27, ID:3213316
看来你是在读MARC吧


来自:huiyue, 时间:2005-9-21 11:37:48, ID:3213329
肯定慢,你可以试试Word这种软件
它也一样慢的。


来自:sdcx, 时间:2005-9-21 12:58:23, ID:3213412
是在读marc,看来snavy也研究过啊,下次请教了。

看来读文件效率就这样了,没有什么好说的,i/o在那里。




来自:sdcx, 时间:2005-9-21 14:53:11, ID:3213440
...


来自:sdcx, 时间:2005-9-21 13:45:28, ID:3213497
发现一些问题,当文件大起来读速度变得很慢很慢是由于copy命令,我现在决定自己写个读命令看看效率如何。


来自:sdcx, 时间:2005-9-21 14:29:23, ID:3213565
取字符串:
MarcRecore := copy(PChar(MARCFile.Data), 1, FRecordLeng);


这个程序这样写很有问题,都是我的错,我把它改成手工循环取数据,
效率增加几何倍数。(文件小时候感觉不出copy效率差,文件大了就明显)

大家要引起教训啊。




来自:kinneng, 时间:2005-9-21 15:43:40, ID:3213720
copy是慢,Word慢是因为后台做分页


来自:刘麻子, 时间:2005-9-21 16:17:54, ID:3213782
来自:kinneng, 时间:2005-9-21 11:12:59, ID:3213284
说清楚点,无论多大的文件,如果一次读n个字节,那么速度跟读n个字节的小文件差不多,还有什么要说的。
--------------------------------
这样说就对了 [:D]


来自:lzh7735, 时间:2005-9-26 14:25:44, ID:3219284
映射:
MARCFile := TMemMapFile.Create(FFileName, fmOpenRead, 0, True);
取字符串:
MarcRecore := copy(PChar(MARCFile.Data), 1, FRecordLeng);
移动:
function TMemMapFile.nextstep(Size: Longint): integer;
begin
Inc(FData, Size);
end;

==================================================================

不管直接用ReadFile还是文件映射,在内部都是通过ReadFile来实现,只不过这此细节被操作系统隐藏起来,目的就是为了操作方便。

如kinneng所说,“无论多大的文件,如果一次读n个字节,那么速度跟读n个字节的小文件差不多”,再看楼主的取字符串的代码:
MarcRecore := copy(PChar(MARCFile.Data), 1, FRecordLeng);
本人认为,这种方法也是使效率大打节扣的原因,D对字符串的操作本身就需要付出不小的代价,如果直接使用Move或Copymemory效率会更好![:)]



来自:魏启明, 时间:2005-9-26 15:01:41, ID:3219331
首先几百兆的文件读起来速度本身就是很慢的。
内存映射文件的大小不要大于64k,建议就取64k,(一个内存页面)。

其他就看你的n个字节的处理的效率了。

建议用网络上常用的那个MD5信息提取代码做一下对比测试。它也是内存映射顺序读取文件,每个字节都读取,在大文件时速度还算是快吧。




来自:goodopell110, 时间:2005-10-8 2:49:38, ID:3228891
之所以速度慢,是有两方面的原因:
1。用TFileStream打开文件,操作系统在打开文件后会为文件生成内存镜像,文件一大,那么开辟空间以及内存拷贝的工作就会变得极为缓慢。
2。将TFileStream中的一部分再复制给TMemoryStream,这个复制过程会开辟新的内存再进行复制,理所当然内存大了,复制时间也会变长。

我决心针对目前我所遇到的问题,再写一个文件读取类,目前就叫TFastFileStream吧,它必须从TStream继承而来,这样才能和其它组件方便地结合起来。

首先,要解决的是打开大文件慢的问题,对于这个,使用MapViewOfFile(),将文件直接当作内存镜像来访问就可以了,关于MapViewOfFile(),以及文件内存镜像,可以参考这篇文章:http://www.vccode.com/file_show.php?id=2409

Delphi下建立文件镜像的方法为:
constructor TFastFileStream.Create(const AFileName:String);
var
FileSizeHigh:LongWord;
begin
FFileHandle:=CreateFile(PChar(AFileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if FFileHandle=INVALID_HANDLE_VALUE then begin
raise FastFileStreamException.Create('Error when open file');
end;
FSize:=GetFileSize(FFileHandle,@FileSizeHigh);
if FSize=INVALID_FILE_SIZE then begin
raise FastFileStreamException.Create('Error when get file size');
end;
FMappingHandle:=CreateFileMapping(FFileHandle,nil,PAGE_READONLY,0,0,nil);
if FMappingHandle=0 then begin
raise FastFileStreamException.Create('Error when mapping file');
end;
FMemory:=MapViewOfFile(FMappingHandle,FILE_MAP_READ,0,0,0);
if FMemory=nil then begin
raise FastFileStreamException.Create('Error when map view of file');
end;
end;

最后,被做成镜像的数据就存放在FMemory中了,然后,覆盖TStream的Read()方法,当外部需要取得数据时,让它到FMemory中去取:
function TFastFileStream.Read(var Buffer;Count:LongInt):LongInt;
begin
if (FPosition >= 0) and (Count >= 0) then
begin
Result := FSize - FPosition;
if Result > 0 then
begin
if Result > Count then Result := Count;
//Move(Pointer(Longint(FMemory) + FPosition)^, Buffer, Result);
CopyMemory(Pointer(@Buffer),Pointer(LongInt(FMemory)+FPosition),Result);
Inc(FPosition, Result);
Exit;
end;
end;
Result := 0;
end;
这段函数主要还是模仿TCustomMemoryStream中的同名方法来写的,但是有一点比较奇怪,当我使用Delphi自己的内存拷贝函数Move()时,程序总是会访问到非法地址,所以只好改为用API函数CopyMemory()了。

另外,需要实现的函数还有:
function TFastFileStream.GetSize():Int64;
begin
result:=FSize;
end;

function TFastFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
case Ord(Origin) of
soFromBeginning: FPosition := Offset;
soFromCurrent: Inc(FPosition, Offset);
soFromEnd: FPosition := FSize + Offset;
end;
Result := FPosition;
end;
这样,一套完整的文件读取机制就有了。

由于复杂度的关系,我没有实现文件保存机制,感兴趣的朋友请自己实现吧。

接下去,需要解决的是如何将目前用到的两个Stream的复制操作进行优化,开始想到的办法是,建立一个新的Stream类,它在从别的Stream复制出数据时,不新开内存,而是将内部的内存指针指向源Stream内的数据块中的某一段,但是这样一来,这个Stream类只有在源Stream的生存期内才可用,关系变得似乎有些混乱了。

后来,忽然又想到另一个办法,其实对于外部类来说(即我用到的文件型数据库组件),它只是使用Read(),Seek()等方法来访问数据的,那么我只要用一些欺骗的方法,让内部类返回给外部的只是其内部数据中的某一段就可以了。
对于我的程序来说,在找到我要的数据的位置后,对其设置一个虚拟的数据范围,在以后的外部访问时,都返回该虚拟数据范围内的数据。这样一来,只需要在原TFastFileStream的基础上进行一定的改造就可以了。

procedure TFastFileStream.SetUseableMemory(const StartPos:Int64;const Size:Int64);
begin
FUseableStartPos:=StartPos;
FSize:=Size;
end;

function TFastFileStream.Read(var Buffer;Count:LongInt):LongInt;
begin
...
CopyMemory(Pointer(@Buffer),Pointer(LongInt(FMemory)+FUseableStartPos+FPosition),Result);
...
end;

好了,到此为止改造就结束了,最后换上这个新写的FileStream类,一试,速度果然是惊人的快啊,原来打开一个近30MB的文件,使用两个Stream类,需要约40秒,改成新的TFastFileStream后,只需要一个类就搞定,时间在5秒以内,哈哈,果然爽阿!

如果需要这个类的完整代码,可以写信联系我:
tonyki[at]citiz.net




来自:一只笨小狗, 时间:2005-10-24 9:20:42, ID:3241490
直接用内存映射不慢,只要不用copy读数据.


来自:小雨哥, 时间:2006-2-23 12:00:00, ID:3361859
TO:goodopell110
你对你的组件中使用的这个设计思路,确实非常不错。不过,就文中你提到的快速读取文件
类的结构看,在文件大于 100 MB 以上时,我基本可以保证它没有 TMemoryStream 速度快
。TMemoryStream 在这个数量级上一般不会大于 1s ,缺点是文件多大内存也要占用多大,
但这个缺点在你上面提供的类结构中好像也存在。所以,似乎这是个换汤不换药的做法,
除了改造后对你实际应用中的访问更便利外,与Fast没有太大的关系。


来自:爱元元的哥哥, 时间:2006-2-23 12:24:08, ID:3361873
写一个驱动,工作在ring0,用它申请一块比较大的未分页内存,然后把这个内存句柄传递给ring3的应用。ring0找到文件所在的扇区,直接打开磁盘句柄来读扇区,这样跳过了文件系统,省去了内核查询文件句柄,验证读取缓冲区,省去了系统根据FAT或者NTFS查询所对应的物理扇区,省略了杀毒软件的过滤,XP还原系统的监控,我以前自己做的类似ghost的程序可以达到平均每分钟1.7G的速度。


来自:爱元元的哥哥, 时间:2006-2-23 12:34:37, ID:3361879
说清楚点,无论多大的文件,如果一次读n个字节,那么速度跟读n个字节的小文件差不多,还有什么要说的。
---------------------------------------------------------------------
刘麻子上面的说法简直是白痴,读取文件最大的瓶颈是IDE硬盘的磁头定位,这是一个机械寻址的过程,调动机械臂,旋转到指定磁道,然后等速度稳定下来,抓取磁头数据,一般是NT毫秒级别的单位。

磁盘自己的I/O管理器(IDE Controler芯片内提供的)有一定的策略,如果你读一个扇区,磁盘可以预读这个扇区附近的上百个扇区,这就是为什么我们经常要做磁盘整理,速度会快很多。

文件驱动系统内部有I/O函数和FastIO的区别,内存映射大量使用了FastIO提供的cache机制,同时还使用了x86构架缺页中断机制,效率是很高的。


来自:小雨哥, 时间:2006-2-23 12:58:01, ID:3361904
为了不至于流于瞎扯,我使用最典型的 TMemoryStream 的读取演示就可以看到我上面说的
内存流速度: http://www.delphibbs.com/keylife/images/u179470/LoadTest.rar

这个压缩包内一个是数据产生程序,默认可以产生一个 123MB 的数据文件,一个就是装入
测试,不仅装入,它还同时统计数据文件的条目,并显示耗用时间。有兴趣不妨看看速度。

爱兄说的没错,IDE 是一个极限,但我倒并不很同意一定要去无极限地突破。


来自:小雨哥, 时间:2006-2-23 13:19:51, ID:3361928
为什么我一看到上面 goodopell110 的做法我就很肯定他没有 TMemoryStream 速度快呢?
原因很简单,如果没有弥补掉 TMemoryStream 的内存占用问题,而仅仅是改用 API 提供
的方法,那么,在内存申请上,API 的速度比 Delphi 的速度要慢最少 30%,一个 600MB
以上的内存分配,API 甚至要比 Delphi 慢 130% 以上。


来自:dreamisx, 时间:2006-2-23 13:29:18, ID:3361934
我不知道那么多,我只知道用TFileStream, TMemoryStream的Read方法一次读入一块(比如4K)速度是很快的。


来自:小雨哥, 时间:2006-2-23 14:41:44, ID:3362011
关于内存分配速度的测试,可以看我下面这个程序的结果。

注意:使用 API 分配大内存时请等待分配完成再操作,机器内存小者勿执行本程序。

其中,二者的内存分配都以提交为目标,并都以清空分配到的内存为标准。

http://www.delphibbs.com/keylife/images/u179470/Memory.rar


来自:zqw0117, 时间:2006-2-23 15:24:15, ID:3362061
学习兼关注下,这是DFW最近以来比较有水平的讨论!


来自:金卡绣球jk8.com, 时间:2006-2-23 16:14:07, ID:3362147
以前当它是BYTE来处理,每1M读进来,再处理,再
很快的呀


来自:小雨哥, 时间:2006-2-25 19:13:39, ID:3364456
不去追求爱兄那种死活不管只要极限的境界的话,“快”这个字在实际应用中可以区分为感
官上的快和实际运算的快。一个实际运算最快需要 3 s 钟的运算,从开始运算到结果出来
,即便他已经使用了公认的最快的算法,给人的感觉还是慢。同样的运算,如果他能考虑每
500ms 左右就给人一个不同的信息反馈,即便他实际上比纯粹的运算需要更多的时间,一个
3 s 的等待还是会给人以不算太慢的感觉。这是很普遍的行为逻辑。

从技术角度来看,一个“快”的操作,本身受到很多因素的制约,不必过度单纯地考虑我到
底要用哪种文件载入方式,以为找到了一种快速载入文件的办法就可以获得“快”的前提。
就象楼上 goodopell110 所做的,他通过巧妙地安排第二个流的读法,显然比刻意寻找一种
文件的载入方式更有效,而实际上他所选用的文件载入方式,严格地追究起来还是慢的。最
后,通过对载入的文件选择合适的处理方式,在我看来比选择一个“快”的文件载入方式更
重要。这可以从我上面的例子的演示就可以看到,因为我觉得这个选择比上一个选择的余地
要大得多,就像一个 Memo 要装入 3MB 文本那样,如果不使用分页机制,在载入前的全部
“快”的努力,就会在载入的瞬间全部前功尽弃。


来自:冲浪, 时间:2006-3-9 0:40:47, ID:3376183
我决得这个网址大全很实用的,请大家多多支持
http://16311.855.com


来自:weihua200205, 时间:2006-3-26 15:21:01, ID:3394117
我是一个新手.我想问一下.为什么我的Delphi7用起来的时候总是说我的函数这里没声明,那里没定义.
是不是我的delphi没装好.还是配置上有问题。


来自:sdcx, 时间:2006-4-30 9:34:36, ID:3431499
很久没有来了,竟然还讨论了那么多啊,真是感谢大家


来自:delphiroad, 时间:2006-9-5 0:03:22, ID:3562734
直接用io操作是最快的了

www.lucky-power.com


来自:oushengfen, 时间:2007-1-5 11:36:20, ID:3652370
刘麻子上面的说法简直是白痴,读取文件最大的瓶颈是IDE硬盘的磁头定位,这是一个机械寻址的过程,调动机械臂,旋转到指定磁道,然后等速度稳定下来,抓取磁头数据,一般是NT毫秒级别的单位。

磁盘自己的I/O管理器(IDE Controler芯片内提供的)有一定的策略,如果你读一个扇区,磁盘可以预读这个扇区附近的上百个扇区,这就是为什么我们经常要做磁盘整理,速度会快很多。

文件驱动系统内部有I/O函数和FastIO的区别,内存映射大量使用了FastIO提供的cache机制,同时还使用了x86构架缺页中断机制,效率是很高的。

这个说得还不错.


来自:dcms, 时间:2007-1-5 11:39:43, ID:3652374
关键是想办法变大为小才是正途啊!

文件太大,用上面那些办法都是不可取的:)

我就不明白文件为什么要搞那么大,理由是什么?


来自:shadowpj, 时间:2007-1-5 12:25:12, ID:3652405
好贴~~学习中


来自:shadowpj, 时间:2007-1-5 12:28:39, ID:3652407
小雨哥,可以把你的Tableload源码给我学习下吗。谢谢啊shadowpj@163.com



来自:我爱PASCAL, 时间:2007-1-5 13:45:04, ID:3652462
再快也不会超过硬盘的转速。


来自:SupermanTm, 时间:2007-1-5 14:14:53, ID:3652500
这样的帖子应加精置顶!
近年内都少见到如此核心的讨论了


来自:zhaokaien, 时间:2007-1-5 16:33:24, ID:3652650
多线程+内存映射


来自:xsj_by, 时间:2007-2-4 8:31:12, ID:3665595
小雨哥,可以把你的Tableload源码给我学习下吗。谢谢啊bysyxx@163.com



来自:yahoo123, 时间:2007-2-27 15:46:11, ID:3673552
Tableload源码请继续谈论,不要停止啊


来自:yahoo123, 时间:2007-2-27 16:16:52, ID:3673579
小雨哥,可以把你的Tableload源码给我学习下吗。谢谢啊fangchan2008@yahoo.com.cn



来自:johnneo, 时间:2007-8-18 14:50:10, ID:3824535
可以不可以将文件分段处理呢?浏览应该问题不大,检索的时候该怎么办呢?
我的blog:http://szhaitao.blog.hexun.com & http://www.hoolee.com/user/haitao
--以上均为泛泛之谈--
不尽牛人滚滚来,无边硬伤纷纷现 人在江湖(出来的),哪能不挨刀(总归是要的)
网络对话,歧义纷生;你以为明白了对方的话,其实呢?

您所在的IP暂时不能使用低版本的QQ,请到:http://im.qq.com/下载安装最新版的QQ,感谢您对QQ的支持和使用

相关信息:


欢迎光临本社区,您还没有登录,不能发贴子。请在 这里登录