一个读取速度超快的FileStream!

[入库:2005年8月18日] [更新:2007年3月24日]

本文简介:选择自 tonylk 的 blog

最近一直为自己制作的相册软件(http://www.tonixsoft.com/ultraalbum/index.php?lang=chs)打开大文件时速度慢而郁闷,我以前的做法是先用tfilestream打开一个文件,然后在其中找到其中的数据段,把其中内容复制给一个tmemorystream,之所以要再将它复制给一个独立的tmemorystream是因为,后续处理的一个文件型数据库组件必须接受一整个tstream,作为其存储媒介,整个过程简直慢得无法忍受。

之所以速度慢,是有两方面的原因:
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

本文关键:一个读取速度超快的FileStream!
  相关方案
Google
 

本站最佳浏览方式为 分辨率 1024x768 IE 6.0(或更高版本的 IE浏览器)

go top