快速搜索神器
项目代码: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
模糊查询,将查询到的字段和关键字都转换为汉字的首字母。