原文链接:https://www.likecs.com/show-306421749.html,原文中代码是C++MFC程序,更详细。本文是C语言测试代码。

(1)稀疏文件(Sparse File)定义

指的是文件中出现大量的0数据,这些数据对我们用处不大,但是却一样的占用空间。NTFS文件系统对此进行了优化,那些无用的0字节被用一定的算法压缩起来。例如声明一个很大的稀疏文件(如100GB),这个文件实际上并不需要占用那么大的空,内部都是一些无用的0数据,那么NTFS就会利用算法释放这些无用的0字节空间,这是对磁盘占用空间的一种优化。但要注意FAT32并不支持稀疏文件的压缩。

实例场景:假设我们要创建一个内存映射文件来存储录音的数据。当用户说话的时候,我们把数字音频数据写入到内存缓存,并以磁盘文件作为内存缓存的后备存储器。一个部分调拨的内存映射文件当然是最简单和最高效的方式。问题在于我们并不知道用户在停止录制之前会说多久,可能是五分钟,可能是五小时…这差距还是很大的。所以我们需要一个足够大的文件来保存这些数据。单是,在使用稀疏调拨的内存映射文件时,大小并没有多大关系。

(2)与稀疏文件操作有关的函数

①判断系统是否支持稀疏文件:GetVolumeInformation 函数

通过传出的参数lpFileSystemFlags & FILE_SUPPORTS_SPARSE_FILES判断结果是否为FILE_SUPPORTS_SPARSE_FILES。
  
②判断一个文件是否是稀疏文件:GetFileInformationByHandle 函数

    BY_HANDLE_FILE_INFORMATION stFileInfo;
    GetFileInformationByHandle(hFile, &stFileInfo);
    当stFileInfo.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE为TRUE时表示稀疏文件。
  
③产生一个稀疏文件:DeviceIoControl(hFile,FSCTL_SET_SPARS,...);

  大部分文件,在改变它的EndOfFile的时候,中间的空白会被操作系统填0,也就是说,如果用SetFilePointer和SetEndOfFile来产生一个很大的文件,那么这个文件它占用的是真正的磁盘空间,即使里面全是0,系统默认的也会在DeviceIoControl()中的ControlCode里用FSCTL_SET_ZERO_DATA标记,这个标记使得那些文件空洞被0所填充。为了节省磁盘空间,我们必须把一个文件声明为稀疏文件,以便让系统把那些无用的0字节压缩,并释放相应的磁盘空间,要将标记改为FSCTL_SET_SPARSE。

④查找稀疏文件中包含非零数据的范围:DeviceIoControl(hFile, FSCTL_QUERY_ALLOCATED_RANGES,...);

(3)以稀疏文件为后备存储器的内存映射文件(一般的使用步骤)

① 创建文件:hFile = CreateFile(...);

② 产生稀疏文件
  DeviceIoControl(hFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dw, NULL));

③ 创建内存映射文件对象
  hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE,…);

④ 映射到进程的地址空间
   pvFile = MapViewOfFile(hFileMap, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0);

⑤ 读、写文件(像直接读写内存一样的读写文件)
⑥ 撤消映射:UnmapViewOfFile(pvFile);
⑦ 关闭句柄:CloseHandle(hFileMap);CloseHandle(hFile);

#include <windows.h>
#include <iostream>
#include <string>
#include <winioctl.h>
using namespace std; #define FL (1024*1024)
#define FH 0
char g_szPath[MAX_PATH];
char g_Volume[MAX_PATH];
//(3797960) int main()
{
// 先检查当前磁盘是否支持稀疏文件
memset(g_Volume, 0, MAX_PATH);
GetCurrentDirectory(MAX_PATH, g_szPath);
strncpy_s(g_Volume, g_szPath, 3);
DWORD dwFileSystemFlags = 0;
bool bOK = GetVolumeInformation(g_Volume, NULL, 0, NULL, NULL, &dwFileSystemFlags, NULL, 0);// 获取磁盘信息
bOK = bOK && (dwFileSystemFlags & FILE_SUPPORTS_SPARSE_FILES);// dwFileSystemFlags & FILE_SUPPORTS_SPARSE_FILES 查看标志位判断对稀疏文件的支持
if (!bOK)
{
// 如果系统不支持稀疏文件,就直接关闭程序
return 1;
} // ① 创建文件
DWORD dw;
string FileName = "MMF.txt";
HANDLE hFile = CreateFile(FileName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
int err = GetLastError();
if (hFile == INVALID_HANDLE_VALUE)
{
cout << "打开文件失败" << endl;
return 1;
} // ② 产生稀疏文件
bool ret = DeviceIoControl(hFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dw, NULL);// 设置稀疏文件
err = GetLastError();
if (!ret)
{
cout << "err no:" << err << endl;
return 2;
} // ③ 创建内存映射文件对象
HANDLE hFileMapping = ::CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 1024 * 1024, NULL); // 申请1M,但实际测试只有64KB if (!hFileMapping)
{
CloseHandle(hFileMapping);
cout << "创建文件映像失败" << endl;
return 2;
} // ④ 映射到进程的地址空间
LPVOID pFile = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); // ⑤ 读、写文件(像直接读写内存一样的读写文件) char WriteIn[] = "写入测试";
memcpy((LPVOID)((INT64)pFile + 0x120), WriteIn, strlen(WriteIn) + 1);
FlushViewOfFile(pFile, 4); // 不强制刷新的话,后面获取文件磁盘占用大小会失败,因为还没刷新到文件中。 LARGE_INTEGER lpFileSize;
GetFileSizeEx(hFile, &lpFileSize);
printf("文件实际大小(压缩前大小):\n%lld bytes\n%4.2f KB\n%4.2f MB\n%4.2f GB\n", lpFileSize.QuadPart, (float)lpFileSize.QuadPart / 1024, (float)lpFileSize.QuadPart / (1024 * 1024), (float)lpFileSize.QuadPart / (1024 * 1024 * 1024)); ULARGE_INTEGER ulFileSize;
ulFileSize.LowPart = GetCompressedFileSize("C:\\Users\\Lenovo\\source\\repos\\test2333\\test2333\\MMF.txt", &ulFileSize.HighPart);
err = GetLastError();
printf("文件磁盘大小\n%lld bytes\n%4.2f KB\n%4.2f MB\n%4.2f GB\n", ulFileSize.QuadPart, (float)ulFileSize.QuadPart / 1024, (float)ulFileSize.QuadPart / (1024 * 1024), (float)ulFileSize.QuadPart / (1024 * 1024 * 1024)); // ⑥ 撤消映射
// ⑦ 关闭句柄
UnmapViewOfFile(pFile);
CloseHandle(hFileMapping);
CloseHandle(hFile); system("pause");
return 0;
}

测试结果:申请1M,但实际测试只有64KB。
测试写入时也不用考虑文件尾,因为1M大小就是文件尾。像是之前写的读写文件的测试代码(https://www.cnblogs.com/renleiguanchashi/p/16910946.html),如果写入超过文件尾,会写不进去,要先设置文件尾更大。这里因为文件尾是1M处,很大了,不需要再设置文件尾了。

17.5 稀疏调拨的内存映射文件--《Windows核心编程》的更多相关文章

  1. 文件映射(Windows核心编程)

    映射内存的可执行文件和dll 当一个线程调用CreateProcess的时候,系统会执行以下步骤: 系统会先确定CreateProcess所指定的可执行文件的所在位置.如果找不到文件,那么Create ...

  2. 第17章 内存映射文件(3)_稀疏文件(Sparse File)

    17.8 稀疏调拨的内存映射文件 17.8.1 稀疏文件简介 (1)稀疏文件(Sparse File):指的是文件中出现大量的0数据,这些数据对我们用处不大,但是却一样的占用空间.NTFS文件系统对此 ...

  3. 《windows核心编程》 17章 内存映射文件

    内存映射文件主要用于以下三种情况: 系统使用内存映射文件载入并运行exe和dll,这大量节省了页交换文件的空间以及应用程序的启动时间 开发人员可以使用内存映射文件来访问磁盘上的数据文件.这使得我们可以 ...

  4. 【WIN32进阶之路】:内存映射文件

    第一章:源起  遇到一个问题,如果一个客户数据文件有2g大,客户要通过界面查询文件中的数据并用列表控件显示数据,要怎么处理这个文件才能让应用程序不会长时间无响应,客户感觉不到程序的卡顿? 第二章:解决 ...

  5. 【NIO】NIO之浅谈内存映射文件原理与DirectMemory

    Java类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段.本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原 ...

  6. JAVA NIO之浅谈内存映射文件原理与DirectMemory

    JAVA类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段.本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原 ...

  7. 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件

    本文背景: 在编程中,很多Windows或C++的内存函数不知道有什么区别,更别谈有效使用:根本的原因是,没有清楚的理解操作系统的内存管理机制,本文企图通过简单的总结描述,结合实例来阐明这个机制. 本 ...

  8. 《windows核心编程系列》十六谈谈内存映射文件

    内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...

  9. C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 VC中进程与进程之间共享内存 .net环境下跨进程、高频率读写数据 使用C#开发Android应用之WebApp 分布式事务之消息补偿解决方案

    C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing). ...

  10. Windows核心编程 第十七章 -内存映射文件(下)

    17.3 使用内存映射文件 若要使用内存映射文件,必须执行下列操作步骤: 1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件. 2) 创建一个文件映射内核对象,告诉系统该 ...

随机推荐

  1. vue 状态管理 四、Action用法

    系列导航 vue 状态管理 一.状态管理概念和基本结构 vue 状态管理 二.状态管理的基本使用 vue 状态管理 三.Mutations和Getters用法 vue 状态管理 四.Action用法 ...

  2. node pressure and pod eviction

    0. overview There are too many guides about node pressure and pod eviction, most of them are specifi ...

  3. jedis 与 redission 实现分布式锁

    本文为博主原创,未经允许不得转载: 目录: 1. Jedis 实现分布式锁 2. Redission 实现分布式锁 为了确保分布式锁可用,至少要保证锁的实现同时满足以下几个条件 互斥性:在任意时刻只有 ...

  4. springboot启动流程 (1) 流程概览

    本文将通过阅读源码方式分析SpringBoot应用的启动流程,不涉及Spring启动部分(有相应的文章介绍). 本文不会对各个流程做展开分析,后续会有文章介绍详细流程. SpringApplicati ...

  5. PageHelper 分页不起作用

    将 reasonable 设置为 false   .

  6. 《OnJava》——11内部类

    内部类 利用内部类,可以将逻辑上存在关联的类组织在一起,而且可以控制一个类在另一个类中的可见性. 内部类和组合不同,内部类是一种代码隐藏机制:将代码放在其他类的内部. 11.1 创建内部类 创建内部类 ...

  7. Go-插入排序

    // InsertSort 插入排序 // 思路: // 1. 第一个元素默认是已经排好序的 // 2. 从第二个元素开始,依次比较前面一个元素中,如果小于则交换位置 // 插入排序思路: 将一个元素 ...

  8. ChatGPT-NextWeb部署和调试打造属于自己的GPT

    首先我关注这个项目有一段时间了,不得不说作者和他的社区真的很猛! 首先这个项目截至目前已经有了40.9K的Start了,Fork也已经有了38.1K了,这个数据真的超级牛批了. 那么我们来看一下这款号 ...

  9. [转帖]WinXP添加TLS1.1、TLS1.2支持

    现象 HTTPS服务在Win7及Win10能够正常打开,但是在XP下用IE浏览器却无法打开,XP下用第三方浏览器(我试了谷歌浏览器)却能正常打开.经过抓包分析,用IE浏览器是协商用的是TLS1而用第三 ...

  10. [转帖]nginx 日志打印响应时间 request_time 和 upstream_response_time

    https://www.cnblogs.com/chooperman/p/14722450.html 设置log_format,添加request_time,$upstream_response_ti ...