VC编程之VC读取大文件之创建文件映射及文件写入效率
小标 2018-09-04 来源 : 阅读 867 评论 0

摘要:本文主要向大家介绍了VC编程之VC读取大文件之创建文件映射及文件写入效率,通过具体的内容向大家展示,希望对大家学习VC编程有所帮助。

本文主要向大家介绍了VC编程之VC读取大文件之创建文件映射及文件写入效率,通过具体的内容向大家展示,希望对大家学习VC编程有所帮助。

文件太大,没法一次读取到内存进行操作?Windows提供了内存映射API来读取大文件,与普通文件读取相比,内存映射效率比较高。
从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,如图2中过程1,然后再将这些数据拷贝到用户空间,如图2中过程2,在这个过程中,实际上完成了 两次数据拷贝 ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝 。因此,内存映射的效率要比read/write效率高。(引用自//blog.csdn.net/mg0832058/article/details/5890688)

本文主要以代码的方式演示读取大文件的API使用,顺带测试了缓冲区大小与写文件速度的关系,以及绘制文件写入速率图。
HANDLE hFile = NULL;
 HANDLE hFileMap = NULL;
 LARGE_INTEGER liResult;
 hFile = CreateFile(L"e:\\1.zip", GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 if ( INVALID_HANDLE_VALUE == hFile )
 {
  goto __TestEnd;
 }
 //创建文件映射
 hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
 if ( NULL == hFileMap )
 {
  goto __TestEnd;
 }
 //得到系统分配粒度
 SYSTEM_INFO si;
 GetSystemInfo(&si);
 DWORD dwSysGran = si.dwAllocationGranularity;
 //得到文件大小
 LARGE_INTEGER lFileSize;
 GetFileSizeEx(hFile, &lFileSize);
 CloseHandle(hFile);
 hFile = INVALID_HANDLE_VALUE;
 //性能方面,都知道容器大小了就先初始化大小,免得用vector自己的扩容机制浪费CPU
 vecTime.resize(200, 0);
 char szPath[MAX_PATH] = {0};
 for ( int i=1; i<=200; ++i )
 {
  __int64 qwFileOffset = 0;
  __int64 qwFileSize = lFileSize.QuadPart;
  DWORD dwDataLen = 0;
  FILE* fp = NULL;
  DWORD dwBlockBytes = i*dwSysGran;
  if ( lFileSize.QuadPart<dwBlockBytes )
   dwBlockBytes = lFileSize.QuadPart;
  sprintf(szPath, "d:\\test\\%d.zip", i);
  //DeleteFileA(pFile);
  {
   //PERFOR_TEST("复制文件测试4KB_1");
   CPerforTest test(&liResult);
   fp = fopen(szPath, "ab+");
   while( qwFileSize>0 )
   {
    dwDataLen = qwFileSize<dwBlockBytes? qwFileSize : dwBlockBytes;
    LPBYTE lpData = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_READ|FILE_MAP_WRITE, (DWORD)(qwFileOffset>>32),
     (DWORD)(qwFileOffset&0xffffffff), dwDataLen);
    if ( NULL == lpData )
     break;
    //把文件复制到另一个目录下,写文件操作
    //fp = fopen(szPath, "ab+");//追加方式写入文件,不存在则创建
    fwrite(lpData, dwDataLen, 1, fp);
    //fclose(fp);
    UnmapViewOfFile(lpData);
    qwFileOffset += dwDataLen;
    qwFileSize -= dwDataLen;
   }
   fclose(fp);
  }
  vecTime[i-1] = liResult.LowPart/300;
  Sleep(100);
 }
__TestEnd:
 DWORD dwError = GetLastError();
 //内核句柄清理工作
 if ( hFile != INVALID_HANDLE_VALUE )
 {
  CloseHandle(hFile);
  hFile = INVALID_HANDLE_VALUE;
 }
 if ( hFileMap )
 {
  CloseHandle(hFileMap);
  hFileMap = NULL;
 }值得注意的是,内存映射大小必须是系统分配大小基数的倍数。每次读完一段,我们就把这个文件指针位置qwFileOffset后移一段直到读完。还有就是,必须是有多少读多少,最后一次往往其空间比正常分配的小,我们需要计算分配空间:dwDataLen
 = qwFileSize<dwBlockBytes? qwFileSize : dwBlockBytes;不然的话,分配空间大于文件剩余大小,MapViewOfFile就会失败。
统计绘制效率:
//一次冒泡排序找到最小的那个数及其索引,索引很重要,我们可以知道每次写入多大时效率最高
 DWORD dwMinTime = vecTime[0];
 size_t nIndex = 0;
 for ( size_t i=1; i<vecTime.size(); ++i )
 {
  if ( vecTime[i]<dwMinTime )
  {
   dwMinTime = vecTime[i];
   nIndex = i;
  }
 }
 g_dwMinTime = dwMinTime;
 g_nBuffSize = (nIndex+1)*dwSysGran/1024;//换算成KB
 //通知窗口刷新绘制
 g_bInit = true;
 InvalidateRect(hWnd, NULL, TRUE);
 BringWindowToTop(hWnd);
 return 0;
绘制效率图:
 case WM_SIZE:
  {
   g_bSizeChange = true;
   break;
  }
 case WM_PAINT:
  {
   hdc = BeginPaint(hWnd, &ps);
   // TODO: 在此添加任意绘图代码...
   RECT rcClient;
   GetClientRect(hWnd, &rcClient);
   if ( g_bSizeChange )
   {//窗口大小改变了,需要我们重新创建对应大小的缓冲DC
    if ( g_hMemDC )
    {
     DeleteDC(g_hMemDC);
     DeleteObject(g_hMemBmp);
    }
    g_hMemDC = CreateCompatibleDC(hdc);
    g_hMemBmp= CreateCompatibleBitmap(hdc, rcClient.right-rcClient.left,      rcClient.bottom-rcClient.top);
    SelectObject(g_hMemDC, g_hMemBmp);
    g_bSizeChange = false;
   }
   if ( g_bInit )
   {
    HPEN hOldPen = (HPEN)SelectObject(g_hMemDC, g_hPen);
    POINT pt;
    MoveToEx(g_hMemDC, 0, 0, &pt);
    for ( size_t i=0; i<vecTime.size(); ++i )
    {
     LineTo(g_hMemDC, (i+1)*5, rcClient.bottom-vecTime[i]);
    }
    ::SelectObject(g_hMemDC, hOldPen);
    wchar_t szText[100] = {0};
    swprintf(szText, L"缓冲区为:%u KB时,写入文件用时最短:%u", g_nBuffSize, g_dwMinTime);
    SetTextColor(g_hMemDC, RGB(255,0,0));
    SetBkMode(g_hMemDC, TRANSPARENT);
    RECT rcText = {10,0,600,40};
    DrawText(g_hMemDC, szText, wcslen(szText), &rcText, DT_LEFT|DT_VCENTER|DT_SINGLELINE);
   }
   ::BitBlt(hdc, 0, 0, rcClient.right, rcClient.bottom, g_hMemDC, 0, 0, SRCCOPY);
   EndPaint(hWnd, &ps);
  }
  break;
由于循环执行200次,I/O操作相对耗时,为了防止把电脑卡死了,就在每次写完Sleep(100);绘制部分的视图大小有限有的区域无法绘制出来就会出现断线,绘制结果图:

我的测试文件是一个大小为15M左右的文件,缓冲区变化范围:1×63KB----200×63KB,这里最优的竟然是每次写入大约1.7M数据时。

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言VC/MFC频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程