基于C++和SQLite自主开发的SuperDocumentRetriever

项目简介

项目名称SuperDocumentRetriever(超级文档检索器)

开发环境Visual Studio 2017

项目描述本项目是一款实用型小工具,可以根据输入的关键字快速的在本地文件系统中检索出相关信息

项目特点 : 1. 使用 Sqlite 数据库 
       2. 自主实现简易版 RAII
     3. 可以输入汉字、拼音、拼音首字母,混合输入等多种方式检索信息
     4. 可以根据多种输入方式检索信息并对信息中的关键字进行高亮显示
     5. 使用单例模式
     6. 使用多线程及锁机制

项目背景

       首先呢,我先来说一下,我为什么要做这个项目。上了大学后,各种论文,报告,资料等等,真的是特别多特别多,这些东西就只能存储在我的电脑里面。但是东西一多,这也就引发了一个很难受的问题,那就是在查找的时候非常不方便,我知道有这么个文件,但是就是不知道它到底在那个盘下面,脑阔疼~~ 最开始我处理的办法就是我把资料都放在桌面上,这里我就不贴图了。,效果不用我说你也明白,桌面非常凌乱,更加难查找了。第二种办法就是我用windows文件系统中自带的搜索功能去检索信息,想必大家在搜索的时候是跟我一样的,如下图所示。

在这里插入图片描述
这时候当你在急需找某个文件时,出现这个状况,我相信你的感觉应该是跟我一样的吧,就不用吐槽了吧~~ 并且它不支持多种方式搜索,因为平常我们经常用拼音或者首字母搜索,因为这样更快点,但是它不支持,一直在那里缓冲。基于此,我就打算做这样一个超级文档检索器,不仅可以秒出结果,还可以支持多种搜索功能,高亮关键字等相关功能,做一个实用型小工具。

项目构思

项目逻辑构成
在这里插入图片描述

项目功能构成

在这里插入图片描述
项目模块简介

  1. 扫描模块:既然要快速搜索信息,那么肯定需要一个数据库来存储本地文件系统信息,故此我设计了一个扫描模块。本模块的作用就是将文件系统的信息存储到数据库中,并且,文件系统的增删在数据库中也要及时更新,要始终保持文件系统与数据库文件一致。
  2. 数据库管理模块:既然用到了数据库,就必然涉数据库的连接、关闭及 sql 语句的执行,故此设计数据库管理模块,处理相关问题。
  3. 数据管理模块:Windows文件系统不支持多功能搜索,故此我还得有一个数据管理模块,进行对数据库的增删及一些附加功能去检索信息。
  4. 公共模块:那么当然肯定会有一些与各个模块关联性都不强的东西,故此设计公共模块用来存放这些东西,避免逻辑混乱。
  5. 监控模块:这个模块实我后来加上的,主要是为了提高系统性能设计的,下面我会详细介绍。

项目主体

扫描模块

在扫描模块,首先,我们得从本地文件系统中搜索到文件。那么就必须遍历本地文件系统,当然对于目录而言,还得递归遍历目录下的所有文件。在这里我百度之后用了如下接口:

long _findfirst( char *filespec, struct _finddata_t *fileinfo )
int _findnext( long handle, struct _finddata_t *fileinfo )
int _findclose( long handle )

具体目录遍历函数如下:

//遍历指定路径下的文件信息
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);
}

其次,最重要的就是将本地文件系统数据同步到数据库中,然后进行相关处理。不能忽视的就是本地新增数据,删除数据、重命名数据,数据库必须同步。不然有可能文件系统刚刚新增的数据在数据库中搜不到,文件系统删除的数据在数据库中还能搜到,这样就不合理了。那么这时候我就用了一种巧妙的算法,去处理这个问题。

  1. 先将本地数据与数据库中数据放入 set 集合中,集合自带排序功能,经排序后,有一个极大的好处,那就是两个集合中的数据按道理来说是相等的。
  2. 循环遍历集合,如果本地数据 = 数据库数据,则说明数据一致跳过。
  3. 若 本地数据 < 数据库数据 则说明本地有,数据库没有,要新增数据。
  4. 若 本地数据 > 数据库数据 则说明本地没有,数据库有,要删除数据。

通过这种简单的算法,我们就很容易解决了这个问题。有没有很聪明~~

void DocScanManager::Scan(const std::string& path) {
	//通过我们path路径来取得目录下面的数据和数据库中的数据
	std::set<std::string>localset;
	std::vector<std::string>localdirs;
	std::vector<std::string>localfiles;
	//得到本地的文件和目录
	DirectoryList(path, localfiles, localdirs);
	localset.insert(localdirs.begin(), localdirs.end());
	localset.insert(localfiles.begin(), localfiles.end());

	//得到数据库中的目录和文件
	std::set<std::string> dbset;
	DataManager::GetInstance()->GetDocs(path, dbset);
	auto localit = localset.begin();
	auto dbit = dbset.begin();

	while (localit != localset.end() && dbit != dbset.end()) {
		//本地数据与数据库中数据一致,跳过
		if (*localit == *dbit) {
			localit++;
			dbit++;
		}
		//本地有,数据库中没有,新增数据
		else if (*localit < *dbit) {
			DataManager::GetInstance()->InsertDocs(path, *localit);
			localit++;
		}
		//本地没有,数据库有,删除数据
		else if (*localit > *dbit) {
			DataManager::GetInstance()->DeleteDoc(path, *dbit);
			dbit++;
		}
	}
	//如果没有比较完就直接进行添加或者删除
	while (localit != localset.end()) {
		//新增数据
		DataManager::GetInstance()->InsertDocs(path, *localit);
		localit++;
	}
	while (dbit != dbset.end()) {
		DataManager::GetInstance()->DeleteDoc(path, *dbit);
		dbit++;
	}
	//遍历子目录进行数据的导入
	for (const auto& dirs : localdirs) {
		std::string subpath = path;
		subpath += "\\";
		subpath += dirs;
		Scan(subpath);
	}
}

数据库管理模块

数据库也是本系统非常重要的一部分,由于小编对MySQL数据库比较熟悉,本打算用它,但是最后经过仔细考量与调研,我采用了SQLite数据库,关于SQLite数据库的介绍大家可以参考sqlite菜鸟教程,如果要下载的话,可以去官网`sqlite官网下载。在这里,我就简单的说一下我为什么选择 sqlite 。

  1. 首先 sql 语句没有很大的差别,因为我只需要增删查的功能,语句完全一样,不需要我重新学习。
  2. sqlite 是一个轻量级的数据库,它是一个基于文件型的数据库,整个数据库都包含在磁盘上的一个文件中,因此它有很好的迁移性。
  3. 在很多情况下,需要频繁直接读/写磁盘文件的应用,都很适合转为使用 SQLite ,可以得益于 SQLite 使用 SQL 带来的功能性和简洁性。这刚好符合这个项目的特点。
  4. 因为本系统是一个工具型的项目,MySQL数据库可走网络服务,而本项目只需在本地检索即可,故此若使用MySQL数据库有点大材小用。
  5. 最重要的一点在于这是一个实用型的小工具,如果要用MySQL用户就得下载MySQL,配置环境,不仅占内存,而且很复杂。而SQLite就简单多了,只需要引入相关库文件即可使用。

看过文档之后会发现,有这样一个接口 sqlite3_get_table(),它其实就是返回数据库信息,返回的是一个数组,必须要调用 sqlite3_free_table()这个接口去释放它开好的这块空间,不然就会造成内存泄漏问题。 要不是我细心,我真的的不知道还有这么个东西。故此,我将sqlite相关操作封装成类,并对这个问题自主实现了一个简易版的 RAII 去管理这块内存,避免造成内存泄漏的问题。

//使用ARII将GetTable封装,便于对内存进行管理,自动释放返回的二维数组
class AutoGetTable {
public:
	AutoGetTable(SqliteManager* dbmagar, const std::string sql, int & row, int & col, char**& ppRet)
		:_dbMag(dbmagar), _ppVlaue(0)
	{
		_dbMag->GetTable(sql, row, col, ppRet);
		_ppVlaue = ppRet;
	}
	virtual ~AutoGetTable() {
		if (_ppVlaue) {
			sqlite3_free_table(_ppVlaue);
		}
	}

private:
	SqliteManager* _dbMag;
	char ** _ppVlaue;
};

公共模块

在很多时候,我们都喜欢用拼音或者首字母进行搜索,因为这样简单快捷。故此,我不仅要实现中文汉字搜索,还要实现拼音搜索,首字母搜索。在这个模块中我加入关于中文转拼音,中文转拼音首字母及高亮颜色设置等相关内容。 至于具体如何操作,这就涉及编码相关问题,哈哈我也不会,所以只能在网上搜索借用别人写的代码,解决这个问题。

static int ChineseConvertPy(const std::string& dest_chinese, std::string& out_py) {
	const int spell_value[] = { -20319, -20317, -20304, -20295, -20292, -20283, -20265, -20257, -20242, -20230, -20051, -20036, -20032, -20026,
		-20002, -19990, -19986, -19982, -19976, -19805, -19784, -19775, -19774, -19763, -19756, -19751, -19746, -19741, -19739, -19728,
		-19725, -19715, -19540, -19531, -19525, -19515, -19500, -19484, -19479, -19467, -19289, -19288, -19281, -19275, -19270, -19263,
		-19261, -19249, -19243, -19242, -19238, -19235, -19227, -19224, -19218, -19212, -19038, -19023, -19018, -19006, -19003, -18996,
		-18977, -18961, -18952, -18783, -18774, -18773, -18763, -18756, -18741, -18735, -18731, -18722, -18710, -18697, -18696, -18526,
		-18518, -18501, -18490, -18478, -18463, -18448, -18447, -18446, -18239, -18237, -18231, -18220, -18211, -18201, -18184, -18183,
		-18181, -18012, -17997, -17988, -17970, -17964, -17961, -17950, -17947, -17931, -17928, -17922, -17759, -17752, -17733, -17730,
		-17721, -17703, -17701, -17697, -17692, -17683, -17676, -17496, -17487, -17482, -17468, -17454, -17433, -17427, -17417, -17202,
		-17185, -16983, -16970, -16942, -16915, -16733, -16708, -16706, -16689, -16664, -16657, -16647, -16474, -16470, -16465, -16459,
		-16452, -16448, -16433, -16429, -16427, -16423, -16419, -16412, -16407, -16403, -16401, -16393, -16220, -16216, -16212, -16205,
		-16202, -16187, -16180, -16171, -16169, -16158, -16155, -15959, -15958, -15944, -15933, -15920, -15915, -15903, -15889, -15878,
		-15707, -15701, -15681, -15667, -15661, -15659, -15652, -15640, -15631, -15625, -15454, -15448, -15436, -15435, -15419, -15416,
		-15408, -15394, -15385, -15377, -15375, -15369, -15363, -15362, -15183, -15180, -15165, -15158, -15153, -15150, -15149, -15144,
		-15143, -15141, -15140, -15139, -15128, -15121, -15119, -15117, -15110, -15109, -14941, -14937, -14933, -14930, -14929, -14928,
		-14926, -14922, -14921, -14914, -14908, -14902, -14894, -14889, -14882, -14873, -14871, -14857, -14678, -14674, -14670, -14668,
		-14663, -14654, -14645, -14630, -14594, -14429, -14407, -14399, -14384, -14379, -14368, -14355, -14353, -14345, -14170, -14159,
		-14151, -14149, -14145, -14140, -14137, -14135, -14125, -14123, -14122, -14112, -14109, -14099, -14097, -14094, -14092, -14090,
		-14087, -14083, -13917, -13914, -13910, -13907, -13906, -13905, -13896, -13894, -13878, -13870, -13859, -13847, -13831, -13658,
		-13611, -13601, -13406, -13404, -13400, -13398, -13395, -13391, -13387, -13383, -13367, -13359, -13356, -13343, -13340, -13329,
		-13326, -13318, -13147, -13138, -13120, -13107, -13096, -13095, -13091, -13076, -13068, -13063, -13060, -12888, -12875, -12871,
		-12860, -12858, -12852, -12849, -12838, -12831, -12829, -12812, -12802, -12607, -12597, -12594, -12585, -12556, -12359, -12346,
		-12320, -12300, -12120, -12099, -12089, -12074, -12067, -12058, -12039, -11867, -11861, -11847, -11831, -11798, -11781, -11604,
		-11589, -11536, -11358, -11340, -11339, -11324, -11303, -11097, -11077, -11067, -11055, -11052, -11045, -11041, -11038, -11024,
		-11020, -11019, -11018, -11014, -10838, -10832, -10815, -10800, -10790, -10780, -10764, -10587, -10544, -10533, -10519, -10331,
		-10329, -10328, -10322, -10315, -10309, -10307, -10296, -10281, -10274, -10270, -10262, -10260, -10256, -10254 };

	// 395个字符串,每个字符串长度不超过6
	static const char spell_dict[396][7] = { "a", "ai", "an", "ang", "ao", "ba", "bai", "ban", "bang", "bao", "bei", "ben", "beng", "bi", "bian", "biao",
		"bie", "bin", "bing", "bo", "bu", "ca", "cai", "can", "cang", "cao", "ce", "ceng", "cha", "chai", "chan", "chang", "chao", "che", "chen",
		"cheng", "chi", "chong", "chou", "chu", "chuai", "chuan", "chuang", "chui", "chun", "chuo", "ci", "cong", "cou", "cu", "cuan", "cui",
		"cun", "cuo", "da", "dai", "dan", "dang", "dao", "de", "deng", "di", "dian", "diao", "die", "ding", "diu", "dong", "dou", "du", "duan",
		"dui", "dun", "duo", "e", "en", "er", "fa", "fan", "fang", "fei", "fen", "feng", "fo", "fou", "fu", "ga", "gai", "gan", "gang", "gao",
		"ge", "gei", "gen", "geng", "gong", "gou", "gu", "gua", "guai", "guan", "guang", "gui", "gun", "guo", "ha", "hai", "han", "hang",
		"hao", "he", "hei", "hen", "heng", "hong", "hou", "hu", "hua", "huai", "huan", "huang", "hui", "hun", "huo", "ji", "jia", "jian",
		"jiang", "jiao", "jie", "jin", "jing", "jiong", "jiu", "ju", "juan", "jue", "jun", "ka", "kai", "kan", "kang", "kao", "ke", "ken",
		"keng", "kong", "kou", "ku", "kua", "kuai", "kuan", "kuang", "kui", "kun", "kuo", "la", "lai", "lan", "lang", "lao", "le", "lei",
		"leng", "li", "lia", "lian", "liang", "liao", "lie", "lin", "ling", "liu", "long", "lou", "lu", "lv", "luan", "lue", "lun", "luo",
		"ma", "mai", "man", "mang", "mao", "me", "mei", "men", "meng", "mi", "mian", "miao", "mie", "min", "ming", "miu", "mo", "mou", "mu",
		"na", "nai", "nan", "nang", "nao", "ne", "nei", "nen", "neng", "ni", "nian", "niang", "niao", "nie", "nin", "ning", "niu", "nong",
		"nu", "nv", "nuan", "nue", "nuo", "o", "ou", "pa", "pai", "pan", "pang", "pao", "pei", "pen", "peng", "pi", "pian", "piao", "pie",
		"pin", "ping", "po", "pu", "qi", "qia", "qian", "qiang", "qiao", "qie", "qin", "qing", "qiong", "qiu", "qu", "quan", "que", "qun",
		"ran", "rang", "rao", "re", "ren", "reng", "ri", "rong", "rou", "ru", "ruan", "rui", "run", "ruo", "sa", "sai", "san", "sang",
		"sao", "se", "sen", "seng", "sha", "shai", "shan", "shang", "shao", "she", "shen", "sheng", "shi", "shou", "shu", "shua",
		"shuai", "shuan", "shuang", "shui", "shun", "shuo", "si", "song", "sou", "su", "suan", "sui", "sun", "suo", "ta", "tai",
		"tan", "tang", "tao", "te", "teng", "ti", "tian", "tiao", "tie", "ting", "tong", "tou", "tu", "tuan", "tui", "tun", "tuo",
		"wa", "wai", "wan", "wang", "wei", "wen", "weng", "wo", "wu", "xi", "xia", "xian", "xiang", "xiao", "xie", "xin", "xing",
		"xiong", "xiu", "xu", "xuan", "xue", "xun", "ya", "yan", "yang", "yao", "ye", "yi", "yin", "ying", "yo", "yong", "you",
		"yu", "yuan", "yue", "yun", "za", "zai", "zan", "zang", "zao", "ze", "zei", "zen", "zeng", "zha", "zhai", "zhan", "zhang",
		"zhao", "zhe", "zhen", "zheng", "zhi", "zhong", "zhou", "zhu", "zhua", "zhuai", "zhuan", "zhuang", "zhui", "zhun", "zhuo",
		"zi", "zong", "zou", "zu", "zuan", "zui", "zun", "zuo" };

	try {
		// 循环处理字节数组
		const int length = dest_chinese.length();
		for (int j = 0, chrasc = 0; j < length;) {
			// 非汉字处理
			if (dest_chinese.at(j) >= 0 && dest_chinese.at(j) < 128) {
				out_py += dest_chinese.at(j);
				// 偏移下标
				j++;
				continue;
			}

			// 汉字处理
			chrasc = dest_chinese.at(j) * 256 + dest_chinese.at(j + 1) + 256;
			if (chrasc > 0 && chrasc < 160) {
				// 非汉字
				out_py += dest_chinese.at(j);
				// 偏移下标
				j++;
			}
			else {
				// 汉字
				for (int i = (sizeof(spell_value) / sizeof(spell_value[0]) - 1); i >= 0; --i) {
					// 查找字典
					if (spell_value[i] <= chrasc) {
						out_py += spell_dict[i];
						break;
					}
				}
				// 偏移下标 (汉字双字节)
				j += 2;
			}
		} // for end
	}
	catch (std::exception _e) {
		std::cout << _e.what() << std::endl;
		return -1;
	}
	return 0;
}

//中文汉字转拼音首字母
static std::string ChineseConvertPyInit(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
	};
	const 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"
	};
	const 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 = 5 + 0 * 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);
}

数据处理模块

对于这一模块当然也是整个项目中的重中之重了,其中包含了数据库的增删获取以及我们的核心功能搜索及高亮功能。

  1. 搜索其实并没有很复杂,就是利用我们输入的关键字,检索相应的内容。在这里,我的多功能搜索就比较强大了,不仅可以支持中文汉字搜索,还支持拼音搜索以及拼音首字母搜索,当然还支持混合搜索,既有汉字又有拼音都可以进行搜索,听起来是不是很厉害的样子,其实并不难,就是简单的一个 select 的 like 模糊匹配查找而已。因为中文转拼音,中文转拼音首字母已经是现成的了,所以操作起来就方便多了。 具体代码如下:
//根据关键字在数据库中检索
void DataManager::Search(std::string& key, std::vector<std::pair<std::string, std::string>>&doc_paths) {
	//使用关键字得到数据,使用一个pair对组来存储数据,第一个是name,第二个是path
	
	char ch[256];
	memset(ch, 0, sizeof(ch));
	std::string pinyin;
	ChineseConvertPy(key, pinyin);
	std::string pinyin_initials = ChineseConvertPyInit(key.c_str());
	sprintf(ch, "select doc_name,doc_path from doc_db where doc_name_pinyin \
								 like '%%%s%%' or doc_name_initials like '%%%s%%'COLLATE NOCASE;", pinyin.c_str(),pinyin_initials.c_str());
	//使用AutoGetTable来得到数据
	std::string sql(ch);
	int row;
	int col;
	char ** ppRet;
	//得到数据
	AutoGetTable agt(&_sqlmagar, sql, row, col, ppRet);
	for (int i = 1; i <= row; i++) {
		//i*2+0和i*2+1 这两个位置就是我们需要的位置
		std::pair<std::string, std::string> p;
		p.first = ppRet[i * 2];
		p.second = ppRet[i * 2 + 1];
		doc_paths.push_back(p);
	}
}
  1. 高亮的处理在这里还是比较复杂的。首先得根据我们输入的关键字找到所有相关的数据信息,然后将数据中和关键字匹配的内容高亮起来,这个其实并不容易。
    思路:我们可以将检索到的所有每条数据分为三部分,关键字左边部分,关键字以及关键字右边部分。然后关键字左边部分和右边的部分都可以原样输出,然后将关键字高亮就可以了。但是这远比想象的要复杂。如果是输入的关键字是中文,我们直接可以按照字符串分割完成这个任务,但是假如输入的是拼音全拼或者是拼音首字母那么到底如何对应到相应的汉字上,并且要把对应的汉字高亮起来,要知道每个汉字对应的拼音可是不固定的呢,这你怎么分割。这是一个不容易的问题。经过我苦思苦思再苦思,最终以一种比较稍微复杂一点的算法解决这个问题。

在这里插入图片描述
例如上图中,输入关键字为拼音 gaoliang ,现在要高亮对应文件名称中的 “高亮” 二字。具体的思路如下:

  1. 首先我们可以根据输入的关键字找出在文件名称对应的拼音全拼中的起始位置,再加上关键字的长度就可以计算出在拼音全拼中的终止位置了,标记出来。
  2. 接下来,我们需要两个变量,一个从文件名称起始位置开始走,一个从所对应的拼音全拼串起始位置开始走,当拼音全拼走到标记的起始位置和标记的结束位置时,也就意味着汉字走到了应该被高亮的起始位置和终止位置。
  3. 当然在这里有非常重要的一点就是,每个汉字对应的拼音字母的个数是不一样的,走的时候要根据汉字来走,走一个汉字,在拼音串中要走该汉字所对应的的字母的个数,这样才能保证走的规律是正确的。
  4. 当然还有重要的一点就是,有可能汉字中包含了数字或者字母,这就又尴尬了,那么这样一来,就会打乱这种节奏。不过还好GBK编码对汉字的编码是不一样的,那么这样的话,我们分别对汉字和ASCII码字母处理就可以完全搞定这个问题了。如果是拼音首字母和拼音全拼是一样的逻辑,故此,这个问题到这里就解决了。具体代码如下:
//设置关键字高亮
void DataManager::SetHighLight(const std::string& doc_name, const std::string& key, std::string& prefix, std::string& highlight, std::string& suffix) {
	
	//直接利用中文搜索高亮关键字
	{
		int pos = doc_name.find(key);
		if (pos != std::string::npos) {
			prefix = doc_name.substr(0, pos);
			highlight = key;
			suffix = doc_name.substr(pos + key.size(), std::string::npos);

			return;
		}
	}
	//输入拼音全拼,高亮对应汉字
	{
		std::string doc_pinyin;
		ChineseConvertPy(doc_name,doc_pinyin);
		std::string doc_key;
		ChineseConvertPy(key, doc_key);
		
		int wd_index = 0,wd_start = 0;
		int wd_len = 0;
		int py_index = 0;
		int py_start = doc_pinyin.find(doc_key);
		int py_end = py_start + doc_key.size();
		
		if (py_start != std::string::npos) {
			while (py_index < py_end) {
				if (py_index == py_start) {
					wd_start = wd_index;
				}
				//输入的是ASCLL
				if (doc_name[wd_index] >= 0 && doc_name[wd_index] <= 127) {
					py_index++;
					wd_index++;
				}
				//汉字
				else {
					char chinese[3] = { 0 };
					chinese[0] = doc_name[wd_index];
					chinese[1] = doc_name[wd_index + 1];
					wd_index += 2;

					std::string chineses;
					ChineseConvertPy(chinese, chineses);
					py_index += chineses.size();

				}
			}
			wd_len = wd_index - wd_start;
			prefix = doc_name.substr(0, wd_start);
			highlight = doc_name.substr(wd_start,wd_len);
			suffix = doc_name.substr(wd_start+wd_len, std::string::npos);

			return;
		}
	}

	//输入首字母
	{
		std::string doc_name_initials = ChineseConvertPyInit(doc_name.c_str());
		std::string doc_key_initials = ChineseConvertPyInit(key.c_str());
		
		int wd_index = 0, wd_start = 0;
		int wd_len = 0;
		int py_inials_index = 0;
		int py_inials_start = doc_name_initials.find(doc_key_initials);
		int py_end = py_inials_start + doc_key_initials.size();

		if (py_inials_start != std::string::npos) {
			while (py_inials_index < py_end) {
				if (py_inials_index == py_inials_start) {
					wd_start = wd_index;
				}
				//输入的是ASCLL
				if (doc_name[wd_index] >= 0 && doc_name[wd_index] <= 127) {
					py_inials_index++;
					wd_index++;
				}
				//汉字
				else {
					char chinese[3] = { 0 };
					chinese[0] = doc_name[wd_index];
					chinese[1] = doc_name[wd_index + 1];
					wd_index += 2;

					std::string chineses;
					ChineseConvertPy(chinese, chineses);
					py_inials_index ++;

				}
			}
			wd_len = wd_index - wd_start;
			prefix = doc_name.substr(0, wd_start);
			highlight = doc_name.substr(wd_start, wd_len);
			suffix = doc_name.substr(wd_start + wd_len, std::string::npos);

			return;
		}
	}

}

监控模块

在最开始时,我并没有加上这个模块,但是这就存在了一个比较大的问题那就是假如本地新增或者删除数据,数据库中必须保持一致,不然就会新增的搜不到,删除的却没删能搜到,这样是非常不合理的。最开始我的做法是运行程序后,专门开启一个线程让它循环扫描文件系统,每隔几秒中扫描一次文件系统,更新一下数据库,但是这就很尴尬了,文件系统中的数据量比较小的时候,搜索结果还是秒出,但是当文件系统中的数据量非常大时,这就尴尬了,每次扫描要花的很长的时间,性能太过低下。考虑到这个问题,我最终才想要添加这个监控模块,专门开启一个线程去监控文件系统中的实时变化,这样就不用再扫描一遍,大大的提高了性能。当然监控的相关代码,我也是网上百度修改的,具体情况如下:

void get_dir_path(std::string& s) {
	int pos = -1;
	for (int i = s.size() - 1; i >= 0; i--) {
		if (s[i] == '\\') {
			pos = i;
			break;
		}
	}
	if (pos != -1) {
		s = s.substr(0, pos);
	}
}

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 = (TCHAR*)_T("C:\\Users\\73117\\Desktop\\leihaoa");
	std::string s = "C:\\Users\\73117\\Desktop\\leihaoa";
	//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;

	while (true) {
		if (ReadDirectoryChangesW(dirHandle, &notify, 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();
				}
			}
			if ((pnotify->Action == FILE_ACTION_ADDED) | (pnotify->Action == FILE_ACTION_REMOVED) | (pnotify->Action == FILE_ACTION_RENAMED_OLD_NAME)) {

				std::string dirpath = s + "\\" + file_name;
				get_dir_path(dirpath);
				//	cout << dirpath << endl;
					//返回上一层的目录
				DocScanManager::GetInstance()->MonitorScan(dirpath);
				//cout << "扫描模块的地址monitor" << ScanManager::CreateIntance() << endl;
				//cout << "DirPath:" << dirpath << endl;
			}
		}
	}
	CloseHandle(dirHandle);
}

项目演示

  • 输入中文汉字查询

在这里插入图片描述

  • 输入拼音查询

在这里插入图片描述

  • 输入首字母查询

在这里插入图片描述

项目总结

优点

  • 支持多功能检索。汉字检索、拼音检索、拼音首字母检索、混合检索
  • 支持高亮显示关键字
  • 自主实现RAII
  • 单例模式
  • sqlite数据库的应用
  • 应用C++11的线程及锁机制

不足

  • 对于生僻字无法识别
  • 对于单个字符,误搜概率较大
  • 严重依赖GBK编码方式

最后在这里附上项目源码的GitHub链接SuperDocumentRetriever

如果觉得还不错的话,记得给小编点赞哦~~

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
基于标准库的C语言SQLite3常用功能封装和使用可以包括以下几个方面: 1. 连接数据库:通过调用`sqlite3_open_v2`函数来创建和打开一个SQLite3数据库文件,返回一个数据库对象。 2. 执行SQL语句:使用`sqlite3_exec`函数可以执行SQL语句,包括创建表、插入数据、更新数据、删除数据等操作。 3. 查询数据:使用`sqlite3_prepare_v2`函数来准备一个SQL语句,然后通过`sqlite3_step`函数来遍历查询结果,使用`sqlite3_column_xxx`函数获取每个字段的值。最后使用`sqlite3_finalize`函数来完成查询。 4. 绑定参数:对于需要动态参数的SQL语句,可以使用`sqlite3_bind_xxx`函数来绑定参数值,然后通过`sqlite3_step`函数执行SQL语句。 5. 错误处理:通过调用`sqlite3_errmsg`函数可以获取错误信息,用于处理和调试错误。 6. 事务处理:使用`sqlite3_exec`函数执行`BEGIN`、`COMMIT`和`ROLLBACK`语句来实现事务操作,确保原子性和一致性。 7. 数据库关闭:通过调用`sqlite3_close_v2`函数来关闭数据库连接,并释放相关资源。 需要注意的是,SQLite3是一个嵌入式数据库,可以直接将其源代码编译到项目中,无需单独安装。使用C语言与SQLite3进行交互相对较底层,需要手动管理内存和资源,因此在使用过程中需要注意内存泄漏和错误处理等问题。为了方便使用,可以封装一些常用功能函数和结构体,以提高开发效率和代码重用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值