项目实现:云备份②(文件操作、Json等工具类的实现)
云备份
- 前言
- 文件操作实用工具类设计
- 文件属性的获取
- 文件的读写操作
- 文件压缩与解压缩的实现
- 文件目录操作
- Json 实用工具类设计
- 编译优化
前言
如果有老铁不知道当前项目实现的功能是什么的话,可以先移步这篇文章内容: 云备份项目的介绍
其中介绍了云备份项目的基本功能、环境搭建的详情。
文件操作实用工具类设计
文件属性的获取
创建 src 目录,在 src 目录下进行 util.hpp
文件的代码编写,实现文件工具类:
int64_t FileSize()
:获取文件大小time_t LastMTime()
:获取文件最后一次的修改时间time_t LastATime()
:获取文件最后一次的访问时间std::string FileName()
:获取文件名称
#include <iostream>
#include <string>
#include <vector>
#include <sys/stat.h>
namespace cloud
{
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 filesize false\n";
return -1;
}
return st.st_size;
}
// 获取文件最后一次的修改时间
time_t LastMTime()
{
struct stat st; // 用于获取文件属性
if (stat(_filename.c_str(), &st) < 0)
{
std::cout << "get file last modify time false\n";
return -1;
}
return st.st_mtime; // 文件的修改时间
}
// 获取文件最后一次的访问时间
time_t LastATime()
{
struct stat st; // 用于获取文件属性
if (stat(_filename.c_str(), &st) < 0)
{
std::cout << "get file last access time false\n";
return -1;
}
return st.st_atime; // 文件的修改时间
}
// 获取文件名
std::string FileName()
{
//找到切分文件名称的最后一个/
auto pos = _filename.find_last_of("/");
if(pos == std::string::npos)
{
//当前文件没有路径
return _filename;
}
//找到/后,进行字符串切割
return _filename.substr(pos+1);
}
private:
std::string _filename;
};
}
代码测试:
#include "util.hpp"
void FileUtilText(const std::string &filename)
{
cloud::FileUtil fu(filename);
std::cout << fu.FileSize() << std::endl;
std::cout << fu.LastMTime() << std::endl;
std::cout << fu.LastATime() << std::endl;
std::cout << fu.FileName() << std::endl;
}
int main(int argc, char* argv[])
{
FileUtilText(argv[1]);
return 0;
}
结果如下:
注意:这里直接访问到的是文件修改时间、访问时间,其实是时间戳。
文件的读写操作
继续在 util.hpp 文件内完善 FileUtil 类:
bool GetPosLen(std::string *body, size_t pos, size_t len)
:获取指定位置到指定长度的文件数据bool GetContent(std::string *body)
:获取整个文件的数据内容bool SetContent(const std::string &body)
:向文件写入数据
下面所有代码是在类中实现的,为了方便演示,只展示当前标题要实现的代码:
#include <iostream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <fstream>
namespace cloud
{
class FileUtil
{
public:
//其他函数...
// 获取指定位置到指定长度的文件数据
bool GetPosLen(std::string *body, size_t pos, size_t len)
{
size_t fsize = FileSize(); // 获取文件总长度
// 判断文件总长度是否符合 pos+len 长度
if (pos + len > fsize)
{
std::cout << "get file len is error\n";
return false;
}
std::ifstream ifs;
ifs.open(_filename.c_str(), std::fstream::binary); // 以二进制的方式打开文件
if (ifs.is_open() == false)
{
std::cout << "read open file false\n";
return false;
}
// 定位文件到pos位置
ifs.seekg(pos, std::ios::beg);
body->resize(len); // 扩容字符串
// 读取数据
ifs.read(&(*body)[0], len);
if (ifs.good() == false)
{
// 读取出错
std::cout << "get file content false\n";
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.c_str(), std::ios::binary); //打开目标文件
if(ofs.good() == false)
{
//打开文件失败
std::cout << "write open file false\n";
return false;
}
//将原文件内容写入到目标文件中
ofs.write(&body[0], body.size());
if(ofs.good() == false)
{
//写入失败
std::cout << "write file file false\n";
ofs.close();
return false;
}
//写入成功
ofs.close();
return true;
}
private:
std::string _filename;
};
}
测试代码:
void FileUtilText(const std::string &filename)
{
cloud::FileUtil fu(filename);
std::string body;
fu.GetContent(&body);//获取文件内容到body字符串中
cloud::FileUtil ufu("./test.txt"); //在当前目录的一个./text.txt文件
ufu.SetContent(body); //将body字符串内容写入到./text.txt文件中
}
int main(int argc, char* argv[])
{
FileUtilText(argv[1]);
return 0;
}
测试结果如下:
在这里拿 Makefile 文件举例:
将 Makefile 文件内容进行读取,放入到 text.txt文件中。对比两个文件生成哈希值可以看到一模一样,读取文件内容到写入文件过程是没有问题的。
文件压缩与解压缩的实现
依旧是完善 FileUtil 类,实现两个接口:
bool Compress(const std::string &packname)
:压缩文件bool UnCompress(const std::string &filename)
:解压文件
当然,在这里需要引入第三方库 bundle 进行文件的解压缩操作,不知道如何搭环境的小伙伴可以看这篇文章:项目环境搭建
bundle 库下载后,在 src 目录下,引入 bundle.h、bundle.cpp 文件即可:
实现文件的解压缩功能,在 util.hpp 文件内的 FileUtil 类进行编写:
#include <iostream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <fstream>
#include "bundle.h"
namespace cloud
{
class FileUtil
{
public:
//其他函数...
//压缩文件
bool Compress(const std::string &packname)
{
std::string body;
//获取文件内容
if(GetContent(&body) == false)
{
std::cout << "compress get file content falsed!\n";
return false;
}
//对刚刚提取的数据进行压缩,使用LZIP压缩格式
std::string packed = bundle::pack(bundle::LZIP, body);
//将压缩数据存储到压缩文件中
if(SetContent(packname) == false)
{
std::cout << "compress write packed data falsed!\n";
return false;
}
return true;
}
//解压文件
bool UnCompress(const std::string &filename)
{
std::string body;
//提取压缩包文件数据
if(GetContent(&body) == false)
{
std::cout << "uncompress get file content falsed!\n";
return false;
}
//解压数据
std::string unpacked = bundle::unpack(body);
//将解压数据放入到文件中
if(SetContent(unpacked ) == false)
{
std::cout << "uncompress write packed data falsed!\n";
return false;
}
return true;
}
private:
std::string _filename;
};
}
引入了 bundle.cpp 文件后,在 Makefile 文件中也要有对应内容修改:
cloud:cloud.cc util.hpp bundle.cpp
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f cloud
编写测试代码:
#include "util.hpp"
void FileUtilText(const std::string &filename)
{
std::string packname = filename + ".lz"; //.lz压缩包后缀
cloud::FileUtil fu(filename);
// 压缩文件
fu.Compress(packname);
cloud::FileUtil pfu(packname);
// 解压后的文件内容放入到 test.txt
pfu.UnCompress("./test.txt");
}
int main(int argc, char *argv[])
{
FileUtilText(argv[1]);
return 0;
}
文件目录操作
继续完善 FileUtil 类,实现三个接口:
bool Exists()
:判断文件是否存在bool CreateDirectory()
:创建目录bool ScanDirectory(std::vector<std::string> *arry)
:获取目录下的文件信息
代码实现:
#include <iostream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <fstream>
#include <experimental/filesystem>
#include "bundle.h"
namespace cloud
{
// 引用std::experimental::filesystem命名空间
namespace fs = std::experimental::filesystem;
class FileUtil
{
public:
//其他函数...
// 判断文件是否存在
bool Exists()
{
return fs::exists(_filename);
}
// 创建文件目录
bool CreateDirectory()
{
// 文件存在,无需创建目录
if (Exists()) return true;
// 否则创建这个目录
return fs::create_directories(_filename);
}
// 遍历整个目录,查看文件信息
bool ScanDirectory(std::vector<std::string> *arry)
{
// 迭代遍历目录下的文件
for (auto &p:fs::directory_iterator(_filename))
{
if (fs::is_directory(p) == true) continue;// 如果是一个目录,不再往下遍历
// 将带有路径的文件名称push到arr数组中
arry->push_back(fs::path(p).relative_path().string());
}
return true;
}
private:
std::string _filename;
};
}
在这里是直接调用了 C++17 的 filesystem 函数,对此在Makefile 中,要连接对应的库:-lstdc++fs
cloud:cloud.cc util.hpp bundle.cpp
g++ -o $@ $^ -lpthread -std=c++11 -lstdc++fs
.PHONY:clean
clean:
rm -f cloud
测试代码:
#include "util.hpp"
void FileUtilText(const std::string &filename)
{
cloud::FileUtil fu(filename);
fu.CreateDirectory(); //创建文件目录
std::vector<std::string> array;
fu.ScanDirectory(&array); //遍历目录文件,存储到array数组中
// 遍历目录文件
for (auto &e : array)
std::cout << e << std::endl;
}
int main(int argc, char *argv[])
{
FileUtilText(argv[1]);
return 0;
}
测试结果如下:
先前是没有 test 目录的,运行程序后,直接创建 test 目录。
接下来在 test 目录下创建一些文件、目录,再次运行 cloud ,查看结果:
可以看到在 test 目录下是文件的都遍历了一遍,abc 目录没有进行遍历。
至此,FileUtil 类主要功能就实现完了。接下来是 JsonUtil 类的实现:
Json 实用工具类设计
JsonUtil 类实现依旧是在 util.hpp 文件中进行编写。其类中主要实现两个接口:
static bool Serialize(const Json::Value &root, std::string *str)
:序列化static bool UnSerialize(const std::string &str, Json::Value *root)
:反序列化
使用前需要下载 Json 第三方库,具体的下载方式可以参考这篇文章:Json下载与使用
代码实现:
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <jsoncpp/json/json.h>
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\n";
return false;
}
//将序列化后的字符串保存到str中
*str = ss.str();
return true;
}
//反序列化
static bool UnSerialize(const std::string &str, Json::Value *val)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
//将反序列化内容存储到str中
bool ret = cr->parse(str.c_str(), str.c_str()+str.size(), val, &err);
if(ret == false)
{
std::cout << "parse error:" << err << std::endl;
return false;
}
return true;
}
};
为了编译时不报错,需要对 Makefile 文件进行修改,加入 -ljsoncpp
选项:
cloud:cloud.cc util.hpp bundle.cpp
g++ -o $@ $^ -lpthread -lstdc++fs -std=c++14 -ljsoncpp
测试代码:
void JsonUtilTest()
{
const char *name = "张三";
int age = 18;
float score[] = {90.5, 77, 100};
Json::Value root;
root["姓名"] = name;
root["年龄"] = age;
root["成绩"].append(score[0]);
root["成绩"].append(score[1]);
root["成绩"].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["姓名"].asString() << std::endl;
std::cout << val["年龄"].asInt() << std::endl;
for(auto &e : val["成绩"])
{
std::cout << e << std::endl;
}
}
int main(int argc, char *argv[])
{
JsonUtilTest();
return 0;
}
测试结果如下:
编译优化
云备份项目引入了 bundle 第三方压缩库,整个 bundle.cpp 内容还是比较大的。
为了代码编写的正确性,我们每写完一段代码后都要进行测试。每次测试都要进行整体代码的编译,包括 bundle库。
由于 bundle.cpp 编译时长比较久,难免会很难受。对此,下面对 bundle.cpp 直接生成静态库,编译时直接链接就可以了。这样就可以很好的避免编译等待时间过久的问题。
关于动静态库的制作,可以参考小编的这篇文章:动静态库的介绍
下面开始进行操作:
- 使用 g++ 编译器,将 bundle.cpp 源文件生成
.o
文件
g++ -c bundle.cpp -o bundle.o -std=c++11
- 制作 bundle 静态库
ar -cr libbundle.a bundle.o
只要看到 libbundle.a 文件,就说明制作成功了。对于 src 目录下的 bundle.cpp、bundle.o,可以直接删除。
- 创建 lib 目录,将 libbundle.a 剪切到 lib 目录下即可
4. 更改 Makefile 内容,编译所用到的库
cloud:cloud.cc util.hpp
g++ -o $@ $^ -lpthread -lstdc++fs -std=c++14 -ljsoncpp -L./lib -lbundle
.PHONY:clean
clean:
rm -f cloud
此后,我们对整个项目进行编译,无需再对 bundle.cpp 进行处理,可以节省很多时间。
后续内容会持续更新,喜欢的老铁可以点赞、收藏加关注,感谢大家的观看!