【云備份】服務端工具類實現

1.文件實用工具類設計

不管是客戶端還是服務端,文件的傳輸備份都涉及到文件的讀寫,包括數據管理信息的持久化也是如此,因此首先設 計封裝文件操作類,這個類封裝完畢之后,則在任意模塊中對文件進行操作時都將變的簡單化。

文件系統庫 - cppreference.com

文件實用工具類FileUtil主要包含以下成員:

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.ccbool 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()函數的實現
int64_t FileSize(); // 獲取文件大小,這里使用64位的有符號的int,防止文件過大導致文件大小顯示異常

這個需要我們了解一下不太常用的接口函數——stat

man 2 STAT

?

stat函數用于獲取與指定路徑名相關聯的文件或目錄的屬性,并將這些屬性填充到一個struct stat結構體中。以下是stat函數的函數原型:?

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結構體的字段,盡管具體字段可能會因操作系統而異:

struct stat {dev_t     st_dev;         // 文件所在設備的IDino_t     st_ino;         // 文件的inode號mode_t    st_mode;        // 文件的訪問權限和類型nlink_t   st_nlink;       // 文件的硬鏈接數量uid_t     st_uid;         // 文件的所有者的用戶IDgid_t     st_gid;         // 文件的所有者的組IDoff_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函數

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(僅秒級)更高的時間精度。

#include <time.h>  // 需要包含的頭文件struct timespec {time_t   tv_sec;   // 秒(自 1970-01-01 00:00:00 UTC 的秒數)long     tv_nsec;  // 納秒(0 到 999,999,999)
};

來看看怎么使用?

測試函數——獲取當前時間(納秒級)

#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;
}

?


現在我們就應該知道怎么使用了

 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,那么我們只需要獲取最后一個/后面到最末尾的東西即可

 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

#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; // 文件名--包含路徑};
};

?代碼寫到這里我們,我們必須進行測試我們的代碼對不對,要不然寫到后面我們一直報錯就不好了

cloud.cc

#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

cloud:cloud.cc util.hppg++ $^ -o $@
.PHONY:clean
clean:rm -f cloud

?我們編譯運行一下

這個就很好了!!?

我們去看看

我們只是在main函數里面添加了一句retrun 0,就發現它們都修改了?

我們再看Filename()的測試,也是很不錯!!

這就說明我們完成的代碼就很好了


接下來我要講一下git,如果只想看項目的可以往下一節去了

首先我們先創建本地倉庫

?然后配置本地倉庫

接著提交,發現git add *報錯了?

?我們有兩種解決方法,但是我只選擇下面這種

直接包含子目錄代碼(不保留 Git 歷史)

如果這些目錄不需要獨立維護(例如你只是復制了代碼,不需要跟蹤它們的更新),可以刪除它們的?.git?文件夾,再添加到父倉庫。

刪除子目錄中的?.git?文件夾

rm -rf bundle/.git cpp-httplib/.git

重新添加所有文件,提交更改

接下來要把它推送到遠程倉庫里面去,首先我們要先創建一個遠程倉庫

?

有了本地倉庫和遠程倉庫后,可以將二者關聯起來,以便推送和拉取代碼:

  • 在本地倉庫中,執行以下命令來添加遠程倉庫的地址:

git remote add origin <遠程Git倉庫地址>

其中,<遠程Git倉庫地址>是你的遠程Git倉庫的網址。

對于如何獲取遠程Git倉庫地址,我們舉例說明:

比如,你的遠程Git倉庫地址為:

https://github.com/your/your.git

那么你在本地使用“git remote add origin”指令的語法就應該是:

git remote add origin https://github.com/your/your.git

執行這條指令之后,你的本地項目就與遠程Git倉庫建立了連接,你就可以開始對你的代碼進行版本追蹤和協作開發了。

?

  • 檢查關聯是否成功,執行以下命令:

git remote -v
  • 推送到遠程倉庫

關聯完成后,可以將本地倉庫中的代碼推送到遠程倉庫中:

git push -f origin master

注意:-f是強制的意思?

這樣就將本地倉庫中的代碼推送到了遠程倉庫的?master?分支上。如果是第一次推送,可能需要輸入用戶名和密碼進行身份認證。


?

?這很好了

git remote add origin的一些常用操作
1. 更改默認的遠程倉庫
在項目中可能存在多個遠程倉庫,如果你想更改默認倉庫,可以使用如下指令:

git remote set-url origin <新的遠程Git倉庫地址>

2. 查看當前的遠程倉庫
如果你想查看當前項目的遠程倉庫,可以使用如下指令:

git remote -v

3. 刪除遠程倉庫
如果你需要刪除已經添加的遠程倉庫,可以使用如下指令:

git remote rm origin

執行這條指令之后,Git就會將已經添加的名為“origin”的倉庫刪除。


2.2.文件的讀寫操作的實現

GetPosLen()函數

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()

bool GetContent(std::string* body)
{size_t fsize = FileSize();return GetPosLen(body, 0, fsize);
}

?SetContent()

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;
}

接下來我們來測試一下

cloud.cc

#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函數。這是非常簡單的。

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;
}

由于我們bundle庫是第三方庫,所以不要忘記了添加頭文件

除此之外還是不夠的,我們還需要將bundle.h和bundle.cpp拷貝到當前目錄下來

好,現在我們來測試一下

makefile

cloud:cloud.cc util.hpp bundle.cppg++ $^ -o $@ -lpthread
.PHONY:clean
clean:rm -f cloud

?cloud.cc

#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 庫的主要特性和功能:

  1. std::experimental::filesystem::path 類:用于表示文件路徑。它可以自動處理不同操作系統的路徑分隔符,使得代碼更具可移植性。
  2. 文件和目錄操作:這個庫提供了許多函數來執行常見的文件和目錄操作,包括文件創建、復制、移動、刪除,以及目錄的創建、刪除、遍歷等。
  3. 文件屬性查詢:你可以使用這個庫來查詢文件和目錄的屬性,如文件大小、修改時間等。
  4. 異常處理:std::experimental::filesystem 庫定義了一些異常類,以處理與文件系統操作相關的錯誤,如文件不存在或無法訪問等問題。
  5. 迭代器:你可以使用迭代器來遍歷目錄中的文件和子目錄,這是一個非常方便的功能,用于遞歸遍歷文件系統。

需要注意的是,盡管 std::experimental::filesystem 在 C++17 中引入,但它是一個實驗性的特性,并且不一定在所有編譯器和平臺上都得到完全支持。因此,一些編譯器可能需要特定的編譯選項或配置才能使用這個庫。

從 C++17 開始,文件系統庫已正式成為 C++ 標準的一部分,并遷移到 std::filesystem 命名空間中,而不再是實驗性的特性。因此,在新的 C++標準中,建議使用 std::filesystem 庫來執行文件系統操作。

大家想要了解具體內容請去:文件系統庫 - cppreference.com

  • ScanDirectory()函數的實現?

我們點開這個?

?

我們看看例子

可能大家看不太懂,我加注釋給你們看看?

// 使用實驗性文件系統庫(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.txtstd::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;
}

?這樣子就很簡單易懂了。

我們完全可以將其復制下來,就能知道怎么使用這個代碼了。

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()的實現

我們去剛剛那個網站,就很容易看到下面這個?

點進去看就明白了

也就是下面這個

bool exists(const path& p);

檢查給定的路徑(path)是否對應一個實際存在的文件或目錄。?

返回值

  • 若路徑?p?對應的文件或目錄存在,返回?true;否則返回?false

現在我們很容易就寫出下面這個?

namespace fs = std::experimental::filesystem;
bool Exists()
{return fs::exists(_filename);
}

  • ??Remove()的實現

點進去看看?

?我們借助DeepSeek幫我們解析這個函數的用法

  • remove:刪除單個文件或空目錄(類似 POSIX 的?remove)。

    • 符號鏈接處理:刪除符號鏈接本身,而非其指向的目標。

    • 限制:若路徑是目錄,必須為空才能刪除,否則失敗。

bool remove(const path& p);
  • p?– 要刪除的文件或空目錄的路徑。

  • 返回值

    • 成功刪除或文件不存在時返回?true

    • 若路徑存在但刪除失敗(如目錄非空或權限不足),返回?false

bool Remove()
{if(Exists() == false){return true;}remove(_filename.c_str());return true;
}

  • ?CreateDirectory()的實現

還是熟悉的配方,我不過多說,大家不懂的可以去這里看:Filesystem library - cppreference.com

bool CreateDirectory()
{if (Exists()) return true;//如果存在,則直接返回true即可return fs::create_directories(_filename);//不存在的話,創建一個文件
}

注意要包含頭文件#include <experimental/filesystem>


接下來來測試一下

Util.hpp

#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; // 文件名--包含路徑};
};

cloud.cc?

#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

cloud:cloud.cc util.hpp bundle.cppg++ $^ -o $@ -lpthread -lstdc++fs
.PHONY:clean
clean:rm -f cloud

編譯運行

發現它創建了一個目錄

?

這個文件管理寫的很好了吧!!


還是老樣子!git push一下

?

?3.JSON實用工具類實現

Jsoncpp已經為我們你提供了序列化與反序列化接口,但是為了使得實用更加便捷,我們可以自己再封裝一個JsonUtil的類。

JsonUtil類中包含以下成員

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的使用,接下來我們直接看函數的實現。

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?

cloud:cloud.cc util.hpp bundle.cppg++ $^ -o $@ -lpthread -lstdc++fs -ljsoncpp
.PHONY:clean
clean:rm -f cloud

cloud.cc?

?

#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",值為整型變量ageroot["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源代碼

#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;}};
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79256.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79256.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79256.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CGI 協議是否會具體到通訊報文?

CGI&#xff08;Common Gateway Interface&#xff09;不涉及具體的網絡通訊報文格式&#xff0c;它定義的是 Web服務器與外部程序之間的數據交互方式&#xff0c;而不是像HTTP或FastCGI那樣的二進制協議。下面分幾個方面詳細說明&#xff1a; 1. CGI 的交互方式&#xff08;非…

【Mytais系列】Type模塊:類型轉換

MyBatis 的 類型系統&#xff08;Type System&#xff09; 是框架處理 Java 類型與數據庫類型之間映射的核心模塊&#xff0c;它通過 類型處理器&#xff08;TypeHandler&#xff09;、類型別名&#xff08;TypeAlias&#xff09; 和 類型轉換器 等機制&#xff0c;實現了數據庫…

新華三H3CNE網絡工程師認證—動態NAT

靜態NAT嚴格地一對一進行地址映射&#xff0c;這就導致即便內網主機長時間離線或者不發送數據時&#xff0c;與之對應的共有地址也處于使用狀態。為了避免地址浪費&#xff0c;動態NAT提出了地址池的概念&#xff1a;所有可用的共用地址組成地址池。 當內部主機訪問外部網絡時臨…

華為OD機試真題 Java 實現【水庫蓄水問題】

前言 博主刷的華為機考題&#xff0c;代碼僅供參考&#xff0c;因為沒有后臺數據&#xff0c;可能有沒考慮到的情況 如果感覺對你有幫助&#xff0c;請點點關注點點贊吧&#xff0c;謝謝你&#xff01; 題目描述 思路 1. 其實就是找一個最大的水坑&#xff0c;兩個…

【Linux】Petalinux驅動開發基礎

基于Petalinux做Linux驅動開發。 部分圖片和經驗來源于網絡,若有侵權麻煩聯系我刪除,主要是做筆記的時候忘記寫來源了,做完筆記很久才寫博客。 專欄目錄:記錄自己的嵌入式學習之路-CSDN博客 目錄 1 一個完整的Linux系統(針對Zynq) 1.1 PS部分 1.2 PL部分(若…

JAVA刷題記錄: 遞歸,搜索與回溯

專題一 遞歸 面試題 08.06. 漢諾塔問題 - 力扣&#xff08;LeetCode&#xff09; class Solution {public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {dfs(A, B, C, A.size());}public void dfs(List<Integer> a, List<In…

YOLOv11改進:利用RT-DETR主干網絡PPHGNetV2助力輕量化目標檢測

這里寫自定義目錄標題 YOLOv11改進&#xff1a;利用RT-DETR主干網絡PPHGNetV2助力輕量化目標檢測1. 介紹2. 引言3. 技術背景3.1 YOLOv11概述3.2 RT-DETR與PPHGNetV23.3 相關工作 4. 應用使用場景5. 詳細代碼實現5.1 環境準備5.2 PPHGNetV2主干網絡實現5.3 YOLOv11與PPHGNetV2集…

WPF之Button控件詳解

文章目錄 1. 引言2. Button控件基礎Button類定義 3. Button控件的核心屬性3.1 Content屬性3.2 IsDefault屬性3.3 IsCancel屬性3.4 其他常用屬性 4. 按鈕樣式與模板自定義4.1 簡單樣式設置4.2 使用Style對象4.3 觸發器使用4.4 使用ControlTemplate完全自定義4.5 按鈕視覺狀態 5.…

【Java】2025 年 Java 學習路線:從入門到精通

文章目錄 一、Java基礎階段(4-8周)1. 開發環境搭建2. 核心語法基礎3. 面向對象編程(OOP)4. 核心類庫二、Java進階階段(6-10周)1. JVM深度理解2. 并發編程3. 新特性掌握4. 設計模式三、開發框架與中間件(8-12周)1. Spring生態2. 持久層框架3. 常用中間件四、項目實戰階段…

虛幻引擎入門筆記

【虛幻5】UE5新手入門嘗試 虛幻引擎的基礎設置 1.驗證-當文件誤刪的時候&#xff0c;對其進行驗證&#xff0c;可以恢復。 2.虛幻引擎極其強大&#xff0c;可以實現多種復合技能&#xff0c;所在創建項目頁面可以看見不只是創建游戲的項目 3.更改虛幻引擎默認的緩存地址。有些…

【PostgreSQL數據分析實戰:從數據清洗到可視化全流程】1.1 數據庫核心概念與PostgreSQL技術優勢

&#x1f449; 點擊關注不迷路 &#x1f449; 點擊關注不迷路 &#x1f449; 點擊關注不迷路 文章大綱 深度解析PostgreSQL核心架構與技術優勢&#xff1a;從數據庫原理到實戰場景1.1 數據庫核心概念與PostgreSQL技術優勢1.1.1 關系型數據庫核心架構解析1.1.1.1 數據庫系統的底…

詳解SLAM中的李群和李代數(上)

1 概述 最近閱讀高翔大神的《視覺SLAM十四講》這本書&#xff0c;感覺整本書寫的非常的平實&#xff0c;用非常接地氣的語言毫無保留的介紹了視覺SLAM的相關知識&#xff0c;非常值得一讀。不過&#xff0c;在第4章出現的李群和李代數的相關概念就有點令人難以費解了。其實這段…

libevent庫詳解:高性能異步IO的利器

目錄 一、libevent 簡介 主要特點&#xff1a; 二、事件模型原理 1. event_base 2. event 3. evconnlistener&#xff08;TCP監聽器&#xff09; 4. bufferevent 簡化流程如下&#xff1a; 三、libevent 使用示例 1. 創建事件主循環 2. 創建監聽器&#xff08;TCP&a…

從 “零” 做個開源音樂軟件“SteadyBeat”吧!<1> 準備

換換腦子&#xff0c;做個音樂軟件&#xff0c;根據調性、和弦走向&#xff08;情感&#xff09;、節拍、速度等需求&#xff0c;結合AI和一眾工具&#xff0c;自動生成伴奏、Solo等&#xff0c;有點像庫樂隊&#xff01;自己平時也用得著&#xff0c;暫時取名叫《SteadyBeat》…

npm error code CERT_HAS_EXPIRED

npm error code CERT_HAS_EXPIRED 歡迎來到我的主頁&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就職于醫療科技公司&#xff0c;熱衷分享知識&#xff0c;武漢城市開發者社區主理人 擅長.net、C、python開發&#xff0c; 如果遇到技術問題&#xff0c;即可私…

數字世界的“私人車道“:網絡切片如何用Python搭建專屬通信高速路?

數字世界的"私人車道"&#xff1a;網絡切片如何用Python搭建專屬通信高速路&#xff1f; 2024年6月&#xff0c;中國移動宣布在浙江某智能工廠完成全球首個"5G工業網絡切片"規模商用——這條為生產線定制的"數字專屬車道"&#xff0c;將設備控制…

VSCode Verilog編輯仿真環境搭建

VSCode Verilog環境搭建 下載Iverilog安裝Iverilog驗證安裝VS Code安裝插件 下載Iverilog 官網下載Iverilog 安裝Iverilog 一定要勾選這兩項 建議勾選這兩項 驗證安裝 運行Windows PowerShell輸入命令&#xff1a;iverilog輸入命令&#xff1a;Get-Command gtkwave …

C++ - 數據容器之 list(創建與初始化、元素訪問、容量判斷、元素遍歷、添加元素、刪除元素)

一、創建與初始化 引入 <list> 并使用 std 命名空間 #include <list>using namespace std;創建一個空 list list<int> my_list;創建一個包含 5 個元素&#xff0c;每個元素初始化為 0 的 list list<int> my_list(5);創建一個包含 5 個元素&#xf…

自動化測試項目1 --- 嘮嗑星球 [軟件測試實戰 Java 篇]

目錄 項目介紹 項目源碼庫地址 項目功能測試 1.自動化實施步驟 1.1 編寫測試用例 1.2 自動化測試腳本開發 1.2.1 配置相關環境, 添加相關依賴 1.2.2 相關代碼編寫 2. 自動化功能測試總結 2.1 彈窗的解決相關問題 2.2 斷言的使用和說明 2.3 重新登錄問題 項目性能…

Codeforces Round 1022 (Div. 2)(ABC)

A. Permutation Warm-Up 翻譯&#xff1a; 對于長度為 n 的排列 p&#xff0c;我們定義函數&#xff1a; 給你一個數 n。你需要計算函數 f(p) 在考慮從 1 到 n 的所有可能的數字排列時&#xff0c;可以取多少個不同的值。 思路&#xff1a; 按序排列時和為0&…