• 内存映射大文件


    对于一些小文件,用普通的文件流就可以很好的解决,可是对于超大文件,比如2G或者更多,文件流就不行了,所以要使用API的内存映射的相关方法,即使是内存映射,也不能一次映射全部文件的大小,所以必须采取分块映射,每次处理一小部分。

    先来看几个函数

    CreateFile :打开文件

    GetFileSize : 获取文件尺寸

    CreateFileMapping :创建映射

    MapViewOfFile :映射文件 

    看MapViewOfFile的帮助,他的最后两个参数都需要是页面粒度的整数倍,一般机器的页面粒度为64k(65536字节),而我们实际操作中,一般都不是这样规矩的,任意位置,任意长度都是可能的,所以就要做一些处理。

    本例的任务是从一个长度列表中(FInfoList),依次读取长度值,然后到另外一个大文件(FSourceFileName)中去顺序读取指定长度的数据,如果是小文件,这个就好办了,一次读到文件流中,然后依次读取就是了,大数对于大文件,就需要不断改变映射的位置,来取得我们想要的数据。 

    本例中显示先通过GetSystemInfo来获取页面粒度,然后以10倍的页面粒度为一个映射数据块,在for循环中,会判断已经读取的长度(totallen)加上即将读取的长度,是否在本次映射范围之内(10倍的页面粒度),如果在就继续读取,如果超出了,就要记下剩下的数据,然后重新映射下一块内存,并将记录下的剩余数据合并到新读取的数据中,有点绕啊(可能是我的想法太绕了),下面列出代码。 

    procedure TGetDataThread.DoGetData;  

    var  

      FFile_Handle:THandle;  

      FFile_Map:THandle;  

      list:TStringList;  

      p:PChar;  

      i,interval:Integer;  

    begin  

      try  

      totallen := 0;  

      offset := 0;  

      tstream := TMemoryStream.Create;  

      stream := TMemoryStream.Create;  

      list := TStringList.Create;  

      //获取系统信息   

      GetSystemInfo(sysinfo);  

      //页面分配粒度大小   

      blocksize := sysinfo.dwAllocationGranularity;  

      //打开文件   

      FFile_Handle := CreateFile(PChar(FSourceFileName),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);  

      if FFile_Handle = INVALID_HANDLE_VALUE then Exit;  

      //获取文件尺寸   

      filesize := GetFileSize(FFile_Handle,nil);  

      //创建映射   

      FFile_Map := CreateFileMapping(FFile_Handle,nil,PAGE_READONLY,0,0,nil);  

      if FFile_Map = 0 then Exit;  

      //此处我们已10倍blocksize为一个数据块来映射,如果文件尺寸小于10倍blocksize,则直接映射整个文件长度   

      if filesize div blocksize > 10 then  

        readlen := 10*blocksize  

      else  

        readlen := filesize;  

      for i := 0 to FInfoList.Count - 1 do  

      begin  

        list.Delimiter := ':';  

        list.DelimitedText := FInfoList.Strings[i];  

        //取得长度,我这里做了解析,因为我存储的信息为 a:b:c 这种类型,所以以:号分隔   

        len := StrToInt(list.Strings[1]);  

        interval := StrToInt(list.Strings[2]);  

        if (i = 0) or (totallen+len >=readlen) then  

        begin  

          //如果已读取的长度加上即将要读取的长度大于 10倍blocksize,那么我们要保留之前映射末尾的内容,以便和新映射的内容合并   

          if i > 0 then  

          begin  

            offset := offset + readlen;  

            //写入临时流   

            tstream.Write(p^,readlen-totallen);  

            tstream.Position := 0;  

          end;  

          //如果未读取的数据长度已经不够一个分配粒度,那么就直接映射剩下的长度   

          if filesize-offset < blocksize then  

            readlen := filesize-offset;  

          //映射,p是指向映射区域的指针   

          //注意这里第三个参数,一直设为0,这个值要根据实际情况设置   

          p := PChar(MapViewOfFile(FFile_Map,FILE_MAP_READ,0,offset,readlen));  

        end;  

        //如果临时流中有数据,需要合并   

        if tstream.Size > 0 then  

        begin  

          //把临时流数据copy过来   

          stream.CopyFrom(tstream,tstream.Size);  

          //然后在末尾写入新数据,合并完成   

          stream.Write(p^,len-tstream.Size);  

          totallen := len-tstream.Size;  

          //移动指针的位置,指向下一个数据的开始   

          Inc(p,len-tstream.Size);  

          tstream.Clear;  

        end  

        else  

        begin  

          stream.Write(p^,len);  

          totallen := totallen + len;  

          Inc(p,len);  

        end;  

        stream.Position := 0;  

        //将流保存成文件   

        stream.SaveToFile(IntToStr(i)+'.txt');  

        stream.Clear;  

      end;  

      finally  

        stream.Free;  

        tstream.Free;  

        CloseHandle(FFile_Handle);  

        CloseHandle(FFile_Map);  

      end;  

    end;  

  • 相关阅读:
    解决无法进入安全模式
    解决SQLite找不到数据表的问题
    利用js的垃圾回收原理来理解闭包(Closure)问题
    《降级论》《按时交作业的学生何以常穿脏袜子》读后感
    ie6下出现“缺少标识符、字符串或者数字”
    两个想法
    WdatePicker.js帮助文档
    最近一直在研究网站的推广。。。
    这个。。。。我不知道说什么。。。
    今天忽然发现,最近做的网站的一个目标关键字已经实现了。。。
  • 原文地址:https://www.cnblogs.com/hnxxcxg/p/2753265.html
Copyright © 2020-2023  润新知