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;
}
对比:

性能上重建数据库耗时是everything的 3倍, 不过也差不多了, 以后慢慢优化
搜索速度比不上everything, 耗时是其2倍左右,但是也算是秒速了
相关文章:
Everything实现,快速搜索文件
最近编写NTFS文件实时搜索工具, 类似 Everything 这样, 翻阅了很多博客, 结果大致如下: 1.分析比较肤浅, 采用USN日志枚举来获取文件记录 速度一言难尽, 因为日志枚举的是全盘所有文件的所有日志, 记录比文件记录还多, 速度当然很慢, 还有的甚至于是 使用 DeviceIoControl 函数…...
[硬件] DELL BIOS 相关注意事项
前言 前段时间重装系统. DELL BIOS属实资料少, 又难用. 这里给出相关的注意事项, 并且配上图片. BIOS相关注意事项 进入BIOS ESC/F2/ F12. 都可以进入BIOS, 当进U盘的入Win PE系统时, 使用F12 效果更佳. 关闭安全模式 切换到Boot Configuration选项,将Secure Boot选项off选…...
Rocky Linux 下安装Liboffice
Rocky Linux下安装Liboffice。 Step1: 在桌面,单击击键盘的Window键,点击出现的白色software按钮图标; Step2: 输入lib,即可自动跳出libre Office, 进行安装; Step3: Have fun with Rocky Linux....
【每日学点鸿蒙知识】长时任务、HarmonyAppProvision申请、preferences、Testing工具、应用保活
1、HarmonyOS 如何解决语音聊天、通信app退后台系统采集播放回调就会停止,回前台未恢复? 关于应用切到后台系统采集播放回调停止的问题原因如下:为了降低设备耗电速度、保障用户使用流畅度,系统会对退至后台的应用进行管控&#…...
步进电机驱动算法——S形加减速算法原理
1. 前言: 最近项目又用到了步进电机,为了在运动中加减速更加平稳决定研究一下S型加减速,原来用过野火的s型加减速程序,云里雾里的移植成功了,今天再翻来程序看一脸懵逼,重新学习了一下发现所有公式都能看懂…...
【图像去噪】论文复现:大道至简!ZS-N2N的Pytorch源码复现,跑通源码,获得指标计算结果,补充保存去噪结果图像代码,代码实现与论文理论对应!
请先看【专栏介绍文章】:【图像去噪(Image Denoising)】关于【图像去噪】专栏的相关说明,包含适配人群、专栏简介、专栏亮点、阅读方法、定价理由、品质承诺、关于更新、去噪概述、文章目录、资料汇总、问题汇总(更新中) 完整代码和训练好的模型权重文件下载链接见本文底…...
2024年中国新能源汽车用车发展怎么样 PaperGPT(一)
概述 在国家政策的强力扶持下,2024年中国新能源汽车市场迎来了新的发展机遇。本文将基于《中国新能源汽车用车报告(2024年)》的数据,对新能源汽车的市场发展和用车趋势概述。 新能源汽车市场发展 政策推动:国家和地…...
数据结构-排序思想
直接插入排序 将后面的无序区中的元素挨个向前面的有序区中插入。 1.将顺序表中R[0]用作哨兵,按索引i2...n的次序,将R[i]向有序区R[1...i-1]中执行插入操作。 2.插入操作可采取在有序区中从后向前的查找比较和移动的方法。 3.此操作中比较的次数与原序列…...
python 快速排序(Quick Sort)
快速排序(Quick Sort) 快速排序是一种高效的排序算法,采用分治法(Divide and Conquer)策略。它的基本思想是:选择一个基准元素(pivot),将数组分为两部分,使得…...
MySQL数据库——常见慢查询优化方式
本文详细介绍MySQL的慢查询相关概念,分析步骤及其优化方案等。 文章目录 什么是慢查询日志?慢查询日志的相关参数如何启用慢查询日志?方式一:修改配置文件方式二:通过命令动态启用 分析慢查询日志方式一:直…...
【AIGC篇】AIGC 引擎:点燃创作自动化的未来之火
:羑悻的小杀马特.-CSDN博客 未来都是惊喜。你生来本应为高山。并非草芥。 引言: 在当今数字化的时代,人工智能生成内容(AIGC)正以一种前所未有的力量改变着我们的创作领域。它就像一个神秘而强大的魔法师,…...
C语言性能优化:从基础到高级的全面指南
引言 C 语言以其高效、灵活和功能强大而著称,被广泛应用于系统编程、嵌入式开发、游戏开发等领域。然而,要写出高性能的 C 语言代码,需要对 C 语言的特性和底层硬件有深入的了解。本文将详细介绍 C 语言性能优化的背后技术,并通过…...
常用的公共 NTP(网络时间协议)服务器
公共 NTP 服务列表 以下是一些常用的公共 NTP(网络时间协议)服务器,供您参考: 中国地区公共 NTP 服务器 国家授时中心 NTP 服务器:ntp.ntsc.ac.cn中国 NTP 快速授时服务:cn.ntp.org.cn阿里云公共 NTP 服务…...
Kafka中的Topic和Partition有什么关系?
大家好,我是锋哥。今天分享关于【Kafka中的Topic和Partition有什么关系?】面试题。希望对大家有帮助; Kafka中的Topic和Partition有什么关系? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 Apache Kafka 中&#…...
Unity 使用UGUI制作卷轴开启关闭效果
视频效果 代码 using UnityEngine.UI; using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using DG.Tweening.Core; using DG.Tweening.Plugins.Options;public class JuanZhou : MonoBehaviour {[SerializeField]private …...
MarkDown怎么转pdf;Mark Text怎么使用;
MarkDown怎么转pdf 目录 MarkDown怎么转pdf先用CSDN进行编辑,能双向看版式;标题最后直接导出pdfMark Text怎么使用一、界面介绍二、基本操作三、视图模式四、其他功能先用CSDN进行编辑,能双向看版式; 标题最后直接导出pdf Mark Text怎么使用 Mark Text是一款简洁的开源Mar…...
整合版canal ha搭建--基于1.1.4版本
开启MySql Binlog(1)修改MySql配置文件(2)重启MySql服务,查看配置是否生效(3)配置起效果后,创建canal用户,并赋予权限安装canal-admin(1)解压 canal.admin-1…...
QGIS移动图元功能
有时需要在QGIS里面移动一些矢量图层,比如图层的地理配准,网上搜了一些资料没有查看,后来仔细找了下,在编辑-编辑几何图形-移动要素里面,可以移动图层。 注意:移动前先要选择上要移动的图层,之…...
【模电刷题复习--填空】
如有错误,欢迎各位大佬在评论区批评指正 模电刷题 一、填空题1.本征半导体中,若掺入微量的__五__价元素,则形成___n___型半导体,其多数载流子是自由电子,若掺入微量的__三__价元素,则形成__p__型半导体。其…...
shardingsphere-jdbc-core-spring-boot-starter的性能问题(理论)
hardingSphere-JDBC-core-spring-boot-starter 是 ShardingSphere 提供的与 Spring Boot 集成的模块,用于实现数据库的分库分表等功能。在性能方面,它既有优势也存在一定的挑战,以下是具体分析: 优势方面 数据分片提升查询性能 通…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
