• C++项目——云备份-③-实用工具类设计与实现


    专栏导读

    🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

    🌸专栏简介:本文收录于 C++项目——云备份

    🌸相关专栏推荐:C语言初阶系列C语言进阶系列 C++系列数据结构与算法Linux
    🌸项目Gitee链接:https://gitee.com/li-yuanjiu/cloud-backup

    在这里插入图片描述

    1.文件实用工具类的设计

    不管是客户端还是服务端,文件的传输备份都涉及到文件的读写,包括数据管理信息的持久化也是如此,因此首先设计封装文件操作类,这个类封装完毕之后,则在任意模块中对文件进行操作时都将变的简单化。

    文件实用工具类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; // 文件名--包含路径
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • _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:浏览指定目录下的所有文件;

    2.文件实用工具类的实现

    2.1前置知识补充

    FileUtil的实现中,我们使用了较多相对不常见库函数,在此先做复习。

    2.1.1struct stat 与 stat介绍

    认识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;      // 文件占用的块数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    struct stat结构体中的这些字段提供了关于文件或目录的各种信息。不同的操作系统可能会提供额外的字段,或者字段的意义可能会有所不同。

    stat函数用于获取与指定路径名相关联的文件或目录的属性,并将这些属性填充到一个struct stat结构体中。以下是stat函数的函数原型:

    int stat(const char *pathname, struct stat *statbuf);
    
    • 1
    • pathname是要获取属性的文件或目录的路径名;
    • statbuf是一个指向struct stat结构体的指针,用于存储获取到的属性信息;
    • stat函数返回一个整数值,如果操作成功,返回0;如果出现错误,返回-1,并设置errno全局变量以指示错误的类型。

    注意需要包含头文件来使用stat函数。

    2.1.2std::experimental::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 库来执行文件系统操作。

    2.2FileUtil实现

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    3.JSON实用工具类的设计

    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); // 反序列化操作
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    由于前面的章节已经介绍过Json的使用,接下来我们直接看函数的实现。

    4.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;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    5.实用工具类整理

    最后,我们将两个实用工具类都放到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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228

    在这里插入图片描述

  • 相关阅读:
    大学生HTML个人网页作业作品:基于html css实现围棋网页(带报告4800字)
    记一次 .NET 某仪器测量系统 CPU爆高分析
    51.HarmonyOS鸿蒙系统 App(ArkUI)通知
    springboot中定时任务cron不生效,fixedRate指定间隔失效,只执行一次的问题
    Kaggle竞赛 Real or Not? NLP with Disaster Tweets 文本分类
    基于C#的应用程序单例唯一运行的完美解决方案 - 开源研究系列文章
    Gradle-JDK版本问题导致运行失败
    如何快速实现Modbus RTU和Modbus TCP协议转换?
    #775 Div.1 C. Tyler and Strings 组合数学
    uni-app医院智能导诊系统源码
  • 原文地址:https://blog.csdn.net/gllll_yu/article/details/134058902