每日激励:“困难像弹簧,你弱它就强;你强它就弱。”
绪论:
本章是仿RBMQ项目的分支篇章(后面会更),该文章主要写到对文件增、删、查、改以及一些文件其他操作和目录的创建删除操作,通过写这些文件/目录接口,来直接能通过这个文件类,然后调用文件类中的函数,从而实现快速方便的管理自己的文件。
————————
早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。
File文件操作工具
判断文件是否存在文件大小文件读/写文件创建/删除目录创建/删除项目操作(仿MQ)
helper.hpp中创建类:FileHelper
构造:(文件名):初始化文件名
学习对文件操作的函数
1. 判断文件是否存在函数
1. 使用判断文件状态的函数stat,来判断文件是否存在int stat(const char* path,struct stat* buf)返回值:文件存在返回0,反之返回-1,并且得到buf文件属性结构1. path:文件名2. buf:一个输出型参数,通过该参数得到访问 文件的状态 传入struct stat结构体int stat(const char* path,struct stat* buf);struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */}该结构体中就包含着许多文件的属性如:文件大小:st_size;
2. 修改文件名称函数
int rename(const char *oldpath, const char *newpath);返回值:成功返回0(直接判断返回值是否0)1.oldpath:旧文件名(需要修改的文件)2.newpath:新文件名
3. 删除文件函数
int remove(const char *pathname);1. pathname:文件名返回值:成功返回0,失败返回-1
4. 打开文件函数
#include <fstream>explicit fstream (const char* filename, ios_base::openmode mode = ios_base::in | ios_base::out);filename:打开的文件名mode:打开文件的操作
5. 文件的读取
直接使用fstream类的读取类函数read()istream& read (char* s, streamsize n);1. s:要存储读取数据的地址(输出型参数,也就是吧地址传进去,最终函数会将数据写道改地址上,注意提 前申请空间!)2. n:要提取的字符数是一个有符号整型。设置读位置类函数:fstream::seekg()istream& seekg(streamoff offset,seek_dir origin); 1.offset:偏移量所能取得的最大值,2.seek_dir:表示移动的基准位置offset:1. ios::beg: 文件开头2. ios::cur: 文件当前位置3. ios::end: 文件结尾
6. 创建目录
int mkdir(const char *pathname, mode_t mode);成功返回0,反之非01. pathname:目录名2. mode:权限
7. 使用system可以执行指令
system就是调用系统命令行cmd(在Linux下的shell指令),输入为字符串,然后把这个字符串输出给命令行,让命令行执行。
头文件:#include <sys/stat.h>、 #include <sys/types.h>int system(const char* command);返回值:失败返回-11. command:要命令行中执行的指令
实现逻辑:
类: FileHelper
构造: FileHelper(文件名):初始化文件名
成员变量:
文件名(_filename)成员函数(实现步骤):
文件是否存在函数:
使用库函数:使用 stat 判断文件是否存在创建结构体文件状态结构体(struct stat)调用stat(路径path,状态结构的指针 stat)此时直接通过获取状态的做法,判断文件是否存在存在返回0,反之返回-1文件大小:size_t size()
先判断文件是否存在失败返回0返回st中的st_size变量文件的读(返回bool判断是否读取成功):
从offset开始读取len长度的数据read(body,偏移量offset、读取长度len) 打开文件 使用ifstream ifs(文件名,权限 二进制 | 读权限)打开ifs调用is_open判断是否打开成功失败ELOG: %s 文件打开失败!,filename;返回false 为body申请len大小的空间(后续要将读取的数据 存到 body中)跳转文件读写位置 ifs使用seekg(偏移量,设置为文件起始位置开始) 读取指定长度的文件数据 ifs使用read读取(缓冲区首地址,长度)判断read是否成功调用good函数判断(成功返回true)ELOG:%s 文件读取数据失败 filenameifs关闭文件close,返回false 关闭文件 关闭文件,返回true read(string* body) 获取fsize文件大小(根据文件大小调整body空间)获取文件大小(使用内置rsize函数)返回:调用readFile函数(&body[0],起始位置开始,body大小)
文件写:
从指定offset偏移量位置读取len长度的数据writeFile(要写的内容body,偏移量offset,长度len); 打开文件 和上面方法一样,但使用fstream(因为ofstream不具备文件读操作权限,就无法跳转,所以使用读写类:fstream)fstream(文件名,二进制 | 读 | 写)判断是否成功…(同上) 跳转到文件指定位置写 使用seekg 写入数据 使用write(写入缓冲区body,长度)判断是否写入成功失败打印:ELOG %s 文件写入数据失败 filename关闭文件,返回false 关闭,返回true write(要写的内容body) 调用writeFile(body,起始位置开始,body大小)
修改文件名称:rename(nname)
调用库函数:int rename(const char *oldpath, const char *newpath);直接调用::rename(o,n)函数(前面加上 :: 代表使用全局的,而非局部的)成功返回0(直接判断返回值是否0)文件的创建:createFile
直接复用write中fstream打开文件方法(打开不成功,就会自动创建,并且还不需要读权限)关闭文件返回true删除文件:removeFile
使用库函数remove成功返回0失败返回-1创建目录:createDirectory(路径)
int mkdir(const char *pathname, mode_t mode);头文件:需要逐层创建,对传进来的目录不断的截断创建,因为要防止,前面的目录不存在:
例如:/aaa/bb/cc/ddd.hpp,需要从第一个父级目录开始创建
因此:从前完后不断的找到 “/” 进行截断,然后再创建目录
执行具体过程:aaa/、aaa/bbb/、aaa/bbb/cc/、
最终在后面找不到"/"了那么就创建文件:
createFIle(aaa/bbb/cc/ddd.hpp)
移除目录
使用system函数执行rm -rf指令(库函数平台一致性)2. 创建cmd变量存 删除文件指令调用system(cmd) ,返回是否成功(若返回值为-1则失败)
获取文件的父级目录:parentDirectory()(静态的函数)
/aa/bb/cc/d/test.txt(从后往前找到一个/,其前面就是父级目录)pos记录filename中从后往前寻找(find_last_of)/如果未找到,表示当前路径就是最终目录,返回./找到后:截取0 ~ pos位置的父级路径给到path,并返回文件工具源码:
FileHelper.hpp
class FileHelper{ public: FileHelper(const std::string& filename):_filename(filename){ } /** * 判断路径是否存在 */ bool exists(){ //使用stat函数 struct stat st; return (stat(_filename.c_str(),&st) == 0); } /** * 判断文件大小,直接通过获取的文件状态返回的对象st * 获取其内部成员 st_size 即是文件的大小 */ size_t sizeFile(){ struct stat st; stat(_filename.c_str(),&st); return st.st_size; } bool renameFile(const std::string& newfilename){ //使用函数rename return (std::rename(_filename.c_str(),newfilename.c_str()) == 0);//返回0表示修改成功 } static bool removeFile(const char* filename){ return ::remove(filename) == 0;//返回0表示删除成功 } static bool createFile(const char* filename){ std::fstream fs(filename, std::ios::binary | std::ios::out); //binary二进制打开防止数据出现问题, //out:写权限 return fs.is_open();//通过打开的文件判断是否打开成功 } /** * 文件的读取 * 将读取的文件中的所有内容放进buf中 * buf:输出型参数,存要读取的数据 */ bool readFile(std::string& buf,size_t offset,size_t len){ //1. 打开文件,调整buf空间 std::ifstream fs(_filename,std::ios::binary | std::ios::in);//二进制和读方式打开文件filename //调整buf空间 buf.resize(len); // 判断是否打开成功 if(!fs.is_open()){ ELOG("%s,文件打开失败!",_filename.c_str()); return false; } //2. 从当前位置读取所有内容放到buf中 //2.1 偏移到文件的指定位置 fs.seekg(offset,std::ios::beg);//从文件开始beg,偏移到offset //2.2 读取所有内容给到buf fs.read(&buf[0],len);// if(fs.good() == false){//判断上一步读取是否成功 ELOG("%s,文件读取数据失败",_filename.c_str()); return false; } fs.close(); return true; } bool readFile(std::string& buf){ //获取文件大小 size_t filesize = sizeFile(); //调整body空间:resize buf.resize(filesize); return readFile(buf,0,filesize); } /** * 文件的写入 * 将读取的文件中的所有内容放进buf中 * body:所要写的内容 * offset:偏移量 * len:写入长度 */ bool writeFile(const std::string& body,size_t offset,size_t len){ //1. 打开文件 std::ofstream fs(_filename.c_str(),std::ios::binary | std::ios::out | std::ios::in); // 判断是否打开成功 if(!fs.is_open()){ ELOG("%s,文件打开失败!",_filename.c_str()); return false; } //2. 跳到指定位置写: fs.seekp(offset,std::ios::beg); //3. 写 fs.write(body.c_str(),len); if(fs.good() == false){ ELOG("%s,文件写入数据失败",_filename.c_str()); return false; } //3. 关闭 fs.close(); return true; } bool writeFile(const std::string& body){ return writeFile(body,0,body.size()); } static bool createDirectory(const std::string& path){ //循环创建 size_t pos = -1,idx = 0; //pos查找/位置 pos = path.find_first_of("/"); while(pos != std::string::npos) { std::string subpath = path.substr(0,pos);//每次从 0 ~ pos(因为要保证层级结构) int ret = mkdir(subpath.c_str(),0775);//创建目录 ,并设置 权限为 775 if(ret != 0 && errno != EEXIST){ ELOG("创建目录 %s 失败",subpath.c_str()); return false; } idx = pos + 1;//idx记录未获取的目录的 / 分割的字符串起始位置 pos = path.find_first_of('/',idx); } //当出来时,就表示找不到 "/" //也就表示 前面的目录 即使不存在也会被创建好了 return mkdir(path.c_str(),0775) == 0;/// } static bool removeDirectory(const std::string& path){ //此处使用stytem来执行 rm -rf 指令进行删除操作,这样就能减代码量(就不在需要再去写删除多层目录的代码) std::string cmd = "rm -rf " + path; return system(cmd.c_str()) != -1; } static std::string parentDirectory(const std::string& path){ //获取父目录 //直接从后往前找到 / 前面就是父级目录 int pos = path.find_last_of("/"); if(pos == std::string::npos){ //表示当前路径就是最终目录,返回./ return "./"; } return path.substr(0,pos); } private: std::string _filename;};
项目操作(仿MQ):
再mqtest目录下进行测试:
创建mq_filetest.cpp文件进行测试文件
引用helper.hpp
测试:
主函数内:
实例化文件工具类 helper(文件名 …/mqcommon.logger.hpp)判断该文件是否存在,进行输出DLOG 是否存在 %d existsFile(直接调用类函数)DLOG 文件大小 %d sizeFile(直接调用类函数)实例化文件工具类 tmp_helper ./aaa/bbb/ccc/tmp.hpp(创建)判断文件是否存在(返回bool)若不存在, 首先获取父级目录parentDirectory,(将文件和目录的创建和删除函数修改为静态的,方便使用,其中参数(文件名)需要自己传入)判断目录是否存在(使用文件工具类匿名函数调用静态后函数exists判断):不存的话在进行创建目录createDirectory最后在创建文件createFile( …/tmp.hpp)(在创建目录中修改下,需要注意的是当文件已存在时的情况, 它也会返回-1且errno 会被设置为 EEXIST,若文件已存在不是失败情况,所以对于失败情况的判断需要重新写)在发次发现问题,当没有找到“/”的情况下,文件创建我们应该是从根目录开始,而不是直接截取最后的路径(这样会导致创建到当前文件中) 进行读写测试: DLOG(打印查看) 局部数据的读取和写入 读取文件(…/tmp.hpp)中的 offset位置的len长度写入 … rename删除文件、删除目录本章完。预知后事如何,暂听下回分解。
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量C++细致内容,早关注不迷路。