1.文件实用工具类设计
不管是客户端还是服务端,文件的传输备份都涉及到文件的读写,包括数据管理信息的持久化也是如此,因此首先设 计封装文件操作类,这个类封装完毕之后,则在任意模块中对文件进行操作时都将变的简单化。
文件实用工具类FileUtil
主要包含以下成员:
cpp
namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
class FileUtil
{
public:
FileUtil(const std::string& filename) :_filename(filename);
//主要针对普通文件的接口
int64_t FileSize(); // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
time_t LastModtime(); // 获取文件最后一次修改时间
time_t LastAccTime(); // 获取文件最后一次访问时间
std::string FileName(); // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
bool SetContent(const std::string& body); // 向文件写入数据
bool GetContent(std::string* body); // 获取文件内容
bool GetPosLen(std::string* body, size_t pos, size_t len); // 获取文件指定区间的数据
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:获取文件大小;
- LastModtime:获取文件最后一次修改时间;
- LastAccTime:获取文件最后一次访问 时间,这个是为了我们后面的热点文件管理,如果一个文件很久都没有被访问过,我们就要对它进行压缩处理
- FileName:获取文件路径中的纯文件名称,比如说我们传入了路径名 a/b/c/test.cc ,使用Filename只获取最后的文件名test.cc;
- GetPosLen:获取文件指定区间的数据;这个主要是为了我们后面的断点续传功能。
- GetContent:获取文件内容;这里函数参数使用指针的原因是它是一个输入输出型参数,文件的内容都会被传入到这个指针指向的那块地方。当然你使用引用也是可以的。
- SetContent:向文件写入数据;这里函数参数使用引用的原因是它是一个输入型参数,使用引用能提高效率
- Compress:压缩文件;
- UnCompress:解压缩文件;
- Exists:判断文件是否存在;
- Remove:删除文件;
- CreateDirectory:创建文件目录;
- GetDirectory:浏览指定目录下的所有文件;我们说Linux下一切皆是文件,目录也是。
我们这里不单单对普通文件进行处理,我们对目录文件也设计了接口。
首先我们打开我们的云服务器看看

这些都是我们之前实验下来的那些文件啊,我们现在把不必要的文件进行删除

我们创建一个util.hpp,把我们上面的东西给搞进去。

2.文件实用工具类实现
2.1.获取文件属性操作的实现
- FileSize()函数的实现
cpp
int64_t FileSize(); // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
这个需要我们了解一下不太常用的接口函数------stat
cpp
man 2 STAT

stat函数用于获取与指定路径名相关联的文件或目录的属性,并将这些属性填充到一个struct stat结构体中。以下是stat函数的函数原型:
cpp
int stat(const char *pathname, struct stat *statbuf);
- pathname是要获取属性的文件或目录的路径名;
- statbuf是一个指向struct stat结构体的指针,用于存储获取到的属性信息;
stat函数返回一个整数值,如果操作成功,返回0;
如果出现错误,返回-1,并设置errno全局变量以指示错误的类型。
- struct stat类型
我们来看看能获取到什么文件信息!
在C语言中,struct stat是一个用于表示文件或文件系统对象属性的结构体类型。
这个结构体通常用于与文件和目录相关的操作,例如获取文件的大小、访问权限、最后修改时间等信息。
struct stat类型的定义通常由操作系统提供,因此其具体字段可能会因操作系统而异。
以下是一个典型的struct stat结构体的字段,尽管具体字段可能会因操作系统而异:
cpp
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; // 文件占用的块数
};
我们也可以在上面的界面往下滑!看看我的系统的真实情况是啥

现在我们就应该知道怎么设计这个FileSize函数了吧!因为我们这有sz_size函数

cpp
int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get FileSize Failed!!" <<std::endl;
return -1;
}
return st.st_size;
}
- LastModtime()和LastAcctime()的实现
其实这个和上面的FileSize()是差不多的,只不过......算了先开下面这个

struct timespec
是 C 语言中用于表示高精度时间 的结构体,尤其在 Linux/Unix 系统中广泛使用。它设计用来存储时间的秒(tv_sec
)和纳秒(tv_nsec
)分量,提供比传统的 time_t
(仅秒级)更高的时间精度。
cpp
#include <time.h> // 需要包含的头文件
struct timespec {
time_t tv_sec; // 秒(自 1970-01-01 00:00:00 UTC 的秒数)
long tv_nsec; // 纳秒(0 到 999,999,999)
};
来看看怎么使用
测试函数------获取当前时间(纳秒级)
cpp
#include <stdio.h>
#include <time.h>
int main() {
struct timespec ts;
// 获取系统实时时钟(CLOCK_REALTIME)
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
perror("clock_gettime");
return 1;
}
printf("Current time: %lld seconds, %ld nanoseconds\n",
(long long)ts.tv_sec, ts.tv_nsec);
return 0;
}

现在我们就应该知道怎么使用了
cpp
time_t LastModtime() // 获取文件最后一次修改时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastModtime Failed!!" <<std::endl;
return -1;
}
return st.st_mtim.tv_sec;
}
time_t LastAcctime() // 获取文件最后一次访问时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastAccTime Failed!!" <<std::endl;
return -1;
}
return st.st_atim.tv_sec;
}
- Filename()的实现
我们知道我们的路径名都是用/来进行分割的,比如./a/b.txt,那么我们只需要获取最后一个/后面到最末尾的东西即可
cpp
std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
{
size_t pos = _filename.find_last_of("/");//寻找最后一个/
if (pos == std::string::npos)//没找到,说明没有/
{
return _filename;
}
return _filename.substr(pos + 1);//从pos+1位置截取到末尾
}
- 小测试
我们目前写的代码就是现在这样子
util.hpp
cpp
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
class FileUtil
{
public:
FileUtil(const std::string& filename):_filename(filename){}
//主要针对普通文件的接口
int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
{
struct stat st;
int re=stat(_filename.c_str(),&st);
if(re<0)//stat函数获取文件属性失败了
{
std::cout<<"Get FileSize Failed!!"<<std::endl;
return -1;
}
return st.st_size;
}
time_t LastModtime() // 获取文件最后一次修改时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastModtime Failed!!" << std::endl;
return -1;
}
return st.st_mtim.tv_sec;
}
time_t LastAcctime() // 获取文件最后一次访问时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastAcctime Failed!!" << std::endl;
return -1;
}
return st.st_atim.tv_sec;
}
std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
{
size_t pos=_filename.find_last_of("/");//寻找最后一个/
if(pos==std::string::npos)//没找到,说明没有/
{
return _filename;
}
return _filename.substr(pos+1);//从pos截取到末尾
}
private:
std::string _filename; // 文件名--包含路径
};
};
代码写到这里我们,我们必须进行测试我们的代码对不对,要不然写到后面我们一直报错就不好了
cpp
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
cloud::FileUtil fu(filename);
std::cout<<fu.FileSize()<<std::endl;
std::cout<<fu.LastModtime()<<std::endl;
std::cout<<fu.LastAcctime()<<std::endl;
std::cout<<fu.FileName()<<std::endl;
}
int main(int argc,char*argv[])
{
FileUtilTest(argv[1]);
}
makefile
cpp
cloud:cloud.cc util.hpp
g++ $^ -o $@
.PHONY:clean
clean:
rm -f cloud
我们编译运行一下
这个就很好了!!
我们去看看

我们只是在main函数里面添加了一句retrun 0,就发现它们都修改了

我们再看Filename()的测试,也是很不错!!

这就说明我们完成的代码就很好了
接下来我要讲一下git,如果只想看项目的可以往下一节去了
首先我们先创建本地仓库


然后配置本地仓库

接着提交,发现git add *报错了
我们有两种解决方法,但是我只选择下面这种
直接包含子目录代码(不保留 Git 历史)
如果这些目录不需要独立维护(例如你只是复制了代码,不需要跟踪它们的更新),可以删除它们的 .git
文件夹,再添加到父仓库。
删除子目录中的 .git
文件夹:
cpp
rm -rf bundle/.git cpp-httplib/.git
重新添加所有文件,提交更改

接下来要把它推送到远程仓库里面去,首先我们要先创建一个远程仓库


有了本地仓库和远程仓库后,可以将二者关联起来,以便推送和拉取代码:
- 在本地仓库中,执行以下命令来添加远程仓库的地址:
cppgit remote add origin <远程Git仓库地址>
其中,<远程Git仓库地址>是你的远程Git仓库的网址。
对于如何获取远程Git仓库地址,我们举例说明:
比如,你的远程Git仓库地址为:
bashhttps://github.com/your/your.git
那么你在本地使用"git remote add origin"指令的语法就应该是:
bashgit remote add origin https://github.com/your/your.git
执行这条指令之后,你的本地项目就与远程Git仓库建立了连接,你就可以开始对你的代码进行版本追踪和协作开发了。
- 检查关联是否成功,执行以下命令:
cpp
git remote -v
- 推送到远程仓库
关联完成后,可以将本地仓库中的代码推送到远程仓库中:
cpp
git push -f origin master
注意:-f是强制的意思
这样就将本地仓库中的代码推送到了远程仓库的 master 分支上。如果是第一次推送,可能需要输入用户名和密码进行身份认证。



这很好了
git remote add origin的一些常用操作
- 更改默认的远程仓库
在项目中可能存在多个远程仓库,如果你想更改默认仓库,可以使用如下指令:
cppgit remote set-url origin <新的远程Git仓库地址>
- 查看当前的远程仓库
如果你想查看当前项目的远程仓库,可以使用如下指令:
cppgit remote -v
- 删除远程仓库
如果你需要删除已经添加的远程仓库,可以使用如下指令:
cppgit remote rm origin
执行这条指令之后,Git就会将已经添加的名为"origin"的仓库删除。
2.2.文件的读写操作的实现
GetPosLen()函数
cpp
bool GetPosLen(std::string* body, size_t pos, size_t len)
{
std::ifstream ifs;
ifs.open(_filename, std::ios::binary);//打开文件,以二进制方式来读取数据
if (ifs.is_open() == false)//打开失败
{
std::cout << "GetPosLen: open file failed!" << std::endl;
return false;
}
size_t fsize = this->FileSize();//获取文件大小
if (pos + len > fsize)//超过文件大小了
{
std::cout << "GetPosLen: get file len error" << std::endl;
return false;
}
ifs.seekg(pos, std::ios::beg); // 将文件指针定位到pos处
body->resize(len);//把存储读取的数据的载体的大小修改到够大的
ifs.read(&(*body)[0], len);//读取数据,注意这里body是指针,需要先解引用
if (ifs.good() == false)//上次读取出错了
{
std::cout << "GetPosLen: get file content failed" << std::endl;
ifs.close();
return false;
}
ifs.close();
return true;
}
GetContent()
cpp
bool GetContent(std::string* body)
{
size_t fsize = FileSize();
return GetPosLen(body, 0, fsize);
}
SetContent()
cpp
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;
}
接下来我们来测试一下
cpp
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
cloud::FileUtil fu(filename);
std::string body;
fu.GetContent(&body);
cloud::FileUtil nfu("./hello.txt");
nfu.SetContent(body);
}
int main(int argc,char*argv[])
{
FileUtilTest(argv[1]);
return 0;

我们进去看看

简直一模一样。但是看着一样是不一定一样的,我们需要借助工具来看看是不是一样的

很显然是一样的了。
同样的,在这里,我们还是需要使用git来进行备份一下


2.3.文件压缩和解压缩操作
接下来来实现Compress函数和Uncompress函数。这是非常简单的。
cpp
bool Compress(const std::string& packname)
{
// 1.获取源文件数据
std::string body;
if (this->GetContent(&body) == false)//源文件数据都存储在body里面
{
//获取源文件数据失败
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;
}
接下来看解压缩的操作
cpp
bool UnCompress(const std::string& filename)
{
// 1.将当前压缩包数据读取出来
std::string body;
if (this->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;
}
由于我们bundle库是第三方库,所以不要忘记了添加头文件

除此之外还是不够的,我们还需要将bundle.h和bundle.cpp拷贝到当前目录下来

好,现在我们来测试一下
makefile
cpp
cloud:cloud.cc util.hpp bundle.cpp
g++ $^ -o $@ -lpthread
.PHONY:clean
clean:
rm -f cloud
cpp
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
cloud::FileUtil fu(filename);
fu.Compress(filename+".lz");
cloud::FileUtil pfu(filename+".lz");
pfu.UnCompress("hello.txt");
}
int main(int argc,char*argv[])
{
FileUtilTest(argv[1]);
return 0;
}
我们编译运行一下

这些警告不管。

2.4.目录文件操作实现
我们先来认识一个接口------scandir,c语言里面浏览一个目录的内容

看到三级指针就蒙蔽了!所以这个接口用起来是不太好用的,这个时候就需要借助C++17所支持的filesystem了。
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 库来执行文件系统操作。
大家想要了解具体内容请去:文件系统库 - cppreference.com

- ScanDirectory()函数的实现
我们点开这个

我们看看例子

可能大家看不太懂,我加注释给你们看看
cpp
// 使用实验性文件系统库(C++17 前需用 experimental 命名空间,C++17 后改为 std::filesystem)
#include <experimental/filesystem>
#include <fstream> // 文件流操作(如创建文件)
#include <iostream> // 输入输出流
// 为实验性文件系统库定义别名 fs,简化代码
namespace fs = std::experimental::filesystem;
int main() {
// 1. 创建嵌套目录 "sandbox/a/b"
// create_directories 会递归创建所有不存在的父目录
fs::create_directories("sandbox/a/b");
// 2. 在 sandbox 目录下创建两个空文件
// 使用 std::ofstream 的构造函数直接创建文件(文件内容为空)
std::ofstream{"sandbox/file1.txt"}; // 创建 file1.txt
std::ofstream{"sandbox/file2.txt"}; // 创建 file2.txt
// 3. 遍历 sandbox 目录下的所有条目并打印路径
// directory_iterator 用于遍历目录内容
// entry 是目录条目,包含文件/子目录的信息
std::cout << "目录内容:\n";
for (const fs::directory_entry& entry : fs::directory_iterator{"sandbox"}) {
// 直接输出 entry 会显示其完整路径(需支持 operator<< 重载)
std::cout << " " << entry << '\n';
}
// 4. 递归删除整个 sandbox 目录及其内容
// remove_all 会删除目录、子目录和所有文件
fs::remove_all("sandbox");
return 0;
}
这样子就很简单易懂了。
我们完全可以将其复制下来,就能知道怎么使用这个代码了。
cpp
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;
}
注意:迭代器返回的p不是string,不能直接将p添加进array里面,我们需要使用path将其转换成string
我们进去看看怎么使用

我们发现它打印的都是带有\的文件名,这就是我们要找的。
此外注意relative_path函数返回的是一个path对象

而我们是要string的,所以我们还是需要借助path的接口string(),这个自己去官网看
- Exists()的实现
我们去刚刚那个网站,就很容易看到下面这个

点进去看就明白了

也就是下面这个
cpp
bool exists(const path& p);
检查给定的路径(path
)是否对应一个实际存在的文件或目录。
返回值:
- 若路径
p
对应的文件或目录存在,返回true
;否则返回false
。
现在我们很容易就写出下面这个
cpp
namespace fs = std::experimental::filesystem;
bool Exists()
{
return fs::exists(_filename);
}
- Remove()的实现

点进去看看
我们借助DeepSeek帮我们解析这个函数的用法
-
remove
:删除单个文件或空目录(类似 POSIX 的remove
)。-
符号链接处理:删除符号链接本身,而非其指向的目标。
-
限制:若路径是目录,必须为空才能删除,否则失败。
-
cpp
bool remove(const path& p);
p
-- 要删除的文件或空目录的路径。
-
返回值:
-
成功删除或文件不存在时返回
true
。 -
若路径存在但删除失败(如目录非空或权限不足),返回
false
。
-
cpp
bool Remove()
{
if(Exists() == false)
{
return true;
}
remove(_filename.c_str());
return true;
}
- CreateDirectory()的实现
还是熟悉的配方,我不过多说,大家不懂的可以去这里看:Filesystem library - cppreference.com
cpp
bool CreateDirectory()
{
if (Exists()) return true;//如果存在,则直接返回true即可
return fs::create_directories(_filename);//不存在的话,创建一个文件
}
注意要包含头文件#include <experimental/filesystem>
接下来来测试一下
Util.hpp
cpp
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include"bundle.h"
#include <experimental/filesystem>
namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
namespace fs = std::experimental::filesystem;
class FileUtil
{
public:
FileUtil(const std::string& filename):_filename(filename){}
//主要针对普通文件的接口
int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
{
struct stat st;
int re=stat(_filename.c_str(),&st);
if(re<0)//stat函数获取文件属性失败了
{
std::cout<<"Get FileSize Failed!!"<<std::endl;
return -1;
}
return st.st_size;
}
time_t LastModtime() // 获取文件最后一次修改时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastModtime Failed!!" << std::endl;
return -1;
}
return st.st_mtim.tv_sec;
}
time_t LastAcctime() // 获取文件最后一次访问时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastAcctime Failed!!" << std::endl;
return -1;
}
return st.st_atim.tv_sec;
}
std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
{
size_t pos=_filename.find_last_of("/");//寻找最后一个/
if(pos==std::string::npos)//没找到,说明没有/
{
return _filename;
}
return _filename.substr(pos+1);//从pos截取到末尾
}
bool GetPosLen(std::string* body, size_t pos, size_t len)
{
std::ifstream ifs;
ifs.open(_filename, std::ios::binary);//打开文件,以二进制方式来读取数据
if (ifs.is_open() == false)//打开失败
{
std::cout << "GetPosLen: open file failed!" << std::endl;
return false;
}
size_t fsize = this->FileSize();//获取文件大小
if (pos + len > fsize)//超过文件大小了
{
std::cout << "GetPosLen: get file len error" << 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 (this->GetContent(&body) == false)//源文件数据都存储在body里面
{
//获取源文件数据失败
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 (this->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());//添加文件
//注意迭代器返回的p不是string,不能直接将p添加进array里面,我们需要使用path将其
}
return true;
}
private:
std::string _filename; // 文件名--包含路径
};
};
cpp
#include"util.hpp"
void FileUtilTest(const std::string &filename)
{
cloud::FileUtil fu(filename);
fu.CreateDirectory();
std::vector<std::string>arry;
fu.ScanDirectory(&arry);
for(auto&a:arry)
{
std::cout<<a<<std::endl;
}
}
int main(int argc,char*argv[])
{
FileUtilTest(argv[1]);
return 0;
}
注意我们这个是使用了c++17里面的文件系统,这是需要我们额外链接的!
makefile
cpp
cloud:cloud.cc util.hpp bundle.cpp
g++ $^ -o $@ -lpthread -lstdc++fs
.PHONY:clean
clean:
rm -f cloud
编译运行

发现它创建了一个目录

这个文件管理写的很好了吧!!
还是老样子!git push一下

3.JSON实用工具类实现
Jsoncpp
已经为我们你提供了序列化与反序列化接口,但是为了使得实用更加便捷,我们可以自己再封装一个JsonUtil
的类。
JsonUtil类中包含以下成员
cpp
class JsonUtil
{
public:
//这里使用static,是为了方便我们直接调用即可
static bool Serialize(const Json::Value &root, std::string *str); // 序列化操作
static bool Unserialize(const std::string &str, Json::Value *root); // 反序列化操作
};
由于前面的章节已经介绍过Json的使用,接下来我们直接看函数的实现。
cpp
class JsonUtil {
public:
/**
* @brief 将 Json::Value 对象序列化为字符串
* @param root 输入的 JSON 数据结构(待序列化)
* @param str 输出的序列化后的字符串
* @return true 序列化成功,false 序列化失败
*/
static bool Serialize(const Json::Value &root, std::string *str) {
// 1. 创建 JSON 流写入器构建器(可配置格式化选项,如缩进)
Json::StreamWriterBuilder swb;
// 2. 通过构建器生成 StreamWriter 对象(unique_ptr 自动管理内存)
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss; // 用于存储序列化结果
// 3. 将 JSON 数据写入流
// 返回值 0 表示成功,非 0 表示失败(JsonCpp 的约定)
if (sw->write(root, &ss) != 0) {
std::cout << "JSON 序列化失败!" << std::endl;
return false;
}
// 4. 将 stringstream 内容转为字符串
*str = ss.str();
return true;
}
/**
* @brief 将字符串反序列化为 Json::Value 对象
* @param str 输入的 JSON 格式字符串
* @param root 输出的解析后的 JSON 数据结构
* @return true 解析成功,false 解析失败
*/
static bool Unserialize(const std::string &str, Json::Value *root) {
// 1. 创建 JSON 字符读取器构建器
Json::CharReaderBuilder crb;
// 2. 通过构建器生成 CharReader 对象(unique_ptr 自动管理内存)
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err; // 存储解析错误信息
// 3. 解析字符串
// 参数说明:
// - str.c_str():字符串起始地址
// - str.c_str() + str.size():字符串结束地址
// - root:输出解析后的 JSON 对象
// - &err:错误信息输出
bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);
if (!ret) {
std::cout << "JSON 解析错误: " << err << std::endl;
return false;
}
return true;
}
};
接下来来测试一下
makefile
cpp
cloud:cloud.cc util.hpp bundle.cpp
g++ $^ -o $@ -lpthread -lstdc++fs -ljsoncpp
.PHONY:clean
clean:
rm -f cloud
cpp
#include"util.hpp"
void JsonUtilTest()
{
// 定义并初始化一个常量字符指针,指向字符串"小明"
// 定义一个整型变量并初始化为18,表示年龄
int age = 18;
// 定义一个浮点型数组,存储三门课程的成绩
float score[] = {77.5, 88, 93.6};
// 定义一个Json::Value对象,作为JSON数据的根节点
Json::Value root;
// 向root中添加一个键值对,键为"name",值为name所指向的字符串
root["name"] ="xiaoming";
// 向root中添加一个键值对,键为"age",值为整型变量age
root["age"] = age;
// 向root中添加一个键为"成绩"的数组,并依次添加score数组中的元素
// 使用append函数向数组中插入数据
root["chengji"].append(score[0]);
root["chengji"].append(score[1]);
root["chengji"].append(score[2]);
std::string json_str;
cloud::JsonUtil::Serialize(root,&json_str);
std::cout<<json_str<<std::endl;
Json::Value val;
cloud::JsonUtil::Unserialize(json_str,&val);
std::cout<<val["name"].asString()<<std::endl;
std::cout<<val["age"].asInt()<<std::endl;
for(int i=0;i<val["chengji"].size();i++)
{
std::cout<<val["chengji"][i].asFloat()<<std::endl;
}
}
int main(int argc,char*argv[])
{
JsonUtilTest();
return 0;
}
我们编译运行一下

很完美
好了,我们git push一下即可


4.util.hpp源代码
cpp
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include"bundle.h"
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>
namespace cloud {//注意:我们下面这些接口的名称可能会与系统的那些接口的名字重复,所以最好创建名称空间来避免名称污染
namespace fs = std::experimental::filesystem;
class FileUtil
{
public:
FileUtil(const std::string& filename):_filename(filename){}
//主要针对普通文件的接口
int64_t FileSize() // 获取文件大小,这里使用64位的有符号的int,防止文件过大导致文件大小显示异常
{
struct stat st;
int re=stat(_filename.c_str(),&st);
if(re<0)//stat函数获取文件属性失败了
{
std::cout<<"Get FileSize Failed!!"<<std::endl;
return -1;
}
return st.st_size;
}
time_t LastModtime() // 获取文件最后一次修改时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastModtime Failed!!" << std::endl;
return -1;
}
return st.st_mtim.tv_sec;
}
time_t LastAcctime() // 获取文件最后一次访问时间
{
struct stat st;
int re = stat(_filename.c_str(), &st);
if (re < 0)//stat函数获取文件属性失败了
{
std::cout << "Get LastAcctime Failed!!" << std::endl;
return -1;
}
return st.st_atim.tv_sec;
}
std::string FileName() // 获取文件路径中的纯文件名称 a/b/c/test.cc --> test.cc
{
size_t pos=_filename.find_last_of("/");//寻找最后一个/
if(pos==std::string::npos)//没找到,说明没有/
{
return _filename;
}
return _filename.substr(pos+1);//从pos截取到末尾
}
bool GetPosLen(std::string* body, size_t pos, size_t len)
{
std::ifstream ifs;
ifs.open(_filename, std::ios::binary);//打开文件,以二进制方式来读取数据
if (ifs.is_open() == false)//打开失败
{
std::cout << "GetPosLen: open file failed!" << std::endl;
return false;
}
size_t fsize = this->FileSize();//获取文件大小
if (pos + len > fsize)//超过文件大小了
{
std::cout << "GetPosLen: get file len error" << 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 (this->GetContent(&body) == false)//源文件数据都存储在body里面
{
//获取源文件数据失败
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 (this->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());//添加文件
//注意迭代器返回的p不是string,不能直接将p添加进array里面,我们需要使用path将其
}
return true;
}
private:
std::string _filename; // 文件名--包含路径
};
class JsonUtil {
public:
/**
* @brief 将 Json::Value 对象序列化为字符串
* @param root 输入的 JSON 数据结构(待序列化)
* @param str 输出的序列化后的字符串
* @return true 序列化成功,false 序列化失败
*/
static bool Serialize(const Json::Value &root, std::string *str) {
// 1. 创建 JSON 流写入器构建器(可配置格式化选项,如缩进)
Json::StreamWriterBuilder swb;
// 2. 通过构建器生成 StreamWriter 对象(unique_ptr 自动管理内存)
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss; // 用于存储序列化结果
// 3. 将 JSON 数据写入流
// 返回值 0 表示成功,非 0 表示失败(JsonCpp 的约定)
if (sw->write(root, &ss) != 0) {
std::cout << "JSON 序列化失败!" << std::endl;
return false;
}
// 4. 将 stringstream 内容转为字符串
*str = ss.str();
return true;
}
/**
* @brief 将字符串反序列化为 Json::Value 对象
* @param str 输入的 JSON 格式字符串
* @param root 输出的解析后的 JSON 数据结构
* @return true 解析成功,false 解析失败
*/
static bool Unserialize(const std::string &str, Json::Value *root) {
// 1. 创建 JSON 字符读取器构建器
Json::CharReaderBuilder crb;
// 2. 通过构建器生成 CharReader 对象(unique_ptr 自动管理内存)
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err; // 存储解析错误信息
// 3. 解析字符串
// 参数说明:
// - str.c_str():字符串起始地址
// - str.c_str() + str.size():字符串结束地址
// - root:输出解析后的 JSON 对象
// - &err:错误信息输出
bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);
if (!ret) {
std::cout << "JSON 解析错误: " << err << std::endl;
return false;
}
return true;
}
};
};