🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。
🌸专栏简介:本文收录于 C++项目——云备份
🌸相关专栏推荐:C语言初阶系列、C语言进阶系列 、C++系列、数据结构与算法、Linux
🌸项目Gitee链接:https://gitee.com/li-yuanjiu/cloud-backup
不管是客户端还是服务端,文件的传输备份都涉及到文件的读写,包括数据管理信息的持久化也是如此,因此首先设计封装文件操作类,这个类封装完毕之后,则在任意模块中对文件进行操作时都将变的简单化。
文件实用工具类FileUtil
主要包含以下成员:
class FileUtil
{
public:
FileUtil(const std::string &filename) :_filename(filename);
int64_t FileSize(); // 获取文件大小
time_t LastMtime(); // 获取文件最后一次修改时间
time_t LastATime(); // 获取文件最后一次访问时间
std::string FileName(); // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
bool GetPosLen(std::string *body, size_t pos, size_t len); // 获取文件指定区间的数据
bool GetContent(std::string *body); // 获取文件内容
bool SetContent(const std::string &body); // 向文件写入数据
bool Compress(const std::string &packname); // 压缩文件
bool UnCompress(const std::string &filename); // 解压缩文件
bool Exists(); // 判断文件是否存在
bool Remove(); // 删除文件
bool CreateDirectory(); // 创建文件目录
bool ScanDirectory(std::vector<std::string> *array); // 浏览指定目录下的所有文件
private:
std::string _filename; // 文件名--包含路径
};
_filename
:文件名称(包含路径),例如 a/b/c/test.cc
;FileUtil
:构造函数;FileSize
:获取文件大小;LastMtime
:获取文件最后一次修改时间;LastATime
:获取文件最后一次访问时间;FileName
:获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
;GetPosLen
:获取文件指定区间的数据;GetContent
:获取文件内容;SetContent
:向文件写入数据;Compress
:压缩文件;UnCompress
:解压缩文件;Exists
:判断文件是否存在;Remove
:删除文件;CreateDirectory
:创建文件目录;ScanDirectory
:浏览指定目录下的所有文件;在FileUtil
的实现中,我们使用了较多相对不常见库函数,在此先做复习。
认识stat
函数之前我们首先认识一下struct stat
类型。
在C语言中,struct stat
是一个用于表示文件或文件系统对象属性
的结构体类型。这个结构体通常用于与文件和目录
相关的操作,例如获取文件的大小、访问权限、最后修改时间等信息。struct stat
类型的定义通常由操作系统提供,因此其具体字段可能会因操作系统而异。
以下是一个典型的struct stat
结构体的字段,尽管具体字段可能会因操作系统而异:
struct stat {
dev_t st_dev; // 文件所在设备的ID
ino_t st_ino; // 文件的inode号
mode_t st_mode; // 文件的访问权限和类型
nlink_t st_nlink; // 文件的硬链接数量
uid_t st_uid; // 文件的所有者的用户ID
gid_t st_gid; // 文件的所有者的组ID
off_t st_size; // 文件的大小(以字节为单位)
time_t st_atime; // 文件的最后访问时间
time_t st_mtime; // 文件的最后修改时间
time_t st_ctime; // 文件的最后状态改变时间
blksize_t st_blksize; // 文件系统I/O操作的最佳块大小
blkcnt_t st_blocks; // 文件占用的块数
};
struct stat
结构体中的这些字段提供了关于文件或目录的各种信息。不同的操作系统可能会提供额外的字段,或者字段的意义可能会有所不同。
stat
函数用于获取与指定路径名相关联的文件或目录的属性
,并将这些属性填充到一个struct stat
结构体中。以下是stat
函数的函数原型:
int stat(const char *pathname, struct stat *statbuf);
pathname
是要获取属性的文件或目录的路径名;statbuf
是一个指向struct stat
结构体的指针,用于存储获取到的属性信息;stat
函数返回一个整数值,如果操作成功,返回0
;如果出现错误,返回-1
,并设置errno
全局变量以指示错误的类型。注意需要包含头文件
和
来使用stat
函数。
std::experimental::filesystem
库是 C++ 标准库的一部分,最早出现在 C++17
中,并被视为实验性的文件系统库
。它提供了一组类和函数,用于处理文件系统操作,如文件和目录的创建、访问、遍历、复制、删除
等。这个库的目的是使文件系统操作更加便捷,同时具有跨平台性,因此你可以在不同操作系统上执行相同的文件操作,而不需要考虑底层细节。
以下是一些 std::experimental::filesystem
库的主要特性和功能:
std::experimental::filesystem::path 类
:用于表示文件路径。它可以自动处理不同操作系统的路径分隔符,使得代码更具可移植性。
文件和目录操作
:这个库提供了许多函数来执行常见的文件和目录操作,包括文件创建、复制、移动、删除,以及目录的创建、删除、遍历等。
文件属性查询
:你可以使用这个库来查询文件和目录的属性,如文件大小、修改时间等。
异常处理
:std::experimental::filesystem
库定义了一些异常类,以处理与文件系统操作相关的错误,如文件不存在或无法访问等问题。
迭代器
:你可以使用迭代器来遍历目录中的文件和子目录,这是一个非常方便的功能,用于递归遍历文件系统。
需要注意的是,尽管 std::experimental::filesystem
在 C++17 中引入,但它是一个实验性的特性,并且不一定在所有编译器和平台上都得到完全支持。因此,一些编译器可能需要特定的编译选项或配置才能使用这个库。
从 C++17 开始,文件系统库已正式成为 C++ 标准的一部分,并迁移到 std::filesystem
命名空间中,而不再是实验性的特性。因此,在新的 C++标准中,建议使用 std::filesystem
库来执行文件系统操作。
namespace fs = std::experimental::filesystem;
class FileUtil
{
public:
FileUtil(const std::string &filename) :_filename(filename)
{}
int64_t FileSize()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get file size failed!" << std::endl;
return -1;
}
return st.st_size;
}
time_t LastMtime()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get last modify time failed!" << std::endl;
return -1;
}
return st.st_mtime;
}
time_t LastATime()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get last access time failed!" << std::endl;
return -1;
}
return st.st_atime;
}
std::string FileName()
{
size_t pos = _filename.find_last_of("/");
if(pos == std::string::npos)
{
return _filename;
}
return _filename.substr(pos+1);
}
bool GetPosLen(std::string *body, size_t pos, size_t len)
{
size_t fsize = FileSize();
if(pos + len > fsize)
{
std::cout << "GetPosLen: get file len error" << std::endl;
return false;
}
std::ifstream ifs;
ifs.open(_filename, std::ios::binary);
if(ifs.is_open() == false)
{
std::cout << "GetPosLen: open file failed!" << std::endl;
return false;
}
ifs.seekg(pos, std::ios::beg); // 将文件指针定位到pos处
body->resize(len);
ifs.read(&(*body)[0], len);
if(ifs.good() == false)
{
std::cout << "GetPosLen: get file content failed" << std::endl;
ifs.close();
return false;
}
ifs.close();
return true;
}
bool GetContent(std::string *body)
{
size_t fsize = FileSize();
return GetPosLen(body, 0, fsize);
}
bool SetContent(const std::string &body)
{
std::ofstream ofs;
ofs.open(_filename, std::ios::binary);
if(ofs.is_open() == false)
{
std::cout << "SetContent: write open file failed" << std::endl;
return false;
}
ofs.write(&body[0], body.size());
if(ofs.good() == false)
{
std::cout << "SetContent: write open file failed" << std::endl;
ofs.close();
return false;
}
ofs.close();
return true;
}
bool Compress(const std::string &packname)
{
// 1.获取源文件数据
std::string body;
if(GetContent(&body) == false)
{
std::cout << "compress get file content failed" << std::endl;
return false;
}
// 2.对数据进行压缩
std::string packed = bundle::pack(bundle::LZIP, body);
// 3.将压缩的数据存储到压缩包文件中
FileUtil fu(packname);
if(fu.SetContent(packed) == false)
{
std::cout << "compress write packed data failed!" << std::endl;
return false;
}
return true;
}
bool UnCompress(const std::string &filename)
{
// 1.将当前压缩包数据读取出来
std::string body;
if(GetContent(&body) == false)
{
std::cout << "Uncompress get file content failed!" << std::endl;
return false;
}
// 2.对压缩的数据进行解压缩
std::string unpacked = bundle::unpack(body);
// 3.将解压缩的数据写入到新文件中
FileUtil fu(filename);
if(fu.SetContent(unpacked) == false)
{
std::cout << "Uncompress write packed data failed!" << std::endl;
return false;
}
return true;
}
bool Exists()
{
return fs::exists(_filename);
}
bool Remove()
{
if(Exists() == false)
{
return true;
}
remove(_filename.c_str());
return true;
}
bool CreateDirectory()
{
if(Exists()) return true;
return fs::create_directories(_filename);
}
bool ScanDirectory(std::vector<std::string> *array)
{
for(auto& p : fs::directory_iterator(_filename)) // 迭代器遍历指定目录下的文件
{
if(fs::is_directory(p) == true) continue;
// relative_path 带有路径的文件名
array->push_back(fs::path(p).relative_path().string());
}
return true;
}
private:
std::string _filename;
};
Jsoncpp
已经为我们你提供了序列化与反序列化接口,但是为了使得实用更加便捷,我们可以自己再封装一个JsonUtil
的类。
JsonUtil类中包含以下成员:
class JsonUtil
{
public:
static bool Serialize(const Json::Value &root, std::string *str); // 序列化操作
static bool Unserialize(const std::string &str, Json::Value *root); // 反序列化操作
};
由于前面的章节已经介绍过Json的使用,接下来我们直接看函数的实现。
class JsonUtil
{
public:
static bool Serialize(const Json::Value &root, std::string *str)
{
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss;
if(sw->write(root, &ss) != 0)
{
std::cout << "json write failed!" << std::endl;
return false;
}
*str = ss.str();
return true;
}
static bool Unserialize(const std::string &str, Json::Value *root)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);
if(ret == false)
{
std::cout << "parse error" << std::endl;
return false;
}
return true;
}
};
最后,我们将两个实用工具类都放到util.hpp
的头文件中。
// util.hpp
#ifndef __MY_UTIL__
#define __MY_UTIL__
/*
1.获取文件大小
2.获取文件最后一次修改时间
3.获取文件最后一次访问时间
4.获取文件路径名中的文件名称 /abc/test.txt -> test.txt
5.向文件写入数据
6.获取文件数据
7.获取文件指定位置 指定数据长度
8.判断文件是否存在
9.创建目录
10.浏览获取目录下的所有文件路径名
11.压缩文件
12.解压缩所有文件
13.删除文件
*/
#include
#include
#include
#include
#include
#include
#include
#include "bundle.h"
namespace cloud
{
namespace fs = std::experimental::filesystem;
class FileUtil
{
public:
FileUtil(const std::string &filename) :_filename(filename)
{}
int64_t FileSize()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get file size failed!" << std::endl;
return -1;
}
return st.st_size;
}
time_t LastMtime()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get last modify time failed!" << std::endl;
return -1;
}
return st.st_mtime;
}
time_t LastATime()
{
struct stat st;
if(stat(_filename.c_str(), &st) < 0)
{
std::cout << "get last access time failed!" << std::endl;
return -1;
}
return st.st_atime;
}
std::string FileName()
{
size_t pos = _filename.find_last_of("/");
if(pos == std::string::npos)
{
return _filename;
}
return _filename.substr(pos+1);
}
bool GetPosLen(std::string *body, size_t pos, size_t len)
{
size_t fsize = FileSize();
if(pos + len > fsize)
{
std::cout << "GetPosLen: get file len error" << std::endl;
return false;
}
std::ifstream ifs;
ifs.open(_filename, std::ios::binary);
if(ifs.is_open() == false)
{
std::cout << "GetPosLen: open file failed!" << std::endl;
return false;
}
ifs.seekg(pos, std::ios::beg); // 将文件指针定位到pos处
body->resize(len);
ifs.read(&(*body)[0], len);
if(ifs.good() == false)
{
std::cout << "GetPosLen: get file content failed" << std::endl;
ifs.close();
return false;
}
ifs.close();
return true;
}
bool GetContent(std::string *body)
{
size_t fsize = FileSize();
return GetPosLen(body, 0, fsize);
}
bool SetContent(const std::string &body)
{
std::ofstream ofs;
ofs.open(_filename, std::ios::binary);
if(ofs.is_open() == false)
{
std::cout << "SetContent: write open file failed" << std::endl;
return false;
}
ofs.write(&body[0], body.size());
if(ofs.good() == false)
{
std::cout << "SetContent: write open file failed" << std::endl;
ofs.close();
return false;
}
ofs.close();
return true;
}
bool Compress(const std::string &packname)
{
// 1.获取源文件数据
std::string body;
if(GetContent(&body) == false)
{
std::cout << "compress get file content failed" << std::endl;
return false;
}
// 2.对数据进行压缩
std::string packed = bundle::pack(bundle::LZIP, body);
// 3.将压缩的数据存储到压缩包文件中
FileUtil fu(packname);
if(fu.SetContent(packed) == false)
{
std::cout << "compress write packed data failed!" << std::endl;
return false;
}
return true;
}
bool UnCompress(const std::string &filename)
{
// 1.将当前压缩包数据读取出来
std::string body;
if(GetContent(&body) == false)
{
std::cout << "Uncompress get file content failed!" << std::endl;
return false;
}
// 2.对压缩的数据进行解压缩
std::string unpacked = bundle::unpack(body);
// 3.将解压缩的数据写入到新文件中
FileUtil fu(filename);
if(fu.SetContent(unpacked) == false)
{
std::cout << "Uncompress write packed data failed!" << std::endl;
return false;
}
return true;
}
bool Exists()
{
return fs::exists(_filename);
}
bool Remove()
{
if(Exists() == false)
{
return true;
}
remove(_filename.c_str());
return true;
}
bool CreateDirectory()
{
if(Exists()) return true;
return fs::create_directories(_filename);
}
bool ScanDirectory(std::vector<std::string> *array)
{
for(auto& p : fs::directory_iterator(_filename)) // 迭代器遍历指定目录下的文件
{
if(fs::is_directory(p) == true) continue;
// relative_path 带有路径的文件名
array->push_back(fs::path(p).relative_path().string());
}
return true;
}
private:
std::string _filename;
};
class JsonUtil
{
public:
static bool Serialize(const Json::Value &root, std::string *str)
{
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss;
if(sw->write(root, &ss) != 0)
{
std::cout << "json write failed!" << std::endl;
return false;
}
*str = ss.str();
return true;
}
static bool Unserialize(const std::string &str, Json::Value *root)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);
if(ret == false)
{
std::cout << "parse error" << std::endl;
return false;
}
return true;
}
};
}
#endif