在任何操作系统中,搜索工具都是必不可少的,不管我们多么认真的对文件进行整理,当文件数量非常多时,都可能需要我们花很长时间来找某个文件。搜索工具可以让我们从大量文件中快速找到我们所需文件的位置,大大节省了查找时间。
文件搜索工具的功能如此实用,那它是用什么方法从那么多的文件中来找我们想要的一个特定文件的呢?我们到底可以用什么方法来实现文件搜索的功能呢?
带着疑惑和好奇,我通过从网上找各种资源终于实现了一个低配版的文件搜索工具。实现这个项目同时让我巩固了自己所学的C++基础知识。
1、windows系统自带搜索框
windows中文件夹框下的默认搜索是搜索时再进行暴力遍历查找,非常的慢。
2、文件搜索神器 Everything
windows下有一个搜索神器叫Everything,是将文档信息检索以后,提前存储到数据库,查找时在数据库进行搜索,速度快了很多。
Everything实现原理: Everything并不扫描整个磁盘,只是读取磁盘上的USN日志,并建立索引,提前存储到数据库,查找时在数据库进行搜索,所以速度飞快。
但因此缺点也明显:
(1) 只支持NTFS格式的分区,因为USN日志是NTFS专有的。在FAT、FAT32格式分区上无法使用 Everything。
(2) 只索引文件名称、日期和大小,不索引文件内容和附加属性。
根据Everything的官网所说,它1分钟可以索引100万个文件。48万多个文件,建立索引需要的时间也只有几秒,索引无需逐一扫描硬盘文件,而是直接读取NTFS文件系统的USN日志,Everything由于核心原理建立在NTFS的底层机制上,NTFS文件系统中的 USN 日志记录了系统对NTFS分区中的文件所做的所有更改。对于每一卷,NTFS 都使用 USN 日志来跟踪有关添加、删除和修改的文件的信息。直接读取NTFS文件系统的USN日志,是能做到很快的,但就只能按文件名来处理。如果要做内容的索引,那就不是一秒两秒的事了,我们还要考虑文件格式。也就是说你如果需要搜寻你所有的分区,那么你所有的分区都需要是NTFS格式的,这是由软件的工作原理决定的。
Everything同时也提供了Everything-SDK供开发者使用。 SDK(Software Development Kit 软件开发工具包)。下面一段代码简单演示其使用:
#include
#include "Everything.h"
//通过引入lib和头文件也可以使用
//#pragma comment(lib, "Everything32.lib")
using namespace std;
int main()
{
//A字符 W宽字符
Everything_SetSearchA("C和指针.pdf");
Everything_QueryA(TRUE);
cout << Everything_GetNumResults() <<endl;
for (int i = 0; i<Everything_GetNumResults(); i++)
{
cout << Everything_GetResultFileNameA(i) << " ------> " << Everything_GetResultPathA(i) << endl;
}
return 0;
}
1、windows系统自带搜索框和Everything不支持拼音全拼搜索,QQ的搜索框支持拼音全拼搜索
(1) windows系统自带搜索框搜索结果:
(2) Everything的搜索结果:
(3) QQ的搜索框搜索结果:
2、这三个搜索工具都支持关键字高亮
3、windows系统自带搜索框和Everything不支持拼音首字母搜索,QQ的搜索框支持拼音首字母搜索
1、项目实现目标
(1) 支持文档普通搜索
(2) 支持拼音全拼搜索
(3) 支持拼音首字母搜索
(4) 支持关键字搜索
(5) 支持搜索关键字高亮
(6) 设计一个简洁的dos用户界面
通过对当前已有的文件搜索工具的对比,我打算开发一个能够支持拼音全拼搜索、拼音首字母搜索和搜索关键字高亮的搜索小工具。
2、开发环境
(1) 编译器 : VS2013
(2) 编程语言 : C++
(3) 数据库 : sqlite3
3、项目涉及知识点
(1) 数据库操作:(创建数据库,创建表,插入数据,删除数据,创建索引,查询数据(条件查询、 模糊查询))
(2) 单例设计模式
(3) 多线程
(4)静态库、动态库
(5) 日志
(6)汉字与拼音的转换
4、项目框架
5、代码框架
公共模块:Common.h
系统工具模块:Sysutil.h Sysutil.cpp
数据管理模块:DataManager.h DataManager.cpp
扫描管理模块:ScanManager.h ScanManager.cpp
打印界面模块:Sysframe.h Sysframe.cpp
系统驱动模块:DocFastSearchTool.cpp
1、系统工具模块(Sysutil.h Sysutil.cpp)
1.1、目录扫描
主要涉及函数:
//_finddata_t是用来存储文件各种信息的结构体,使用这个结构体要引用的头文件为#include
struct _finddata_t
{
unsigned attrib;
time_t time_create;
time_t time_access;
time_t time_write;
_fsize_t size;
char name[_MAX_FNAME];
};
//功能是搜索与指定的文件名称匹配的第一个实例,若成功则返回第一个实例的句柄,否则返回-1L。
long _findfirst( char *filespec, struct _finddata_t *fileinfo );
//搜索与_findfirst函数提供的文件名称匹配的下一个实例,若成功则返回0,否则返回-1
int _findnext( long handle, struct _finddata_t *fileinfo );
//_findclose用于释放由_findfirst分配的内存
int _findclose( long handle );
代码中对应函数:
void DirectoryList(const string &path, vector<string> &subfile, vector<string> &subdir);
这个函数主要思想:
1.2、日志模块
日志模块就是自己弄一个打印函数,当操作成功或者失败的时候打印相关信息,把关键信息记录下来。在项目中,我关注的相关信息主要是文件名、行号、所在函数、对应的错误操作信息等等。
#define TRACE_LOG(...) \
__TraceDebug(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);
#define ERROR_LOG(...) \
__ErrorDebug(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);
1.3、汉字转拼音全拼
网上有许多汉字转拼音全拼的算法,我的算法就是从网上找的。
代码中对应函数:
string ChineseConvertPinYinAllSpell(const std::string& dest_chinese);
在我的项目中只是针对GB2312中的汉字做转换,GB2312编码包括符号、数字、字母、日文、制表符等,当然最主要的部分还是中文。在GB2312中,一个字母或者数字占一个字节,一个汉字则占了两个字节,简体中文的编码范围从B0A1一直到F7FE,完整编码表可以参考http://ash.jp/code/cn/gb2312tbl.htm。
区位码(引用自百度百科):
GB2312是一个简体中文字符集,由6763个常用汉字和682个全角的非汉字字符组成。其中汉字根据使用的频率分为两级。一级汉字3755个,二级汉字3008个。由于字符数量比较大,GB2312采用了二维矩阵编码法对所有字符进行编码。首先构造一个94行94列的方阵,对每一行称为一个“区”,每一列称为一个“位”,然后将所有字符依照下表的规律填写到方阵中。这样所有的字符在方阵中都有一个唯一的位置,这个位置可以用区号、位号合成表示,称为字符的区位码。如第一个汉字“啊”出现在第16区的第1位上,其区位码为1601。因为区位码同字符的位置是完全对应的,因此区位码同字符之间也是一一对应的。这样所有的字符都可通过其区位码转换为数字编码信息。GB2312字符的排列分布情况见下表。
GB2312字符在计算机中存储是以其区位码为基础的,其中汉字的区码和位码分别占一个存储单元,每个汉字占两个存储单元。由于区码和位码的取值范围都是在1-94之间,这样的范围同西文的存储表示冲突。例如汉字‘珀’在GB2312中的区位码为7174,其两字节表示形式为71,74;而两个西文字符‘GJ’的存储码也是71,74。这种冲突将导致在解释编码时到底表示的是一个汉字还是两个西文字符将无法判断。
为避免同西文的存储发生冲突,GB2312字符在进行存储时,通过将原来的每个字节第8bit设置为1同西文加以区别,如果第8bit为0,则表示西文字符,否则表示GB2312中的字符。实际存储时,采用了将区位码的每个字节分别加上A0H(160)的方法转换为存储码,计算机存储规则是此编码的补码,而且是位码在前,区码在后。例如汉字‘啊’的区位码为1601,其存储码为B0A1H,其转换过程为:16的16进制表示为10H
根据以上规律,我们取得的字符若在[0, 160]之间就可以认为是英文,以此为依据便可将中文字符和英文字符区分开来。
我所用的算法的设计思想就是:先将所有拼音按序保存在一个二维字符数组中(spell_dict),其实所有拼音加起来并不是太多。正好GB2312中的汉字顺序就是按拼音顺序所排,然后把这些拼音所对应的第一个汉字(会有拼音一样的汉字)的存储码保存在一个一维数组中(spell_value),正好是一个递增的数组。然后我们对输入的字符串中的字符依次判断,如果大于等于0且小于128则表示是一个英文字符,直接加到返回结果后面,然后将查找下标后移一位;如果小于0则表示是中文字符,这个时候我们需要将该字节左移8位,然后加上他的下一个字节得到中文字符对应的存储码。然后我们对spell_value从后往前遍历,考虑到会有拼音一样的汉字,当遍历的值等于或者第一次出现小于查找汉字的存储码时,说明我们找到了,将对应拼音加到返回结果后面,然后将查找下标后移两位。
1.4、汉字转拼音首字母全拼
受到汉字转拼音全拼的启发,我把汉字转拼音全拼的代码改成了汉字转拼音首字母。思路非常简单,就是在找到一个汉字对应的拼音时,只在返回结果后面加上拼音的第一个字母。因为我们要的只是拼音首字母,所以1.3中的两个数组中的元素并不需要把所有的拼音列出来,我们只需要把分别以a、b、…y、z为首字母的拼音的第一个填入数组就可以了,当我们按1.3中的方法遍历时,遇到的等于或者第一个小于所查找汉字存储码的拼音的首字母就是想要的结果。
std::string ChineseConvertPinYinInitials(const std::string& name);
1.5、控制界面颜色
这一部分是封装一些函数,实现背景色和打印字符的颜色的控制。
2、数据管理模块(DataManager.h DataManager.cpp)
2.1、数据库封装
class SqliteManager//数据库的封装类
{
public:
SqliteManager();
~SqliteManager();
public:
void Open(const string &path);//打开数据库
void Close();//关闭数据库
void ExecuteSql(const string &sql);//执行数据库语句
void GetResultTable(const string &sql, int &row, int &col, char **&ppRet);//获得查询结果
private:
sqlite3 *m_db;
};
这个类是对数据库基本操作的封装,包括打开数据库、关闭数据、执行数据库语句、获得查询结果,在这个类中,有一个sqlite3 *m_db
,用来获得打开的数据库对象,便于操作。在对数据库操作之前,我们需要引入sqlite所对应的头文件和静态库#include "sqlite\sqlite3.h"
、#pragma comment(lib, "./sqlite/sqlite3.lib")
,这样才能对sqlite3中提供的接口进行使用。项目中用到了如下接口:sqlite3_open();
在指定路径打开数据库,sqlite3_close)
关闭数据库,sqlite3_exec();
执行指定的数据库语句,sqlite3_get_table();
执行指定的数据库语句,并且返回查询的数据结果(参数1:打开数据库时得到的指针;参数2:一条sql语句,跟sqlite3_exec中一样;参数3:查询的数据结果,他是一个指针数组,内存分布为:字段名称,后面是紧接着是每个字段的值;参数4:查询到的数据的行数;参数5:查询到的数据的列数;参数6:错误信息)。
2.2、自动获取数据表机制
class AutoGetResultTable
{
public:
AutoGetResultTable(SqliteManager *db, const string &sql, int &row, int &col, char **&ppRet);
~AutoGetResultTable();
public:
//c++11的方式,不允许拷贝构造,不允许赋值
AutoGetResultTable(const AutoGetResultTable &) = delete;
AutoGetResultTable& operator=(const AutoGetResultTable &) = delete;
private:
SqliteManager *m_db;
char **m_ppRet;
};
这个类把执行sql语句获得相应结果的方法给封装了起来,通过析构函数实现在该类实例对象生命周期结束时释放结果表。
2.3、数据管理
#define DOC_DB "doc.db"
#define DOC_TABLE "doc_tb"
//单例化:构造函数私有化,创建一个获得实例的方法
class DataManager//对数据库文件系统的增删查改
{
public:
static DataManager& GetInstance();
public:
~DataManager();
public:
void InitSqlite();
public:
void InsertDoc(const string &path, const string &doc);
void GetDocs(const string &path, multiset<string> &docs);
void DeleteDoc(const string &path, const string &doc);
public:
void Search(const string &key, vector<pair<string, string> > &doc_path);
static void SplitHighlight(const string &str, const string &key,
string &prefix, string &highlight, string &suffix);//分割高亮,高亮的核心在分割
private:
DataManager();
private:
SqliteManager m_dbmgr;
};
用这个类创建一个对象,用来操作数据库中的数据,主要是对数据库文件系统的增删查改以及关键字高亮用到的分割函数。在这个类中包含一个SqliteManager类型的成员变量,因为对数据库的操作会用到SqliteManager封装的函数。
InitSqlite()
函数通过执行sql语句创建一个表单,表单中包含如下项目:序号、文件名、文件路径、文件拼音全拼、文件拼音首字母全拼。
这个类在构造对象时构造函数会打开或者创建一个数据库,然后调用InitSqlite();
在这个数据库中创建一张表。
InsertDoc()
函数的作用就是往表里面插入文件对应数据,这个函数会调用汉字–拼音转换函数,得到一个文件名对应的拼音全拼和拼音首字母全拼,然后把文件名、文件路径、拼音全拼、拼音首字母全拼一并插入到表中。
GetDocs()
函数用来从表中。
DeleteDoc()
函数用来删除数据库中的某个文件的相关系,主要完成两个部分功能,先是把指定的路径下的指定的文件相关信息删除,如果这个文件是一个文件夹,那么再把其文件夹中的文件相关信息一并删除。
Search()
函数负责从数据库中找出我们想要的文件对应的信息,这个函数会将我们输入的关键字先转为拼音全拼字段和拼音首字母全拼字段,然后整个查找过程都是通过这两个字段进行模糊匹配的,我们需要通过传入一个vector容器将搜索到的内容存起来。
SplitHighlight()
函数是关键字分割高亮的核心,高亮的关键在分割。这个函数传入要分割的文件名str,查找时用的关键字key,还有三个用来存放分割后字段的string对象(prefix、highlight、string &suffix)。这个函数首先做的工作就是将str和key转为小写字符串,方便查找(汉字不变,只是将其中的英文字母转换),然后分三种情况对其分割。
(1) 如果是中文搜索,并且能搜索成功,那么他的搜索过程就是直接在小写的str中找小写的key的起始位置,然后将其分为三段,前缀、高亮部分(key部分)、后缀。具体代码实现如下:
string strlower(str), keylower(key);
//从什么地方开始转换,转换到什么地方,以什么方式转换,将字符串转换为小写,方便查找
transform(strlower.begin(), strlower.end(), strlower.begin(), ::tolower);
transform(keylower.begin(), keylower.end(), keylower.begin(), ::tolower);
//1 如果中文搜索,并能搜索成功,则直接分离
size_t pos = strlower.find(keylower);//找到关键字的首位置
if (pos != string::npos)//对原字符串进行分割
{
prefix = str.substr(0, pos);
highlight = str.substr(pos, keylower.size());
suffix = str.substr(pos+keylower.size(), string::npos);//第二个参数表示返回的字节个数,如果是负数,表示一直返回直到字符串结束位置
return;
}
(2) 如果是使用拼音全拼搜索,那么这个时候就要注意:一个汉字占两个字节,但是一个汉字的拼音占几个字节不确定,需要求一下。先将str_lower和key_lower转换为拼音全拼,然后在str_pinyin中找key_pinyin的位置pos,因为一个汉字和一个拼音大小不同,所以需要两个下标:str_index、pinyin_index,这两个下标一个指向原字符串,一个指向拼音首字母全拼字符串,两个下标在遍历过程相对应,以便记录原字符串中高亮部分的位置。用highlight_index和highlight_len分别记录高亮部分的开始位置和高亮部分的长度。在遍历str过程中,用pinyin_index和pos是否相等来判断是不是找到高亮起始位置,用initials_index和pos + key_pinyin.size()是否相等来判断是不是找到高亮结束位置。在下标移动时要特别注意,汉字字符移两个,对于汉字字符对应的拼音,需要先求一下拼音的长度,然后把pinyin_index移动所求长度那么多偏移。最后通过高亮起始位置和高亮长度将str分成三部分,具体代码实现如下:
//2 使用拼音全拼搜索,则需要匹配分离子串汉字和拼音
//注意一个汉字占两个字节,但是一个汉字的拼音占几个字节不确定,需要求一下
string str_pinyin = ChineseConvertPinYinAllSpell(strlower);
string key_pinyin = ChineseConvertPinYinAllSpell(keylower);//key可能是一半拼音一半汉字
pos = str_pinyin.find(key_pinyin);//在str中找key的位置
if (pos != string::npos)//如果找到了
{
size_t str_index = 0;
size_t pinyin_index = 0;
size_t highlight_index = 0;
size_t highlight_len = 0;
while (str_index < strlower.size())
{
if (pinyin_index == pos)
{
highlight_index = str_index;//找到高亮位置索引
}
if (pinyin_index == pos + key_pinyin.size())//当pinyin_index大于高亮部分索引时把长度计算出来
{
highlight_len = str_index - highlight_index;//求得高亮部分长度
break;
}
if (strlower[str_index]>=0 && strlower[str_index]<=127)
{
//是一个字符,索引移动一个字节
++str_index;
++pinyin_index;
}
else
{
//是汉字
string word(strlower, str_index, 2);//提取一个汉字
string word_pinyin = ChineseConvertPinYinAllSpell(word);//提取的汉字对应的拼音
str_index += 2;//文字跨越的字节数
pinyin_index += word_pinyin.size();//拼音跨越的字节数
}
}
//注意是对原字符串分割,不是对拼音字符串分割
if (str_index == strlower.size())//考虑搜索的是最后一个字的情况
highlight_len = str_index - highlight_index;
prefix = str.substr(0, highlight_index);
highlight = str.substr(highlight_index, highlight_len);
suffix = str.substr(highlight_index+highlight_len, string::npos);
return;
}
(3) 对于使用首字母搜索,先将小写的str和小写的key转为拼音全拼str_initials、key_initials,然后在str_initials中找到key_initials的位置,因为一个汉字和一个拼音首字母大小不同,所以需要两个下标:str_index、initials_index,这两个下标一个指向原字符串,一个指向拼音首字母全拼字符串,两个下标在遍历过程相对应,以便记录原字符串中高亮部分的位置。用highlight_index和highlight_len分别记录高亮部分的开始位置和高亮部分的长度。在遍历str过程中,用initials_index和pos是否相等来判断是不是找到高亮起始位置,用initials_index和pos + key_initials.size()是否相等来判断是不是找到高亮结束位置。在下标移动时要特别注意,汉字字符移两个,英文字符移一个。最后通过高亮起始位置和高亮长度将str分成三部分,具体代码实现如下:
//3 使用首字母搜索
string str_initials = ChineseConvertPinYinInitials(strlower);
string key_initials = ChineseConvertPinYinInitials(keylower);
pos = str_initials.find(key_initials);
if (pos != string::npos)
{
size_t str_index = 0;
size_t initials_index = 0;
size_t highlight_index = 0;
size_t highlight_len = 0;
while (str_index < strlower.size())//对str进行遍历
{
if (initials_index == pos)//首字母字符串的下标也在动,此下标和str下标相对应
{
highlight_index = str_index;//如果找到高亮位置,高亮起始下标赋str_index的值
}
if (initials_index == pos + key_initials.size())//找到了高亮结束位置,求出高亮部分长度,退出循环
{
highlight_len = str_index - highlight_index;//高亮部分的长度需要用str下标计算,因为汉字和拼音首字母占字节数不一样
break;
}
if (strlower[str_index]>=0 && strlower[str_index]<=127)
{
//是一个字符,索引移动一个字节
++str_index;
++initials_index;
}
else
{
//是一个汉字或者中文符号
//注意可能是汉字之外的中文字符这个时候initials_index中是未转换的中文字符,所以initials_index也可能加2
if (str_initials[initials_index] > 127)
initials_index += 2;
else
++initials_index;
str_index += 2;
}
}
if (str_index == strlower.size())//高亮字符一直到最后一个字符的情况
highlight_len = str_index - highlight_index;
prefix = str.substr(0, highlight_index);
highlight = str.substr(highlight_index, highlight_len);
suffix = str.substr(highlight_index + highlight_len, string::npos);
return;
}
3、扫描管理模块(ScanManager.h ScanManager.cpp)
这一模块的主要功能就是对本地文件系统和数据库文件系统分别进行扫描,然后将扫描结果分别放到两个set中(set中的数据是有序的),对比本地文件与数据库文件,在对比过程中会出现三种情况:如果本地字符串小于数据库字符串,说明新增加文件了,数据库需要新增文件;如果本地字符串大于数据库字符串,说明删除文件了,数据库需要删除文件;如果两个字符串相等,说明文件没有任何操作,不需要对数据库操作。最后,需要将该路径下的子目录进行递归遍历。
class ScanManager
{
public:
static ScanManager& CreateInstance(const string &path);
public:
void StartScan(const string &path);
void ScanDirectory(const string &path);
private:
ScanManager();
ScanManager(const ScanManager&);
ScanManager& operator=(const ScanManager&);
//static ScanManager m_scan;
};
//ScanManager ScanManager::m_scan;
ScanDirectory()
函数主要负责以上描述中的功能。CreateInstance()
会创建一个线程,这个线程执行StartScan()
函数,StartScan()
调用ScanDirectory()
通过不断的扫描本地文件系统和数据库文件系统来更新数据库文件系统中的信息。但是这个地方会有一个问题,比如说文件量特别大,然后我突然删除了某个文件,那么整个扫描过程会长一点,这样就会造成数据库更新不及时,这样就会出现错误的搜索结果。
4、打印界面模块(Sysframe.h Sysframe.cpp)
void SetCurPos(int x, int y);
void DrawRow(int x, int y);
void DrawCol(int x, int y);
void DrawFrame(char *title);
void DrawMenu();
void SystemEnd();
void HideCursor();
打印界面模块封装了标准输出控制函数和打印函数,用来打印一个简单直观的用户界面。SetCurPos
用来控制打印光标的位置,方便我们在需要的位置打印字符。DrawRow()
是画行函数,DrawCol()
是画列函数,我的界面包含6行2列。DrawFrame()
通过调用DrawRow()
6次,DrawCol()
2次,并且结合system()
函数设置控制台的颜色和长宽来画出用户界面的框架,打印结果如下:
DrawMenu()
函数用来在框架中填充默认菜单项,打印结果如下:
SystemEnd()
函数用来在我们一次查询结束时将 “请按任意键继续. . .” 放到合适位置,如上图所示。HideCursor()
用来在查询结束后隐藏光标。
5、系统驱动模块(DocFastSearchTool.cpp)
这个模块其实就是项目的主程序,用来启动整个项目。整个项目的启动流程如下:
(1) 初始化搜索文件的根路径(C、D、E盘);
(2) 创建扫描实例,用于数据库文件系统一开始的初始化和修改文件时数据库的更新;
(3) 创建搜索实例,用于在我们输入关键字后在数据库中对其进行搜索;
(4) 进入while(1)循环,调用打印界面函数打印用户界面,开始与用户交互;
(5) 等待用户输入关键字,对用户输入关键字在数据库中查找,返回查找结果集合;
(6) 遍历打印返回的结果集合,在打印之前先将结果中的文件名进行高亮处理;
(7) 结果打印完毕后提示“请按任意键继续. . .”,等待用户输入命令进入下一次while(1)循环体中进行搜索操作。
1、汉字转拼音全拼部分汉字出错问题
在我把网上找的汉字转拼音全拼程序加到自己的项目中后发现有时候会出现搜索出错的情况,我对程序做了全面分析后感觉程序没有任何逻辑错误,同时我发现每次出错都会把不同的汉字转换为同一个拼音,因此我推断可能是在复制程序时把记录拼音对应存储码的一维数组中的某个数给改动了,后来一个数一个数的排除,果然如我所料,我把qiang的存储码数字中多加了一个3,因为存储码都是负数,并且是递增的,所以这个存储码变小了,这就导致存储码小于qiang的汉字如果大于变小后的qiang的存储码时遍历数组时会qiang的存储码的位置停下来,所以都会转换为拼音全拼qiang,修改qiang的存储码后转换就正常了。
2、汉字转拼音首字母全拼函数设计
一开始我在网上找汉字转拼音首字母全拼的函数没有找到简单易懂的,后来受汉字转拼音全拼的启发,我设计了本项目中的汉字转拼音首字母全拼的函数,具体细节见1.4。
3、搜索C盘时总是弹出Invalid arguement
在最后设计完整个的程序后,我需要把C、D、E盘的文件都扫描达到同步整个文件系统的目的,但是这个时候问题就出来了。因为在设计程序的过程中用的只是D盘下的一个子目录来看设计的效果,所以一直都没有看出问题,当我扫描所有目录时才发现会出现上图中的错误,这个错误很奇怪,程序是完全没有问题的,那为什么会报如上错误呢?我一开始想的是可能C盘有的文件会不会不允许访问,所以对C、D、E盘分别扫描了一遍,果然是C盘的问题。为了确定问题的具体所在,我在下图中的一个位置打了断点调试程序,最后发现程序出问题是在函数搜索C:\\Documents and Settings\\*.*
这个路径时发生的,那Documents and Settings
是个啥东西,为什么我的C盘都找不到呢?查找结果如下图。后来通过访问百度发现,从win7开始,Documents and Settings就不可用了,而是由用户文件夹代替了。微软停用了Documents and Settings文件夹,改为了【用户】(users)。所以在扫描C盘时就会出错了,最后,我通过下面的第三图中的方式直接跳过这个文件,问题就解决了。
1、我的项目会不断的扫描磁盘,就算没有对磁盘文件进行修改,所以消耗比较多的电脑资源。
2、我的项目和Everything比起来效率算是超低配了,Everything并不扫描整个磁盘,只是读取磁盘上的USN日志,并建立索引,所以速度飞快;而我的项目每次扫描都是去扫描磁盘,扫描速度那可是相当的慢。
3、项目中还有一些细节需要完善,比如说搜索结果大于一页,需要弄一个换页的功能;比如说搜索结果特别长,将结果中的某些字符省略。
不断扫描磁盘的解决方案: 我觉得可以增加一个监控模块,在发生文件增加或者删除或者修改文件名称时才通知扫描模块去重新扫描磁盘。
换页功能的解决方案: 可以用一个stage_start(初始值是0)变量来标定这一页第一行数据是遍历结果doc_path(一个vector,里面存放pair
省略搜索结果字符的解决方案: 我们可以规定一个MAX_LEN,在打印前判断一下要打印的字符串的长度,太长的话截取字符串然后打印。
项目完整代码:https://github.com/xiao-hao-hao/DocFastSearchTool