目录
windows操作系统的文件系统:每个文件夹下都有两个默认隐藏的文件夹:
0x00.文件基本操作
-
C文件操作:FILE* fopen fwrite fread fseek ftell fclose
-
C++文件操作:fstream文件流对象 file.open file.wirte file.read file.seekg file.close
-
windows文件操作:不太常用,因为用C和C++的文件操作就可以了,前两者是跨平台的,在Linux上也可以使用。
-
用HANDLE句柄来标识文件
-
创建打开文件用 CreateFile
-
写文件 WriteFile
-
读文件 ReadFile
-
设置文件内容指针 SetFilePointer
-
拷贝文件 CopyFile()
-
获取文件大小 GetFileSize
-
移动文件 MoveFile
-
删除文件 DeleteFile
-
关闭文件 CloseHandle
-
当我们在写操作系统内核的代码 需要进行文件操作时,没得C和C++编译器供我们使用,只能用这些函数,所以必须掌握。
-
当一个函数你不知道怎么用的时候,vs编译器按F1或者F12查阅相关文档即可。
HANDLE CreateFileA(//返回创建或者打开后的文件句柄,如果失败,返回-1
LPCSTR lpFileName,//文件名
DWORD dwDesiredAccess,//访问方式 GENERIC_ALL 可读可写,GENERIC。。 只读 ,GENERIC。。只写
DWORD dwShareMode,//共享方式,指文件对于操作系统的很多用户的共享方式
LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全属性 SECURITY_开头
DWORD dwCreationDisposition,//创建(CREATE_AlWAYS) 还是 打开(OPEN_ALWAYS)
DWORD dwFlagsAndAttributes,//方式和属性
HANDLE hTemplateFile//模板文件句柄
);
CreateFile不仅仅可以用来打开或者创建一个文件,它可以用来打开或者创建一个设备(凡是可以和windows通信的都叫设备,包括文件(读写)、文件夹、逻辑磁盘驱动器(读写),物理磁盘驱动器(读写)、控制台(输入输出)等等)
windows为CreateFile打开的文件 创建一个文件内核对象,用这个内核对象来管理这个文件。多次调用CreateFile打开同一个文件,虽然是同一个文件,但是windows每次都会创建一个新的内核对象来管理它。每个内核对象中都有一个文件内容指针。纸箱应该读写的地址。
而所谓的文件句柄,不过是文件内核对象在进程句柄表的中的索引。
windows文件操作的好处:
1.可以创建隐藏文件:dwFlagsAndAttributes中如果选择FILE_ATTRIBUTE_HIDDEN可创建一个隐藏文件,这是C语言的文件操作所不能办到的。
2.windows文件操作是“线程安全的”,C或C++的文件操作则不是。所谓线程安全,就是当一个文件被当前进程所打开或者创建时,该文件就被该进程的该线程所占有,其他进程的线程无法读写该文件,直到该线程closehandle(即将对应文件的内核对象从该进程的句柄表中删除)或者该线程结束或者该进程结束。
3.很多其他操作:
- 获取文件大小
- 拷贝文件
- 移动文件
// win32wenjian.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <windows.h>
int main()
{
//1.使用该函数需要添加头文件 windows.h
//2.该函数的作用:
(1)如果同文件中没有该文件,创建该文件。如果有该文件,打开该文件(文件名处为路径名)。
(2)返回文件句柄给一个变量,方便操作文件。
HANDLE hFile=CreateFile("file.txt",GENERIC_ALL,
FILE_SHARE_READ|FILE_SHARE_WRITE,/*共享方式 有 共享读,共享写,共享删除,全部以FILE_SHARE_开头*/
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,//普通文件
NULL);
if (INVALID_HANDLE_VALUE == hFile)//如果hFile == -1 即创建文件失败
{
printf("创建文件失败!\n");
return -1;
}
printf("创建文件成功!\n");
while (1);
return 0;
}
0x01写入:
//写入
char buff[256] = "像我这么优秀的人,本该灿烂过一生\n";//16*2=32
DWORD writeOff = 0;
bool is = WriteFile(hFile, buff, strlen(buff),//总共写入多少个字符
&writeOff//成功写入了多少字节,一般我们会创建一个DWORD类型的变量,将变量的地址写进去
, NULL);//是否有其他操作,NULL表示没有
//之所以要创建一个wirteOff变量,是因为WriteFile函数只有一个返回值,返回写入成功与否,具体成功写入了多少字符,需要用我们创建的变量writeOff来返回。
if (is)
printf("写入%d字节\n", writeOff);//如果写入的文字变成了乱码,需要将当前项目属性改为多字节字符集
else
printf("写入失败!\n");
0x02获取文件大小:
DWORD 就是unsigned long 4个字节,2^32-1B=4 294 967 295B=4GB,即如果仅仅用返回值表示size的话,最大只能表示4GB,但是一个文件可能比4GB大。所以专门设置了一个参数:文件大小的高字节fileSizeHigh,将文件大小分成两个变量来存。低字节存在size中,高字节部分存在另一个变量中。
注意:打开文件和创建文件的 参数不同,CREATE_ALWAYS OPEN_ALWAYS
//得到文件
HANDLE hMkv = CreateFile("123.mkv", GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hMkv == INVALID_HANDLE_VALUE) {
cout << "打开失败!" << endl;
return 0;
}
DWORD fileSizeHigh;
DWORD size = GetFileSize(hMkv,&fileSizeHigh);
long long totalSize = size | (long long)fileSizeHigh << 32;
printf("文件大小为%ld字节\n", totalSize);
0x03设置文件内容指针:
//设置文件的内容指针
LONG high = 0;
SetFilePointer(hFile,
33,//移动多少个字节
&high,
FILE_BEGIN//从什么地方开始移动
);
DWORD num;
WriteFile(hFile, "奈何20多年到头来,还在人海里浮沉",
strlen("奈何20多年到头来,还在人海里浮沉"),
&num, NULL);
0x04拷贝文件:
//**拷贝文件**
bool ret = CopyFile("file.txt",//已经存在的文件名
"Song.txt",//新的文件名
true);
if (ret)
printf("拷贝成功!\n");
else
printf("拷贝失败!\n");
//用这个函数可以写一个U盘偷猎者
0x05移动文件:
//**移动文件**
MoveFile("file.txt",//已经存在的文件名
"..//dustbin.txt");//移动到上层文件夹中的。。
DeleteFile("song.txt");
0x06关闭文件句柄:
//关闭文件句柄
CloseHandle(hFile);
//只要不关闭文件句柄,现在的进程就一直占据这个文件
//即拥有该文件的操作权限,禁止其他程序来去操作这个文件
//直到关闭文件句柄。实现原理:线程锁
0x07.文件夹操作
windows操作系统的文件系统:每个文件夹下都有两个默认隐藏的文件夹:
名称为.的文件夹:表示当前文件夹
名称为..的文件夹:表示上层文件夹
0x08实现文件夹遍历:
思路:写一个函数 TravelDirectory(传入文件夹名),函数功能就是遍历一个文件夹。怎么遍历呢?当然是用递归来实现,最简单的情形的就是文件夹中没有文件,直接返回。那么如何缩小问题规模呢?当然是采用分治的思想来分布解决这个问题了。遍历一个文件夹可以分为两步,第一步、找到第一个文件,判断是文件还是文件夹,如果找到的是一个文件夹就调用TravelDirectory遍历。如果是文件,就输出文件名。第二步、不断找下一个文件。直到没有下一个文件就返回。
是不是感觉这个思路 和 深度寻路算法很相似呢?其实问题的本质就是深度寻路算法的问题。只不过,遍历文件夹,问题更简单,因为windows的所有文件其实就是一个森林,每一个盘符都是一个树。所以我们不需要像深度遍历图一样,准备一个辅助数组来记录每个点是不是已经走过。
下面我们来详细讨论每一步具体怎么做:
1.通配符号:
%d | int |
\n | 换行 |
\\ | \ |
%% | % |
* | 对应任意个任意字符 |
? | 对应一个任意字符 |
2.找指定文件(从头开始找):
注意:并不是像函数名说的找第一个文件。这个函数就是找指定的文件。
如果能找到指定文件,创建对应的文件对象,返回该文件对象在当前进程的句柄。如果找不到指定名字的文件,返回值为INVALID_HANDLE_VALUE.
HANDLE FindFirstFileA(
LPCSTR lpFileName, //你要找的文件的名字
LPWIN32_FIND_DATAA lpFindFileData //你找到的文件的相关信息,根据属性可以判断是文件还是文件夹
);
使用实例:
WIN32_FIND_DATA fileInfo;
HANDLE hFile = FindFirstFile("123.mkv", &fileInfo);
if (INVALID_HANDLE_VALUE == hFile) {
cout << "找不到" << endl;
return 0;
}
cout << "找到了" << endl;
我们用一个结构体来存该文件的相关信息,WIN32_FIND_DATA结构体定义如下:
typedef struct _WIN32_FIND_DATAW {
DWORD dwFileAttributes;//文件属性
FILETIME ftCreationTime;//创建时间
FILETIME ftLastAccessTime;//最后一次访问时间
FILETIME ftLastWriteTime;//最后一次写入时间
DWORD nFileSizeHigh;//文件大小
DWORD nFileSizeLow;//文件大小
DWORD dwReserved0;//保留位,更新迭代之用
DWORD dwReserved1;//保留位,更新迭代之用
_Field_z_ WCHAR cFileName[ MAX_PATH ];//文件名
_Field_z_ WCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;
其中文件属性包括:
#define FILE_ATTRIBUTE_READONLY 0x00000001
#define FILE_ATTRIBUTE_HIDDEN 0x00000002
#define FILE_ATTRIBUTE_SYSTEM 0x00000004
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
#define FILE_ATTRIBUTE_ARCHIVE 0x00000020
#define FILE_ATTRIBUTE_DEVICE 0x00000040
#define FILE_ATTRIBUTE_NORMAL 0x00000080
#define FILE_ATTRIBUTE_TEMPORARY 0x00000100
#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200
#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400
#define FILE_ATTRIBUTE_COMPRESSED 0x00000800
#define FILE_ATTRIBUTE_OFFLINE 0x00001000
#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
#define FILE_ATTRIBUTE_ENCRYPTED 0x00004000
#define FILE_ATTRIBUTE_INTEGRITY_STREAM 0x00008000
#define FILE_ATTRIBUTE_VIRTUAL 0x00010000
#define FILE_ATTRIBUTE_NO_SCRUB_DATA 0x00020000
#define FILE_ATTRIBUTE_EA 0x00040000
#define FILE_ATTRIBUTE_PINNED 0x00080000
#define FILE_ATTRIBUTE_UNPINNED 0x00100000
#define FILE_ATTRIBUTE_RECALL_ON_OPEN 0x00040000
#define FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS 0x00400000
通过这个函数,我们只要将文件名设置为“传入的文件夹名\\*”,就可以找到文件夹中的第一个任意文件。
3.找下一个文件:给定一个文件对象句柄,找到文件夹中该文件的下一个文件。如果能找到下一个文件,返回值为真,如果找不到下一个文件,返回值为假。我们遍历文件夹就是一个循环,我们可以用这个函数来作为循环结束的条件。
BOOL
WINAPI
FindNextFileA(
_In_ HANDLE hFindFile,
_Out_ LPWIN32_FIND_DATAA lpFindFileData
);
源代码:无注释版。这个代码经过修改,可以查找磁盘中特定后缀的文件。并进行特定操作。
// 20-windows文件操作.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <Windows.h>
using namespace std;
void travelDirectory(char* pathName) {
TCHAR fileName[MAX_PATH] = { 0 };
sprintf(fileName, "%s\\*", pathName);
WIN32_FIND_DATA fileInfo;
HANDLE hFile = FindFirstFile(fileName, &fileInfo);
if (INVALID_HANDLE_VALUE == hFile) {
//cout << "空文件夹" << endl;
return;
}
int ret = 1;
TCHAR tempName[MAX_PATH] = { 0 };
while (ret) {
if (fileInfo.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) {
if (fileInfo.cFileName[0] != '.') {//跳过".文件夹和..文件夹"
memset(tempName, 0, sizeof(TCHAR)*MAX_PATH);
sprintf(tempName, "%s\\%s", pathName, fileInfo.cFileName);
//printf("文件夹:%s\n", tempName);
travelDirectory(tempName);
}
}
#if 0
else if (fileInfo.dwFileAttributes == FILE_ATTRIBUTE_NORMAL) {
printf("文件:%s\n", fileInfo.cFileName);
}
#endif
else if(fileInfo.dwFileAttributes == FILE_ATTRIBUTE_HIDDEN){
printf("隐藏文件:%s\n", fileInfo.cFileName);
}
#if 1
else {
printf("其他文件:%s\n", fileInfo.cFileName);
}
#endif
ret = FindNextFile(hFile, &fileInfo);
}
return;
}
int main()
{
travelDirectory((char*)"F:");
cout << "遍历完成" << endl;
return 0;
}
注释版:
// win32文件夹操作.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <windows.h>//用windows的宏都要加这个头文件
int count = 0;
void findFile(char* fileName) {
//1.设置文件名 fileName//*.*
char findFileName[MAX_PATH] = { 0 }; //define MAX_PATH 260 在windows操作系统上,一个文件的名字,最大260个字节
sprintf(findFileName, "%s\\*.*", fileName);// 反斜杠需要转义 *.*也可以修改为*
/*
Windows中常用的通配符是
* 可以代替所有的字母/中文
? 可以代替一个字母/中文
*/
//2.循环找文件
WIN32_FIND_DATA findData;//结构体,用来专门装文件的属性
HANDLE hFile = FindFirstFile(findFileName, &findData);//
if (INVALID_HANDLE_VALUE == hFile) {//如果没有找到第一个文件
return;
}
int ret = 1;
char temp[MAX_PATH];
while (ret) {//找不到为止
//2.1判断是文件夹还是文件
if (FILE_ATTRIBUTE_DIRECTORY == findData.dwFileAttributes) {
//2.1.1文件夹
if (findData.cFileName[0] != '.')//防止陷入 .文件夹 和 ..文件夹的死循环里面
{
//2.1.1.1设置文件名
memset(temp, 0, MAX_PATH);
sprintf(temp, "%s\\%s", fileName, findData.cFileName);
printf("%d:%s\n", count++, temp);
Sleep(1000);
//2.1.1.2遍历文件夹
findFile(temp);
}
}
else {
//2.1.2文件
//打印文件名 或者do someting else
memset(temp, 0, MAX_PATH);
sprintf(temp, "%s\\%s", fileName, findData.cFileName);
printf("%d:%s\n", count++, temp);
Sleep(1000);
}
//2.2找下一个文件
ret = FindNextFile(hFile, &findData);//如果找到了下一个文件,返回1,没找到返回0
}
}
int main()
{
//获取当前文件夹名
char currentDirName[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH,currentDirName);//获取当前文件夹名称,储存在一个字符数组中
findFile(currentDirName);
}
//作业:
//代码行数查看器。
//找到一个文件夹下所有的.c .cpp文件统计一共写了多少行代码
0x09.文件映射
3.1虚拟内存 和 物理内存
物理内存:内存条。
虚拟内存:物理内存映射而来。
3.2 文件映射虚拟内存:
io操作:常规的文件操作方式,需要从磁盘中读取或者写入磁盘
内存操作:CPU直接读写内存,效率更高
可以像操作内存来操作内存,因为文件io(读写)很慢。
3.3.文件映射编程模型:
- 打开文件 CreateFile
- 创建文件映射 CreateFileMapping
- 加载文件映射 MapViewOfFile:把文件映射为内存
- 使用映射内存
- 卸载文件映射 UnmapViewOfFile
- 关闭文件映射 CloseHandle
- 关闭文件 CloseHandle
// 文件映射.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <windows.h>
int main()
{
//step1:创建文件
HANDLE hFile = CreateFile("American.txt", GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS/*只读*/, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile) {
printf("创建文件失败!\n");
return -1;
}
printf("创建文件成功!\n");
//step2:创建文件映射
HANDLE hFileMapping = CreateFileMapping(hFile,
NULL,//文件映射的属性:NULL表示默认的属性
PAGE_READWRITE,//保护方式:PAGE_开头表示文件映射以 内存页(4K)为单位
0,//高位文件大小
40,//低位文件大小 文件多大 映射多大 40字节
"fileMapping1"//映射的名字
);
//step3:加载文件映射:即我们可以像使用内存一样去使用这个文件了
void* p = MapViewOfFile(hFileMapping,//文件映射的句柄
FILE_MAP_ALL_ACCESS,//访问方式:任何方式,可读可写可操作
0, //开始的地方
0, //开始的地方
40 //总共的长度
);//返回一个指针,这个指针就是映射出来的内存段首地址。
//step4:使用文件映射出来的内存,给内存中赋值 0-9
int* pp = (int*)p;
for (int i = 0; i < 10; i++) {
printf("%d", *pp);
pp++;
}
//step5:卸载文件映射
UnmapViewOfFile(p);
//step6:删除文件映射
CloseHandle(hFileMapping);
//step7:关闭文件
CloseHandle(hFile);
}
选做:创建一个保存有4G个电话号码的文件,查找某个电话号码。
方式1.直接读文件
方式2.映射文件为虚拟内存,内存操作。
作业:代码行数统计器
// 代码行数统计者.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include<string.h>
int judge(char* str);
int counter = 0;//统计代码行数
int num=0;//统计C文件数量
int dirs = 0;//统计文件夹数量
void findFile(char* filename){
char findFileName[MAX_PATH] = { 0 };
sprintf(findFileName, "%s\\*.*",filename);
WIN32_FIND_DATA findFileData;
HANDLE hFile = FindFirstFile(findFileName, &findFileData);
if (hFile == INVALID_HANDLE_VALUE) {
printf("该文件夹中没有.C文件\n");
return;
}
int ret = 1;
char temp[MAX_PATH];
while (ret) {
if (FILE_ATTRIBUTE_DIRECTORY ==findFileData.dwFileAttributes) {//如果是文件夹
if (findFileData.cFileName[0] != '.') {
dirs++;
printf("第%d个文件夹:%s\n",dirs,findFileData.cFileName);
memset(temp, 0, MAX_PATH);
sprintf(temp, "%s\\%s", filename, findFileData.cFileName);
findFile(temp);
}
}
else {
if (judge(findFileData.cFileName))//如果是一个.c文件
{
HANDLE hCpp = CreateFile(findFileData.cFileName, GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hCpp) {
printf("打开文件失败\n");
}
else {
int lines = 0;
num++;
printf("第%d个C文件:%s\n", num, findFileData.cFileName);
printf("打开第%d个文件成功!开始分析代码行数...\n", num);
//得到文件大小
DWORD FileSizeHigh = 0;
DWORD FileSizeLow = GetFileSize(hCpp, &FileSizeHigh);
printf("该文件低位大小为:%d B,高位大小为%d B\n ", (int)FileSizeLow, (int)FileSizeHigh);
Sleep(1000);
//创建文件映射
HANDLE hFileMapping = CreateFileMapping(hCpp,
NULL,//文件映射的属性:NULL表示默认的属性
PAGE_READWRITE,//保护方式:PAGE_开头表示文件映射以 内存页(4K)为单位
FileSizeHigh,//高位文件大小
FileSizeLow,//低位文件大小 文件多大 映射多大
"fileMapping1"//映射的名字
);
//step3:加载文件映射:即我们可以像使用内存一样去使用这个文件了
void* p = MapViewOfFile(hFileMapping,//文件映射的句柄
FILE_MAP_ALL_ACCESS,//访问方式
0, //开始的地方
0, //开始的地方
FileSizeLow //总共的长度
);//返回一个指针,这个指针就是映射出来的内存段首地址
/*
MapViewOfFile()函数允许全部或部分映射文件,在映射时,
需要指定数据文件的偏移地址以及待映射的长度。其中,文
件的偏移地址由DWORD型的参数dwFileOffsetHigh和dwFileOffsetLow
组成的64位值来指定,而且必须是操作系统的分配粒度的整数倍,对于Windows操作系统,分配粒度固定为64KB。
。
*/
char * ppBuffer = (char*)p;
if (ppBuffer == NULL) {
printf("文件指针为空,退出\n");
return;
}
for (int i = 0; i < (int)FileSizeLow; i++) {
if (*ppBuffer++ == '\n') {
counter++;
lines++;
}
}
printf("该文件代码行数大约为:%d\n", lines);
//step5:卸载文件映射
UnmapViewOfFile(p);
//step6:删除文件映射
CloseHandle(hFileMapping);
}
CloseHandle(hCpp);
}
}
ret = FindNextFile(hFile, &findFileData);
}
}
int judge(char* str) {
int len = strlen(str);
if ((
*(str + len - 1) == 'p'&&
*(str + len - 2) == 'p'&&
*(str + len - 3) == 'c'&&
*(str + len - 4) == '.') || ((*(str + len - 1) == 'c') && (*(str + len - 2) == '.')))
return 1;
else
return 0;
}
int main()
{
char currentDirectoryName[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, currentDirectoryName);
findFile(currentDirectoryName);
printf("小盆友,到目前为止你一共写了%d行代码!\n", counter);
while (1);
}