快速搜索神器
項目代碼:github地址
先看看我們的項目成果吧
- 漢字搜索
- 漢字首字母搜索
- 漢字全拼搜索
1.調研實現背景
在linux環境下有非常好用的find命令,查找文檔非常的高效,但是在windows下文件夾框下是默認的暴力遍歷查找,非常的慢。
此搜索神器就是爲了解決這個問題,爲了快速的查找到查找的文件或者目錄。
2.項目實現目標以及框架
-
實現目標
就是爲了查找文件或者目錄能夠快速地查找到,另外它能夠支持拼音搜索,拼音首字母搜索,拼音漢字混合搜索。 -
框架設計
3.文件監控系統
使用掃描+監控的方式,這裏方式是誰也離不開誰,是互補的。
- 文件監控系統是利用windows上的接口函數監控某個目錄下的文檔的變化,是實時的。如果在監控程序沒有啓動的時候是沒有辦法檢測到數據的變化。
- 文件系統掃描是通過系統接口,遍歷獲取目錄下的所有文檔和數據庫中的符合條件的文檔進行對比,獲取文檔的變化,有點是監控程序啓動前,變化的文檔也是可以對比的。添加到數據庫中就是使用的此方法。
主要的獲取目錄文件使用的是_findfirst
和_findnext
static void DirectoryList(const std::string& path, std::vector<std::string>&subfiles, \
std::vector<std::string>&subdirs){
_finddata_t file;//定義一個文件結構體
//此時的路徑是需要改變的,需要遍歷該目錄下面的,傳遞進來的只是到底此目錄文件
std::string _path = path + "\\*.*";//
intptr_t handle = _findfirst(_path.c_str(), &file);
if (handle == -1){
ERROE_LOG("_findfirst:%s", _path.c_str());
}
do {
// _A_SUBDIR(文件夾)就是目錄,否則就是文件,name就是file的名字屬性,是一個數組,長度是256。
if ((file.attrib & _A_SUBDIR)&&!(file.attrib&_A_HIDDEN)){
//目錄的時候需要判斷是不是.或者..,如果包含了這兩個文件在查詢的時候就會遞歸死循環
if ((strcmp(file.name, ".") != 0) && (strcmp(file.name, "..") != 0)){
subdirs.push_back(file.name);
}
}
else{
//此時就是文件了
subfiles.push_back(file.name);
}
} while (_findnext(handle, &file) == 0);
_findclose(handle);
}
而監控使用的是windows上的接口ReadDirectoryChangesW
來監控制定的文件夾
void fileWatcher()
{
DWORD cbBytes;
char file_name[MAX_PATH] = {'\0'}; //設置文件名;
char file_rename[MAX_PATH] = {'\0'}; //設置文件重命名後的名字;
char notify[1024] = { '\0' };
int count = 0; //文件數量。可能同時拷貝、刪除多個文件,可以進行更友好的提示;
TCHAR *dir = _T("D:");
std::string s = "D:";
//HANDLE就是一個句柄
HANDLE dirHandle = CreateFile(dir,
GENERIC_READ | GENERIC_WRITE | FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (dirHandle == INVALID_HANDLE_VALUE){ //若網絡重定向或目標文件系統不支持該操作,函數失敗,同時調用GetLastError()返回ERROR_INVALID_FUNCTION
cout << "error" + GetLastError() << endl;
}
//FILE_NOTIFY_INFORMATION是一個結構體,是柔型數組,將數組強轉爲結構體指針
memset(notify, 0, strlen(notify));
FILE_NOTIFY_INFORMATION *pnotify = (FILE_NOTIFY_INFORMATION*)notify;
// cout << "Start Monitor..." << endl;
while (true){
if (ReadDirectoryChangesW(dirHandle, ¬ify, 1024, true,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
&cbBytes, NULL, NULL))
{
pnotify = (FILE_NOTIFY_INFORMATION*)notify;
//轉換文件名爲多字節字符串;
if (pnotify->FileName){
memset(file_name, 0, strlen(file_name));
int ret = WideCharToMultiByte(CP_ACP, 0, pnotify->FileName, pnotify->FileNameLength / 2, file_name, 99, NULL, NULL);
if (ret == 0){
GetLastError();
}
// cout <<"pmotify->FileName:"<< pnotify->FileName << endl;
}
if ((pnotify->Action == FILE_ACTION_ADDED) | (pnotify->Action == FILE_ACTION_REMOVED) | (pnotify->Action == FILE_ACTION_RENAMED_OLD_NAME)){
// cout << "pnotify->FileName" << pnotify->FileName << endl;;
//如果發生了這些事件的話需要重新掃描當前的目錄文件
//就需要獲取當前的目錄名稱
std::string dirpath = s + "\\" + file_name;
get_dir_path(dirpath);
// cout << dirpath << endl;
//返回上一層的目錄
ScanManager::CreateIntance()->ScanNotRecursion(dirpath);
//cout << "掃描模塊的地址monitor" << ScanManager::CreateIntance() << endl;
//cout << "DirPath:" << dirpath << endl;
}
}
}
CloseHandle(dirHandle);
}
此外實現一個監控模塊的類
class ScanManager{
public:
void Scan(const std::string& path);
static ScanManager* CreateIntance(){
static ScanManager s;
return &s;
}
void startScan(const std::string& path){
Scan(path);
}
void ScanNotRecursion(const std::string& path);
private:
//封裝一個SqliteManager對象,用於我們數據的插入和刪除
ScanManager(){
}//構造單例模式
//需要的就是講掃描出來的數據和數據庫中的數據進行對別,新增的數據插入到數據庫,刪除的數據在數據庫中刪除。
};
這裏實現了構造函數,爲了在程序中永遠只有一個類進行掃描,不會出現兩個類同時掃描同一個文件夾。
4.數據持久化
數據持久化封裝
這裏採用sqlite進行數據持久化,爲什麼不選用mysql進行數據持久化了?因爲本次文件搜索是使用在本地,而不是在網絡中,不需要網絡服務,mysql支持網絡服務但是在累贅了,所以選用本地輕量級的sqlite即可。
sqlite官網下載
另外在菜鳥中也有sqlite的用法
sqlite菜鳥教程
我們直接操作數據庫不符合習慣,所以對sqlite進行封裝。
實現一個sqliteManager的類
class SqliteManager{
public:
SqliteManager()
:_db(nullptr){
};
~SqliteManager(){
Close();
}
//打開一個數據庫
void Open(const std::string& path);
//關閉數據庫
void Close();
//對數據庫執行插入刪除等操作
void ExecuteSql(const std::string sql);
//對數據庫進行查找操作,得到的是一個二維數組的指針,其實就是一個一個存放的,用二維數組表示,實際上是一維數組
void GetTable(const std::string sql, int &row, int &col, char**&ppRet);
//防止拷貝和賦值
SqliteManager(const SqliteManager&) = delete;
SqliteManager& operator=(const SqliteManager&) = delete;
private:
sqlite3* _db;
std::mutex _mutex;
};
其實就是在類中封裝一個數據庫的指針來指向這個數據庫,並且使用的c++11中的mutex
來保持線程安全,本質上sqlite是安全的,但是sqlite特會出現data_lock
,雖然時間很短,但是仍然會導致線程不安全。
重點
因爲在sqlite3_get_table(_db, sql.c_str(), &(ppRet), &row, &col, &errmsg);
中會有一個佈局變量二級指針,它的內存底層是malloc
申請的,所以在使用的實收如果不進行free
的話就會導致內存泄漏。所以實現RAII來管理這個指針。
class AutoGetTable{
public:
AutoGetTable(SqliteManager* dbMag, const std::string sql, int & row, int & col, char**& ppRet)
:_dbMag(dbMag), _ppVlaue(0)
{
_dbMag->GetTable(sql, row, col, ppRet);
_ppVlaue = ppRet;
}
virtual ~AutoGetTable(){
if (_ppVlaue){
sqlite3_free_table(_ppVlaue);
}
}
private:
SqliteManager* _dbMag;
char ** _ppVlaue;
};
數據管理封裝
在對數據庫的接口進行封裝之後實現一個對數據庫的管理操作,這是和監控模塊聯繫在一起的,因爲文檔中的內容和數據庫中的內容比較的時候如果在數據庫中存在文檔中不存在就要刪除,如果在數據庫中不存在在文檔中存在就要增加,都存在的時候就不變。
class DataManager{
public:
static DataManager* GetInstannce(){
static DataManager m_db;
return &m_db;
}
void Init();
//得到數據庫中的內容
void GetDocs(std::string path, std::set<std::string>& dbset);
//向數據庫中插入數據
void InsertDocs(const std::string path,const std::string doc);
//在數據庫中刪除數據
void DeleteDoc(const std::string& path,const std::string doc);
//利用關鍵字進行查找
void Search(const std::string& key, std::vector<std::pair<std::string, std::string>>&doc_paths);
~DataManager(){
}
private:
DataManager(){
Init();
};
//維護一個SqliteManager的對象
SqliteManager _sqma;
};
中間邏輯層
中間邏輯主要是處理搜索和高亮模塊,實現類似qq的搜索功能以及關鍵字出現高亮功能。例如下面關鍵字首字母搜索
關鍵字全拼搜索
關鍵字漢字搜索
- 模糊匹配
使用數據的like實現模糊匹配檢索。
if (key.size() == 1){
sprintf(ch, "select doc_name,doc_path from Doc_Tab where \
doc_name_init like '%%%s%%';", InitPy.c_str());
}
else{
sprintf(ch, "select doc_name,doc_path from Doc_Tab where doc_name_py like '%%%s%%' or \
doc_name_init like '%%%s%%';", pinyin.c_str(), InitPy.c_str());
}
-
拼音全拼搜索
在數據庫中存儲文件名轉換拼音全拼的內容,搜索的時候將關鍵字轉換爲全拼即可。[漢字轉全拼],這裏沒有自己實現,參考別人的代碼。(CSDN)csdn -
拼音首字母搜索
存儲時將文件名轉換成一個拼音首字母存在數據庫表的doc_nam_init字段中,搜索的時候也將關鍵字轉換成拼音首字母,然後使用數據庫模糊匹配
static std::string GetFirstLetter(const char* strChs)
{
static int li_SecPosValue[] = {
1601, 1637, 1833, 2078, 2274, 2302, 2433, 2594, 2787, 3106, 3212,
3472, 3635, 3722, 3730, 3858, 4027, 4086, 4390, 4558, 4684, 4925, 5249
};
static char* lc_FirstLetter[] = {
"a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "w", "x", "y", "z"
};
static char* ls_SecondSecTable =
"CJWGNSPGCGNE[Y[BTYYZDXYKYGT[JNNJQMBSGZSCYJSYY[PGKBZG\
Y[YWJKGKLJYWKPJQHY[W[DZLSGMRYPYWWCCKZNKYYGTTNJJNYKKZYT\
CJNMCYLQLYPYQFQRPZSLWBTGKJFYXJWZLTBNCXJJJJTXDTTSQZYCDXX\
HGCK[PHFFSS[YBGXLPPBYLL[HLXS[ZM[JHSOJNGHDZQYKLGJHSGQZHXQ\
GKEZZWYSCSCJXYEYXADZPMDSSMZJZQJYZC[J[WQJBYZPXGZNZCPWHKXH\
QKMWFBPBYDTJZZKQHY"
"LYGXFPTYJYYZPSZLFCHMQSHGMXXSXJ[[DCSBBQBEFSJYHXWGZKPYLQBGLD\
LCCTNMAYDDKSSNGYCSGXLYZAYBNPTSDKDYLHGYMYLCXPY[JNDQJWXQXFYYF\
JLEJPZRXCCQWQQSBNKYMGPLBMJRQCFLNYMYQMSQYRBCJTHZTQFRXQHXMJJC\
JLXQGJMSHZKBSWYEMYLTXFSYDSWLYCJQXSJNQBSCTYHBFTDCYZDJWYGHQFR\
XWCKQKXEBPTLPXJZSRMEBWHJLBJSLYYSMDXLCLQKXLHXJRZJMFQHXHWY"
"WSBHTRXXGLHQHFNM[YKLDYXZPYLGG[MTCFPAJJZYLJTYANJGBJPLQGDZYQ\
YAXBKYSECJSZNSLYZHSXLZCGHPXZHZNYTDSBCJKDLZAYFMYDLEBBGQYZKXG\
LDNDNYSKJSHDLYXBCGHXYPKDJMMZNGMMCLGWZSZXZJFZNMLZZTHCSYDBDLL\
SCDDNLKJYKJSYCJLKWHQASDKNHCSGANHDAASHTCPLCPQYBSDMPJLPZJOQLC\
DHJJYSPRCHN[NNLHLYYQYHWZPTCZGWWMZFFJQQQQYXACLBHKDJXDGMMY"
"DJXZLLSYGXGKJRYWZWYCLZMSSJZLDBYD[FCXYHLXCHYZJQ[[QAGMNYXPFR\
KSSBJLYXYSYGLNSCMHZWWMNZJJLXXHCHSY[[TTXRYCYXBYHCSMXJSZNPWGP\
XXTAYBGAJCXLY[DCCWZOCWKCCSBNHCPDYZNFCYYTYCKXKYBSQKKYTQQXFCW\
CHCYKELZQBSQYJQCCLMTHSYWHMKTLKJLYCXWHEQQHTQH[PQ[QSCFYMNDMGB\
WHWLGSLLYSDLMLXPTHMJHWLJZYHZJXHTXJLHXRSWLWZJCBXMHZQXSDZP"
"MGFCSGLSXYMJSHXPJXWMYQKSMYPLRTHBXFTPMHYXLCHLHLZYLXGSSSSTCL\
SLDCLRPBHZHXYYFHB[GDMYCNQQWLQHJJ[YWJZYEJJDHPBLQXTQKWHLCHQXA\
GTLXLJXMSL[HTZKZJECXJCJNMFBY[SFYWYBJZGNYSDZSQYRSLJPCLPWXSDW\
EJBJCBCNAYTWGMPAPCLYQPCLZXSBNMSGGFNZJJBZSFZYNDXHPLQKZCZWALS\
BCCJX[YZGWKYPSGXFZFCDKHJGXDLQFSGDSLQWZKXTMHSBGZMJZRGLYJB"
"PMLMSXLZJQQHZYJCZYDJWBMYKLDDPMJEGXYHYLXHLQYQHKYCWCJMYYXNAT\
JHYCCXZPCQLBZWWYTWBQCMLPMYRJCCCXFPZNZZLJPLXXYZTZLGDLDCKLYRZ\
ZGQTGJHHGJLJAXFGFJZSLCFDQZLCLGJDJCSNZLLJPJQDCCLCJXMYZFTSXGC\
GSBRZXJQQCTZHGYQTJQQLZXJYLYLBCYAMCSTYLPDJBYREGKLZYZHLYSZQLZ\
NWCZCLLWJQJJJKDGJZOLBBZPPGLGHTGZXYGHZMYCNQSYCYHBHGXKAMTX"
"YXNBSKYZZGJZLQJDFCJXDYGJQJJPMGWGJJJPKQSBGBMMCJSSCLPQPDXCDY\
YKY[CJDDYYGYWRHJRTGZNYQLDKLJSZZGZQZJGDYKSHPZMTLCPWNJAFYZDJC\
NMWESCYGLBTZCGMSSLLYXQSXSBSJSBBSGGHFJLYPMZJNLYYWDQSHZXTYYWH\
MZYHYWDBXBTLMSYYYFSXJC[DXXLHJHF[SXZQHFZMZCZTQCXZXRTTDJHNNYZ\
QQMNQDMMG[YDXMJGDHCDYZBFFALLZTDLTFXMXQZDNGWQDBDCZJDXBZGS"
"QQDDJCMBKZFFXMKDMDSYYSZCMLJDSYNSBRSKMKMPCKLGDBQTFZSWTFGGLY\
PLLJZHGJ[GYPZLTCSMCNBTJBQFKTHBYZGKPBBYMTDSSXTBNPDKLEYCJNYDD\
YKZDDHQHSDZSCTARLLTKZLGECLLKJLQJAQNBDKKGHPJTZQKSECSHALQFMMG\
JNLYJBBTMLYZXDCJPLDLPCQDHZYCBZSCZBZMSLJFLKRZJSNFRGJHXPDHYJY\
BZGDLQCSEZGXLBLGYXTWMABCHECMWYJYZLLJJYHLG[DJLSLYGKDZPZXJ"
"YYZLWCXSZFGWYYDLYHCLJSCMBJHBLYZLYCBLYDPDQYSXQZBYTDKYXJY[CN\
RJMPDJGKLCLJBCTBJDDBBLBLCZQRPPXJCJLZCSHLTOLJNMDDDLNGKAQHQHJGY\
KHEZNMSHRP[QQJCHGMFPRXHJGDYCHGHLYRZQLCYQJNZSQTKQJYMSZSWLCFQQQ\
XYFGGYPTQWLMCRNFKKFSYYLQBMQAMMMYXCTPSHCPTXXZZSMPHPSHMCLMLDQFY\
QXSZYYDYJZZHQPDSZGLSTJBCKBXYQZJSGPSXQZQZRQTBDKYXZK"
"HHGFLBCSMDLDGDZDBLZYYCXNNCSYBZBFGLZZXSWMSCCMQNJQSBDQSJTXXMBL\
TXZCLZSHZCXRQJGJYLXZFJPHYMZQQYDFQJJLZZNZJCDGZYGCTXMZYSCTLKPHT\
XHTLBJXJLXSCDQXCBBTJFQZFSLTJBTKQBXXJJLJCHCZDBZJDCZJDCPRNPQCJP\
FCZLCLZXZDMXMPHJSGZGSZZQLYLWTJPFSYASMCJBTZKYCWMYTCSJJLJCQLWZMA\
LBXYFBPNLSFHTGJWEJJXXGLLJSTGSHJQLZFKCGNNNSZFDEQ"
"FHBSAQTGYLBXMMYGSZLDYDQMJJRGBJTKGDHGKBLQKBDMBYLXWCXYTTYBKMRT\
JZXQJBHLMHMJJZMQASLDCYXYQDLQCAFYWYXQHZ";
std::string result;
int H = 0;
int L = 0;
int W = 0;
size_t stringlen = strlen(strChs);
for (int i = 0; i < stringlen; i++) {
H = (unsigned char)(strChs[i + 0]);
L = (unsigned char)(strChs[i + 1]);
if (H < 0xA1 || L < 0xA1) {
result += strChs[i];
continue;
}
else {
W = (H - 160) * 100 + L - 160;
}
if (W > 1600 && W < 5590) {
for (int j = 22; j >= 0; j--) {
if (W >= li_SecPosValue[j]) {
result += lc_FirstLetter[j];
i++;
break;
}
}
continue;
}
else {
i++;
W = (H - 160 - 56) * 94 + L - 161;
if (W >= 0 && W <= 3007)
result += ls_SecondSecTable[W];
else {
result += (char)H;
result += (char)L;
}
}
}
return result;
}
- 高亮模塊
高亮模塊處理的時候需要注意處理的方法
static void ColourPrintf(const std::string str) {
// 0-黑 1-藍 2-綠 3-淺綠 4-紅 5-紫 6-黃 7-白 8-灰 9-淡藍 10-淡綠
// 11-淡淺綠 12-淡紅 13-淡紫 14-淡黃 15-亮白
//顏色:前景色 + 背景色*0x10
//例如:字是紅色,背景色是白色,即 紅色 + 亮白 = 4 + 15*0x10
WORD color = 4 + 15 * 0x10;
WORD colorOld;
HANDLE handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(handle, &csbi);
colorOld = csbi.wAttributes;
SetConsoleTextAttribute(handle, color);
cout << str;
SetConsoleTextAttribute(handle, colorOld);
}
- 漢字搜索的時候處理直接進行匹配,進行
find
查找,直接分割成三個部分,使用高亮顯示關鍵字的部分 - 拼音全拼搜索的時候將關鍵字轉換成拼音全拼,然後將所查詢到的字段轉換成拼音全拼,進行截取,但是截取的不是轉換成全拼之後的,而是原有的字段
算法:就是講漢字一個一個轉換成拼音全拼和查找的位置相比較,如果長度達到了查找的位置就開始截取關鍵字,知道截取到漢字轉換爲拼音的數量和關鍵字轉換成全拼的數量相同的時候即可。
- 首字母查詢
同樣使用like
模糊查詢,將查詢到的字段和關鍵字都轉換爲漢字的首字母。