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