Everything实现,快速搜索文件

最近编写NTFS文件实时搜索工具, 类似 Everything 这样, 翻阅了很多博客, 结果大致如下:

1.分析比较肤浅, 采用USN日志枚举来获取文件记录

    速度一言难尽, 因为日志枚举的是全盘所有文件的所有日志, 记录比文件记录还多, 速度当然很慢, 还有的甚至于是 使用 DeviceIoControl 函数 通过 FSCTL_GET_NTFS_FILE_RECORD 参数类型来获取全部文件记录, 我也尝试了, 其速度一言难尽, 越到后面速度越慢, 跑了几分钟我等不下去了手动强制终止了

2.虽然也有解析 MFT 记录的, 但是解析不全面

   如果按照他们写的解析文件记录, 那么你会发现: 为啥我的文件记录数总是比 Everything 少几千上万个, 而且也没有处理短文件名, 部分文件记录只有短文件名, 这个需要解析 属性列表(属性类型0x20)来获取引用记录, 然后解析引用的记录来拿到长文件名.

 

 

总体看来就是很多文章解析讲解不算全面, 直接拿来用是不可能的, 不仅速度慢, 数据还不全, 于是我就花了大量时间查找资料, 编写代码不断发现问题并分析问题, 在不断尝试于分析之下, 才得到现在的经验总结.

 

 

基本准备

数据库选择

当然是 sqlite3 了,小巧方便, 是非常好用的数据库

数据表字段

字段描述
id文件记录号,每个文件本身标识
parent_id文件(夹)所在文件夹的标识号
name文件名
path文件完整路径

 

值得一提的是, 我建表使用的是 id, parent_id,name 组成的联合主键, 这是为啥呢?很多人以为简单地用一个 id 作为主键就行了, 其实不然, 原因如下 

1.一个ID可以有多个父ID

实际对比发现扫描文件数始终比 everything 的少, 部分文件 everything 能搜出来, 我的确搜不到, 经过多次调试分析发现, 解析 文件名属性 可以得到同一个 文件ID 有不同父ID, 查看了这种文件, 发现是在不同文件夹下的同名同数据的文件, 应该是一种节约空间的做法.

2.一个id + parent_id 标识的记录项可能存在多个不同的长文件名

这是我解析时遇到的最麻烦的一个坑,因为解决了 一个ID多个父ID 记录后,  发现我的扫描记录数还是比 Everything 少一些,于是逐个文件夹比较, 找到了文件数比较少且搜索数目不一致的文件夹, 然后修改代码调试分析, 发现 id + parent_id 组合后, 文件名还能不一样(都是长文件名), 于是我解决了这个问题, 最后扫描文件数量终于和 Everything 完全一致了!

所以建表可以这么写:

CREATE TABLE IF NOT EXISTS file_list (id        INTEGER NOT NULL,parent_id INT,name      TEXT,attr      INT,path      TEXT,PRIMARY KEY(id, parent_id, name)
);

 

一. 解析NTFS 主文件表(MFT)

这一步是获取文件数据的唯一迅速且可靠的来源,只需要解析元数据文件中的$MFT(第0条文件记录)的数据属性(属性类型0x80)即可。

里面需要注意的是,这里的数据是一个dataruns,手动解析这个 dataruns 得到文件记录在驱动器上的位置和大小,读取这些数据(一次可以读取一个 dataruns 块,也可以分块读取来减少内存占用)。

读取后按照文件记录(1KB一条记录)进行解析(需要解析属性列表0x20属性和文件名属性0x30),有文件名是短文件名,可以通过从解析属性列表得到的记录参考号来获取文件记录来拿到长文件名。

此外,如果文件记录存在基本文件记录引用,那么需要把解析的文件记录的记录号改成这个基本文件记录号,不然会出现扫描的文件数比 Everything 的多。

 

以下是我根据资料编写的结构

// 文件引用 https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_reference.html
typedef union _FILE_REFERENCE
{uint64_t        Data;struct {uint64_t    RecordNumber : 48;          // 文件记录号uint64_t    SequenceNumber : 16;        // 序号};bool operator < (const _FILE_REFERENCE& r) const{return this->Data < r.Data;}bool operator == (const _FILE_REFERENCE& r) const{return this->Data == r.Data;}uint64_t operator()(const _FILE_REFERENCE& key) const{return key.Data;}}FILE_REFERENCE, * PFILE_REFERENCE;// 文件记录标头 https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_record.html
typedef struct _NTFS_FILE_RECORD_HEADER
{uint32_t        MagicNumber;                // 幻数‘FILE’uint16_t        UpdateSequenceOffset;       // 更新到更新序列的偏移量uint16_t        UpdateSequenceSize;         // 更新序号和队列的长度(总共为S个字),为表格最后两项的大小和(以字为单位),此处值为S的话,最后两项的大小总和为2S个字节。uint64_t        LogFileSerialNumber;        // 日志文件序列号($LogFile Sequence Number,LSN)uint16_t        SequenceNumber;             // 序列号uint16_t        HardLinkCount;              // 硬连接数uint16_t        FirstAttributeOffset;       // 第一个属性的偏移union {uint16_t        Data;struct {uint16_t    Use : 1;            // 记录正在使用中uint16_t    Directory : 1;      // 记录是目录uint16_t    Exension : 1;       // 一个 exensionuint16_t    SpecialIndex : 1;   // 存在特殊索引};}Flags;                                     // 标志uint32_t        RealSize;                   // 文件记录的真实大小uint32_t        AllocatedSize;              // 文件记录的分配大小FILE_REFERENCE  BaseFileRecordReference;    // 对基本 FILE 记录的文件引用uint16_t        NextAttributeId;            // 下一属性 IDuint16_t        Padding;                    // 填充uint32_t        RecordNumber;               // 此 MFT 记录的编号uint16_t        UpdateSequenceNumber;       // 更新序列号uint16_t        UpdateSequenceArray[487];   // 更新序列数组
}NTFS_FILE_RECORD_HEADER, * PNTFS_FILE_RECORD_HEADER;// 文件标志 https://flatcap.github.io/linux-ntfs/ntfs/attributes/file_name.html
typedef union _NTFS_FILE_FLAG
{uint32_t            Data;struct {uint32_t        ReadOnly : 1;           // 0x00000001 只读uint32_t        Hidden : 1;             // 0x00000002 隐藏uint32_t        System : 1;             // 0x00000004 系统uint32_t        Unuse : 1;              // 0x00000008 未使用uint32_t        Directory : 1;          // 0x00000010 目录uint32_t        Archive : 1;            // 0x00000020 档案uint32_t        Device : 1;             // 0x00000040 设备uint32_t        Normal : 1;             // 0x00000080 普通uint32_t        Temporary : 1;          // 0x00000100 临时uint32_t        SparseFile : 1;         // 0x00000200 稀疏uint32_t        ReparsePoint : 1;       // 0x00000400 重解析点uint32_t        Compressed : 1;         // 0x00000800 压缩uint32_t        Offline : 1;            // 0x00001000 脱机uint32_t        NotContentIndexed : 1;  // 0x00002000 无内容索引uint32_t        Encrypted : 1;          // 0x00004000 加密uint32_t        Unuse2 : 13;            // 未使用uint32_t        ExDirectory : 1;        // 0x10000000 目录uint32_t        ExIndexView : 1;        // 0x20000000 索引浏览};
}NTFS_FILE_FLAG, *PNTFS_FILE_FLAG;// 0x30 文件名 https://flatcap.github.io/linux-ntfs/ntfs/attributes/file_name.html
// https://learn.microsoft.com/en-us/windows/win32/devnotes/file-name
typedef struct _NTFS_FILE_NAME
{FILE_REFERENCE          ParentDirectory;            // 父目录的文件引用uint64_t                FileCreationTime;           // 文件创建时间uint64_t                FileAlteredTime;            // 文件修改时间uint64_t                FileChangedTime;            // 文件修改时间uint64_t                FileReadTime;               // 文件读取时间uint64_t                AllocatedSize;              // 分配大小uint64_t                RealSize;                   // 真实大小NTFS_FILE_FLAG          Flags;                      // 文件标志uint32_t                UsedByEAsAndReparse;        // 被EAs和Reparse使用uint8_t                 FileNameLength;             // 文件名长度union {uint8_t             Data;       // 0x00: POSIXstruct {uint8_t         Win32 : 1;  // 0x01: Win32uint8_t         Dos : 1;    // 0x02: DOS};}FilenameNamespace;                                 // 文件名命名空间wchar_t                 FileName[1];                // 文件名
}NTFS_FILE_NAME, * PNTFS_FILE_NAME;

 

 关键解析的大致逻辑如下:

uint64_t NTFS_Base::GetDataRunUint(uint8_t* pAddr, uint8_t size)
{uint64_t nLength = 0;// 计算值if (size > 0 && size <= 8){uint8_t* pData = pAddr + size - 1;for (int i = size - 1; i >= 0; i--, pData--){uint8_t data = *(uint8_t*)pData;nLength = (nLength << 8) | data;}}return nLength;
}int64_t NTFS_Base::GetDataRunInt(uint8_t* pAddr, uint8_t size)
{uint64_t nLength = 0;uint64_t nMaxData = 0x01;int8_t nLastData = 0;// 计算值if (size > 0 && size <= 8){uint8_t* pData = pAddr + size - 1;nLastData = *(uint8_t*)pData;for (int i = size - 1; i >= 0; i--, pData--){uint8_t data = *(uint8_t*)pData;nLength = (nLength << 8) | data;nMaxData = nMaxData << 8;}}// 负数转换if (nLastData < 0){nLength = 0 - (nMaxData - nLength);}return nLength;
}bool NTFS_MFT_Parse::_ParseMasterFileTableData(HANDLE hFile,PNTFS_BOOT_RECORD pBootRecord,PNTFS_FILE_RECORD_HEADER pFileHeaderStart,PNTFS_ATTRIBUTE_HEADER pAttrHeaderStart,const NTFS_VOLUME_INFO& volInfo,NtfsFilenameCb cb
)
{PNTFS_DATA pData = (PNTFS_DATA)pAttrHeaderStart;uint8_t* pRecordBufData = nullptr;uint32_t RecordBufSize = sizeof(NTFS_FILE_RECORD_HEADER) * NTFS_MFT_PARSE_FILE_BUF_COUNT;// 计算每簇字节数uint64_t BytesPerCluster = (uint64_t)pBootRecord->BytesPerSector * (uint64_t)pBootRecord->SectorsPerCluster;PNTFS_ATTRIBUTE_HEADER pHeader = (PNTFS_ATTRIBUTE_HEADER)pData;static std::mutex mtexAccess;// 主文件表的数据是非常驻属性if (pHeader->NonResidentFlag){PNTFS_DATA_RUNS pDataRuns = (PNTFS_DATA_RUNS)((uint64_t)(pAttrHeaderStart) +pAttrHeaderStart->NonResident.DataRunsOffset);uint64_t FileRecordOffset = 0;uint64_t FileRecordIndex = 0;bool fAobrt = false;// 读取缓存分配pRecordBufData = new (std::nothrow) uint8_t[RecordBufSize];if (!pRecordBufData){return false;}// 解析数据运行while (pDataRuns->LengthSize || pDataRuns->OffsetSize){// 计算长度值与偏移值位置uint8_t* pOffsetData = (uint8_t*)pDataRuns + pDataRuns->LengthSize + 1;uint8_t* pLengthData = (uint8_t*)pDataRuns + 1;// 获取长度值与偏移值int64_t Offset = GetDataRunInt(pOffsetData, pDataRuns->OffsetSize);uint64_t Length = GetDataRunUint(pLengthData, pDataRuns->LengthSize);// 计算数据偏移, 数据量FileRecordOffset += Offset * BytesPerCluster;// 分块读取uint64_t DataBlockSize = RecordBufSize;uint64_t BufBlockOffset = FileRecordOffset;uint64_t DataBufSize = Length * BytesPerCluster;// 遍历解析文件记录块while (DataBufSize > 0){// 数据块大小检查if (DataBufSize < DataBlockSize){DataBlockSize = DataBufSize;}// 设置读取偏移if (!SetFileOffset(hFile, BufBlockOffset, nullptr, 0)){break;}// 读取文件记录块DWORD dwNumberOfBytesRead = 0;if (!::ReadFile(hFile, pRecordBufData, (DWORD)DataBlockSize, &dwNumberOfBytesRead, NULL)){break;}// 遍历文件记录PNTFS_FILE_RECORD_HEADER pFileHeaderItem = (PNTFS_FILE_RECORD_HEADER)pRecordBufData;int64_t FileCount = DataBlockSize / sizeof(NTFS_FILE_RECORD_HEADER);for (int64_t i = 0; i < FileCount; i++){// 解析文件记录属性if (FileRecordIndex >= NTFSFileNameType::e16_Unuse_Start &&NTFS_FILE_MGAIC_NUMBER == pFileHeaderItem->MagicNumber){PNTFS_ATTRIBUTE_HEADER pAttrHeaderItem = (PNTFS_ATTRIBUTE_HEADER)((uint8_t*)pFileHeaderItem + pFileHeaderItem->FirstAttributeOffset);FILE_REFERENCE fileRef = { 0 };fileRef.RecordNumber = pFileHeaderItem->RecordNumber;fileRef.SequenceNumber = pFileHeaderItem->SequenceNumber;// 存在基本文件记录段的文件引用, 则使用基本文件引用if (pFileHeaderItem->BaseFileRecordReference.Data){fileRef = pFileHeaderItem->BaseFileRecordReference;}else{fileRef.RecordNumber = pFileHeaderItem->RecordNumber;fileRef.SequenceNumber = pFileHeaderItem->SequenceNumber;}MFT_FILE_INFO_LIST fileInfoList;_ParseFileRecordAttributes(hFile, pBootRecord, pFileHeaderItem, pAttrHeaderItem, fileInfoList);if (!fileInfoList.empty()){if (cb){mtexAccess.lock();bool fContinue = false;fContinue = cb(volInfo, pFileHeaderItem, fileRef, fileInfoList);mtexAccess.unlock();if (!fContinue){fAobrt = true;break;}}}}pFileHeaderItem++;FileRecordIndex++;}if (fAobrt){break;}// 剩余数据量更新DataBufSize -= DataBlockSize;// 数据块偏移更新BufBlockOffset += DataBlockSize;}// 解析下一个数据运行位置pDataRuns = (PNTFS_DATA_RUNS)((uint8_t*)pDataRuns + pDataRuns->LengthSize + pDataRuns->OffsetSize + 1);}}if (pRecordBufData){delete[] pRecordBufData;}return true;
}

二. 监控 USN 日志

当文件发生变动时,同步更新数据库,这里只需要关注文件创建,删除,更名即可。

 

可以定义这么一个结构体存储日志信息

// 日志更新信息
typedef struct _NTFS_USN_INFO
{_tstring            strFileName;            // 文件名FILE_REFERENCE      ReferenceNumber;        // 文件引用号FILE_REFERENCE      ParentReferenceNumber;  // 父文件引用号uint64_t            UpdateSequenceNumber;   // 更新序列号uint32_t            uDriveIndex;            // 卷索引号 如: 0: C 1: D 3:D 4:ENTFS_USN_REASON     Reason;                 // 更改原因uint32_t            FileAttributes;         // 文件属性uint8_t             Namespace;              // 命名空间_NTFS_USN_INFO() :ReferenceNumber{0},ParentReferenceNumber{0},UpdateSequenceNumber(0),uDriveIndex(0),Reason{ 0 }{}}NTFS_USN_INFO, * PNTFS_USN_INFO;

 

以下是部分关键逻辑:


// 替换记录
#define SQL_QUERY_REPLACE R"(
REPLACE INTO file_list (id, parent_id, name, attr) VALUES (%llu, %llu, "%s", %d);
)"// 删除记录
#define SQL_QUERY_DELETE R"(
DELETE FROM file_list where id = %llu AND parent_id = %llu AND name = "%s";
)"// 更新子路径
#define SQL_QUERY_UPDATE_CHILD_PATH   R"(
WITH RECURSIVE sub_tree(id, parent_id, name, path) AS (SELECT id, parent_id, name, path AS pathFROM file_listWHERE id = %llu AND parent_id = %lluUNION ALLSELECT c.id, c.parent_id, c.name, p.path || '\' || c.nameFROM file_list cINNER JOIN sub_tree p ON c.parent_id = p.id
)
UPDATE file_list
SET path = st.path
FROM sub_tree st
WHERE file_list.id = st.id AND file_list.parent_id = st.parent_id AND file_list.name = st.name;
)"// 更新文件路径
#define SQL_QUERY_UPDATE_FILE_PATH   R"(
WITH RECURSIVE path_cte(id, parent_id, name, path) AS (SELECT id, parent_id, name, nameFROM file_listWHERE id = %llu AND parent_id = %llu AND name = "%s"UNION ALLSELECT f.id, f.parent_id, f.name, f.name || '\' ||p.path FROM file_list fINNER JOIN path_cte p ON (f.id = p.parent_id) 
)
UPDATE file_list
SET path = (SELECT path FROM path_cte WHERE parent_id = 0)
WHERE id = %llu AND parent_id = %llu AND name = "%s";
)"int NTFS_Search::_UpdateFilePath(SQL_FILE_ID fileID, SQL_FILE_ID parentID, _tstring strFilename)
{_tstring strSql = CStrUtils::Format(_T(SQL_QUERY_UPDATE_FILE_PATH), fileID.data, parentID.data, strFilename.c_str(), fileID.data, parentID.data, strFilename.c_str());int nRes = m_sql3.Exec(CStrUtils::TStrToU8Str(strSql).c_str(), NULL, NULL, NULL);return nRes;
}int NTFS_Search::_UpdateChildPath(SQL_FILE_ID fileID, SQL_FILE_ID parentID)
{_tstring strSql = CStrUtils::Format(_T(SQL_QUERY_UPDATE_CHILD_PATH), fileID.data, parentID.data);int nRes = m_sql3.Exec(CStrUtils::TStrToU8Str(strSql).c_str(), NULL, NULL, NULL);return nRes;
}void NTFS_Search::UsnProc(const std::vector<NTFS_USN_INFO>& usnList)
{for (const auto& usnInfo : usnList){strFileName = CStrUtils::TStrToU8Str(usnInfo.strFileName);fileParentID.ReferenceNumber = usnInfo.ParentReferenceNumber.RecordNumber;fileParentID.dwDriveIndex = usnInfo.uDriveIndex;fileID.ReferenceNumber = usnInfo.ReferenceNumber.RecordNumber;fileID.dwDriveIndex = usnInfo.uDriveIndex;bool fDirectory = (FILE_ATTRIBUTE_DIRECTORY == (usnInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY));//新建文件, 需要更新文件路径if (usnInfo.Reason.UsnFileCreate){// 添加文件记录strSql = CStrUtils::FormatA(SQL_QUERY_REPLACE, fileID, fileParentID.data, strFileName.c_str(), fDirectory);m_sql3.Exec(strSql.c_str(), NULL, 0, nullptr);// 更新文件路径_UpdateFilePath(fileID, fileParentID, usnInfo.strFileName);}// 删除文件 | 重命名旧文件名, 需要更新子路径if (usnInfo.Reason.UsnRenameOldName || usnInfo.Reason.UsnFileDelete){// 删除记录strSql = CStrUtils::FormatA(SQL_QUERY_DELETE, fileID, fileParentID, strFileName.c_str());m_sql3.Exec(strSql.c_str(), NULL, 0, nullptr);}// 重命名新文件名, 需要更新子路径if (usnInfo.Reason.UsnRenameNewName && usnInfo.Reason.UsnClose){// 更新文件记录(不含路径)strFileName = CStrUtils::TStrToU8Str(usnInfo.strFileName);strSql = CStrUtils::FormatA(SQL_QUERY_REPLACE, fileID, fileParentID.data, strFileName.c_str(), fDirectory);m_sql3.Exec(strSql.c_str(), NULL, 0, nullptr);// 更新文件路径_UpdateFilePath(fileID, fileParentID, usnInfo.strFileName);// 如果此条记录是文件夹, 则更新子其所有子项路径if (fDirectory){_UpdateChildPath(fileID, fileParentID);}}_SetUsn(usnInfo.uDriveIndex, usnInfo.UpdateSequenceNumber);if (m_fQuit){break;}}
}

三. 数据库查询

采用Sqlite3进行数据库操作, 以下是部分关键代码


// 计数
#define SQL_QUERY_COUNT   R"(
SELECT count(*) AS count FROM file_list WHERE path NOT NULL
)"// 更新根路径
#define SQL_QUERY_UPDATE_ROOT_PATH   R"(
WITH RECURSIVE sub_tree(id, parent_id, name, path) AS (SELECT id, parent_id, name, name AS pathFROM file_listWHERE parent_id = 0UNION ALLSELECT c.id, c.parent_id, c.name, p.path || '\' || c.nameFROM file_list cINNER JOIN sub_tree p ON c.parent_id = p.id
)
UPDATE file_list
SET path = (
SELECT path FROM sub_tree 
WHERE sub_tree.id = file_list.id AND sub_tree.parent_id = file_list.parent_id AND sub_tree.name = file_list.name
);
)"// 删除索引
#define SQL_QUERY_DROP_INDEX   R"(
DROP INDEX IF EXISTS idx_file_list_id ON file_list;
DROP INDEX IF EXISTS idx_file_list_parent_id ON file_list;
DROP INDEX IF EXISTS idx_file_list_name ON file_list;
)"// 创建索引
#define SQL_QUERY_CREATE_INDEX   R"(
CREATE INDEX IF NOT EXISTS idx_file_list_id ON file_list(id COLLATE BINARY ASC);
CREATE INDEX IF NOT EXISTS idx_file_list_parent_id ON file_list(parent_id COLLATE BINARY ASC);
--CREATE INDEX IF NOT EXISTS idx_file_list_name ON file_list(name COLLATE NOCASE ASC);
)"// 删除索引
#define SQL_QUERY_DROP_SEARCH_INDEX   R"(
DROP INDEX IF EXISTS idx_file_list_path ON file_list;
)"// 创建索引
#define SQL_QUERY_CREATE_SEARCH_INDEX   R"(
CREATE INDEX IF NOT EXISTS idx_file_list_path ON file_list(path COLLATE NOCASE ASC);
)"// 按文件名查找
#define SQL_QUERY_SEARCH_NAME   R"(
SELECT path FROM file_list WHERE name like "%s" ESCAPE '\' AND path NOT NULL ORDER BY path
)"// 按路径查找
#define SQL_QUERY_SEARCH_PATH   R"(
SELECT path FROM file_list WHERE path like "%s" ESCAPE '\' AND path NOT NULL ORDER BY path
)"// 搜索全部
#define SQL_QUERY_SEARCH_ALL   R"(
SELECT path FROM file_list WHERE path NOT NULL ORDER BY path
)"// 删除表
#define SQL_QUERY_DELETE_TABLE                R"(
DROP TABLE IF EXISTS file_list;
)"// 创建表
#define SQL_QUERY_CREATE_TABLE                R"(
CREATE TABLE IF NOT EXISTS file_list (id        INTEGER NOT NULL,parent_id INT,name      TEXT,attr      INT,path      TEXT,PRIMARY KEY(id, parent_id, name)
);
)"// 建表更新数据
#define SQL_QUERY_REPLACE_PREPQRE R"(
REPLACE INTO file_list (id, parent_id, name, attr, path) VALUES (?, ?, ?, ?, ?);
)"// 替换记录
#define SQL_QUERY_REPLACE R"(
REPLACE INTO file_list (id, parent_id, name, attr) VALUES (%llu, %llu, "%s", %d);
)"// 删除记录
#define SQL_QUERY_DELETE R"(
DELETE FROM file_list where id = %llu AND parent_id = %llu AND name = "%s";
)"bool NTFS_Search::Search(const _tstring& strKeyWord, std::vector<_tstring>& fileList,int64_t nLimit/* = -1*/
)
{if (!m_fInit || strKeyWord.empty()){return false;}_tstring strKey = strKeyWord;bool fSearchPath = false;bool fSearchFuzzy = false;// 检查搜索是否显式指定通配符, 没有通配符则需要前后添加通配符if (!(_tstring::npos != strKeyWord.find(_T("*")) || _tstring::npos != strKeyWord.find(_T("?")))){fSearchFuzzy = true;}// 检查搜索是否为全部(关键字全是 * 字符)bool fAll = true;for (auto& ch : strKeyWord){if (_T('*') != ch){fAll = false;break;}}// 检查搜索是否包含路径if (_tstring::npos != strKeyWord.find(_T("\\"))){fSearchPath = true;}_tstring strFormat = _T(SQL_QUERY_SEARCH_ALL);// 搜索包含路径if (fSearchPath){strFormat = _T(SQL_QUERY_SEARCH_PATH);}else{strFormat = _T(SQL_QUERY_SEARCH_NAME);}if (!fAll && fSearchFuzzy){strKey = _T("*") + strKeyWord + _T("*");}// 转义处理CStrUtils::Replace(strKey, _T(R"(\)"), _T(R"(\\)"));CStrUtils::Replace(strKey, _T(R"(%)"), _T(R"(\%)"));CStrUtils::Replace(strKey, _T(R"(_)"), _T(R"(\_)"));CStrUtils::Replace(strKey, _T(R"(*)"), _T(R"(%)"));CStrUtils::Replace(strKey, _T(R"(?)"), _T(R"(_)"));_tstring strSql;if (fAll){strSql = CStrUtils::Format(_T(SQL_QUERY_SEARCH_ALL), nLimit);}else{strSql = CStrUtils::Format(strFormat.c_str(), strKey.c_str(), nLimit);}std::string strQuery = CStrUtils::TStrToU8Str(strSql);int res = m_sql3.Exec(strQuery.c_str(), [](void* data, int col_count, char** col_data, char** col_name)->int {std::vector<_tstring>* pFileList = (std::vector<_tstring>*)data;for (int i = 0; i < col_count; i++){if (0 == CStrUtils::CompareA("path", (char*)col_name[i])){if (pFileList && col_data[i]){pFileList->push_back(CStrUtils::U8StrToTStr(col_data[i]));}}}return 0;},(char**)&fileList, 0);return res;
}

 

对比:

ebc81f3f30ff45b2a86fa84ee43e59f5.png

性能上重建数据库耗时是everything的 3倍, 不过也差不多了, 以后慢慢优化

 

 

629c470a73d0459ea48f1be6e01de80c.png 

搜索速度比不上everything, 耗时是其2倍左右,但是也算是秒速了

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64687.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

京东大数据治理探索与实践 | 京东零售技术实践

01背景和方案 在当今的数据驱动时代&#xff0c;数据作为关键生产要素之一&#xff0c;其在商业活动中的战略价值愈加凸显&#xff0c;京东也不例外。 作为国内领先的电商平台&#xff0c;京东在数据基础设施上的投入极为巨大&#xff0c;涵盖数万台服务器、数 EB 级存储、数百…

nodejs搭配express网站开发后端接口设计需要注意事项

nodejs搭配express网站开发后端接口设计需要注意事项&#xff01;为了回避一些常见的误区&#xff0c;今天和大家汇总一下&#xff0c;最近我遇到的一些错误信息&#xff0c;虽然都是小问题&#xff0c;但是还是需要分享一下&#xff0c;以免大家再次犯错。 1&#xff1a;第一个…

解决vscode ssh远程连接服务器一直卡在下载 vscode server问题

目录 方法1&#xff1a;使用科学上网 方法2&#xff1a;手动下载 方法3 在使用vscode使用ssh远程连接服务器时&#xff0c;一直卡在下载"vscode 服务器"阶段&#xff0c;但MobaXterm可以正常连接服务器&#xff0c;大概率是网络问题&#xff0c;解决方法如下: 方…

Spring Boot实现OAuth2.0登录实战

一、前言 最近在研究Springboot Vue 的前后端分离框架&#xff0c;刚开始做登录功能&#xff0c;做着做着觉得普通账户密码登录太简单了&#xff0c;决定再加上 GitHub授权 和 人脸识别等多种快捷登录方式。 而GitHub授权登录正好用到了OAuth2.0中最复杂的授权码模式&#xf…

电脑开机提示error loading operating system怎么修复?

前一天电脑还能正常运行&#xff0c;但今天启动时却显示“Error loading operating system”&#xff08;加载操作系统错误&#xff09;。我已经仔细检查了硬盘、接线、内存、CPU和电源&#xff0c;确认这些硬件都没有问题。硬盘在其他电脑上可以正常使用&#xff0c;说明不是硬…

Java web的发展历史

目录 前言&#xff1a; 一.Model I和Model II 1.Model I开发模式 ​编辑 2.Model II开发模式 二. MVC模式 前言&#xff1a; 该篇文章主要介绍了Java web的发展历史&#xff0c;以及MVC相关内容 一.Model I和Model II 1.Model I开发模式 Model1的开发模式是&#xff…

Intel-ECI之Codesys PLC + Ethercat 远端IO + Codesys IDE编程

目录 一、 准备工作 二、安装Codesys 软件 PLC 三、 使用Codesys IDE 编程测试 CODESYS* 是领先的独立于制造商的 IEC 61131-3 自动化软件&#xff0c;适用于工程控制系统。它用于 Intel Edge Controls for Industrial&#xff08;Intel ECI 或 ECI&#xff09;&#xff0c;…

SQL语句练习

阅读《SQL必知必会》&#xff08;第五版&#xff09;然后结合往常表做的练习记录 这里使用的数据库时sqlite3,使用的工具时navicat 表资源链接https://wenku.baidu.com/view/349fb3639b6648d7c1c74652.html 表录入后如上图所示。后面如果有多张表之间的操作&#xff0c;在引入…

【Python】【数据分析】深入探索 Python 数据可视化:Seaborn 可视化库详解

目录 引言一、Seaborn 简介二、安装 Seaborn三、Seaborn 的基本图形3.1 散点图&#xff08;Scatter Plot&#xff09;3.2 线图&#xff08;Line Plot&#xff09;3.3 条形图&#xff08;Bar Plot&#xff09;3.4 箱型图&#xff08;Box Plot&#xff09;3.5 小提琴图&#xff0…

input输入框的placeholder颜色修改(Shadow DOM)

placeholder颜色修改 使用参考说明 https://zh.javascript.info/shadow-dom 查看shadow tree的方法

uniApp使用腾讯地图提示未添加maps模块

uniApp使用腾讯地图&#xff0c;打包提示未添加maps模块解决方案 这是报错信息&#xff0c;在标准基座运行的时候是没问题的&#xff0c;但是打包后会提示未添加&#xff0c;可以通过在mainfest里面把地图插件上腾讯地图的key更换高德地图的key&#xff0c;定位服务可以继续用腾…

linux------vim命令

一、基本模式切换 普通模式&#xff08;Normal Mode&#xff09; 当你打开Vim时&#xff0c;默认进入普通模式。在这个模式下&#xff0c;可以使用各种命令来移动光标、删除文本、复制粘贴等操作。例如&#xff0c;使用h、j、k、l来移动光标。h是向左移动一个字符&#xff0c;j…

数据可视化-2. 条形图

目录 1. 条形图适用场景分析 1.1 比较不同类别的数据 1.2 展示数据分布 1.3 强调特定数据点 1.4 展示时间序列数据的对比 1.5 数据可视化教育 1.6 特定领域的应用 2. 条形图局限性 3. 条形图图代码实现 3.1 Python 源代码 3.2 条形图效果&#xff08;网页显示&#…

2023年下半年软考信息安全工程师案例分析及答案解析

试题一(16分) 回答问题1至问题6,将解答填入答题纸对应的解答栏内。 问题1(4分) 已知DES算法S盒如下,请补全S盒空缺的数据(1)、(2)、(3)、(4)。 【参考答案】3、13、15、0 问题2(2分) 已知S盒的输入为110011,请计算经过S盒变换之后的二进制输出。 【参考…

模型部署学习笔记——模型部署关键知识点总结

模型部署学习笔记——模型部署关键知识点总结 模型部署学习笔记——模型部署关键知识点总结1. CUDA中Grid和Block的定义是什么&#xff1f;Shared Memory的定义&#xff1f;Bank Conflict的定义&#xff1f;Stream和Event的定义&#xff1f;2. TensorRT的工作流程&#xff1f;3…

Spring Cloud Gateway 源码

Spring Cloud Gateway 架构图 按照以上架构图&#xff0c;请求的处理流程&#xff1a; 1.客户端请求发送到网关 DispatcherHandler 2.网关通过 HandlerMapping 找到相应的 WebHandler 3.WebHandler生成FilterChain过滤器链执行所有的过滤器 4.返回Response结果 自动装配类Gat…

基于Spring Boot的店铺租赁平台的设计与实现

一、项目背景 随着互联网技术的飞速发展&#xff0c;线上交易已成为商业活动的重要趋势。店铺租赁作为商业地产的核心环节&#xff0c;其传统模式面临着信息不对称、交易效率低下等问题。因此&#xff0c;开发一个高效、便捷的线上店铺租赁平台显得尤为重要。本项目利用Java S…

基于卷积神经网络(CNN)和ResNet50的水果与蔬菜图像分类系统

前言 在现代智能生活中&#xff0c;计算机视觉技术已经成为不可或缺的工具&#xff0c;特别是在食物识别领域。想象一下&#xff0c;您只需拍摄一张水果或蔬菜的照片&#xff0c;系统就能自动识别其种类并为您提供丰富的食谱建议。这项技术不仅在日常生活中极具实用性&#xf…

Tomcat部署war包项目解决404问题

问题出在了Tomcat的版本上了&#xff0c;应该先去看这个项目使用的springboot版本&#xff0c;然后去仓库里找到对应Tomcat版本。 Maven Repository: org.springframework.boot spring-boot-starter-tomcat 因此我们应该选择Tomcat9版本。 当我把Tomcat11换成Tomcat9时&…

Redis篇--常见问题篇1--缓存穿透(缓存空值,布隆过滤器,接口限流)

1、概述 缓存穿透是指客户端请求的数据既不在Redis缓存中&#xff0c;也不在数据库中。换句话说&#xff0c;缓存和数据库中都不存在该数据&#xff0c;但客户端仍然发起了查询请求。这种情况下&#xff0c;缓存无法命中&#xff0c;请求会直接穿透到数据库&#xff0c;而数据…