手機(jī)系統(tǒng)網(wǎng)站windows優(yōu)化大師怎么徹底刪除
最近編寫NTFS文件實(shí)時(shí)搜索工具, 類似 Everything 這樣, 翻閱了很多博客, 結(jié)果大致如下:
1.分析比較膚淺, 采用USN日志枚舉來獲取文件記錄
? ? 速度一言難盡, 因?yàn)槿罩久杜e的是全盤所有文件的所有日志, 記錄比文件記錄還多, 速度當(dāng)然很慢, 還有的甚至于是 使用?DeviceIoControl 函數(shù) 通過 FSCTL_GET_NTFS_FILE_RECORD 參數(shù)類型來獲取全部文件記錄, 我也嘗試了, 其速度一言難盡, 越到后面速度越慢, 跑了幾分鐘我等不下去了手動(dòng)強(qiáng)制終止了
2.雖然也有解析 MFT 記錄的, 但是解析不全面
? ?如果按照他們寫的解析文件記錄, 那么你會(huì)發(fā)現(xiàn): 為啥我的文件記錄數(shù)總是比 Everything 少幾千上萬個(gè), 而且也沒有處理短文件名, 部分文件記錄只有短文件名, 這個(gè)需要解析 屬性列表(屬性類型0x20)來獲取引用記錄, 然后解析引用的記錄來拿到長文件名.
?
?
總體看來就是很多文章解析講解不算全面, 直接拿來用是不可能的, 不僅速度慢, 數(shù)據(jù)還不全, 于是我就花了大量時(shí)間查找資料, 編寫代碼不斷發(fā)現(xiàn)問題并分析問題, 在不斷嘗試于分析之下, 才得到現(xiàn)在的經(jīng)驗(yàn)總結(jié).
?
?
基本準(zhǔn)備
數(shù)據(jù)庫選擇
當(dāng)然是 sqlite3 了,小巧方便, 是非常好用的數(shù)據(jù)庫
數(shù)據(jù)表字段
字段 | 描述 |
id | 文件記錄號(hào),每個(gè)文件本身標(biāo)識(shí) |
parent_id | 文件(夾)所在文件夾的標(biāo)識(shí)號(hào) |
name | 文件名 |
path | 文件完整路徑 |
?
值得一提的是, 我建表使用的是 id, parent_id,name 組成的聯(lián)合主鍵, 這是為啥呢?很多人以為簡單地用一個(gè) id 作為主鍵就行了, 其實(shí)不然, 原因如下?
1.一個(gè)ID可以有多個(gè)父ID
實(shí)際對(duì)比發(fā)現(xiàn)掃描文件數(shù)始終比 everything 的少, 部分文件?everything 能搜出來, 我的確搜不到, 經(jīng)過多次調(diào)試分析發(fā)現(xiàn), 解析 文件名屬性 可以得到同一個(gè) 文件ID 有不同父ID, 查看了這種文件, 發(fā)現(xiàn)是在不同文件夾下的同名同數(shù)據(jù)的文件, 應(yīng)該是一種節(jié)約空間的做法.
2.一個(gè)id + parent_id 標(biāo)識(shí)的記錄項(xiàng)可能存在多個(gè)不同的長文件名
這是我解析時(shí)遇到的最麻煩的一個(gè)坑,因?yàn)榻鉀Q了 一個(gè)ID多個(gè)父ID 記錄后,? 發(fā)現(xiàn)我的掃描記錄數(shù)還是比 Everything 少一些,于是逐個(gè)文件夾比較, 找到了文件數(shù)比較少且搜索數(shù)目不一致的文件夾, 然后修改代碼調(diào)試分析, 發(fā)現(xiàn)?id + parent_id 組合后, 文件名還能不一樣(都是長文件名), 于是我解決了這個(gè)問題, 最后掃描文件數(shù)量終于和 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)
這一步是獲取文件數(shù)據(jù)的唯一迅速且可靠的來源,只需要解析元數(shù)據(jù)文件中的$MFT(第0條文件記錄)的數(shù)據(jù)屬性(屬性類型0x80)即可。
里面需要注意的是,這里的數(shù)據(jù)是一個(gè)dataruns,手動(dòng)解析這個(gè) dataruns 得到文件記錄在驅(qū)動(dòng)器上的位置和大小,讀取這些數(shù)據(jù)(一次可以讀取一個(gè) dataruns 塊,也可以分塊讀取來減少內(nèi)存占用)。
讀取后按照文件記錄(1KB一條記錄)進(jìn)行解析(需要解析屬性列表0x20屬性和文件名屬性0x30),有文件名是短文件名,可以通過從解析屬性列表得到的記錄參考號(hào)來獲取文件記錄來拿到長文件名。
此外,如果文件記錄存在基本文件記錄引用,那么需要把解析的文件記錄的記錄號(hào)改成這個(gè)基本文件記錄號(hào),不然會(huì)出現(xiàn)掃描的文件數(shù)比 Everything 的多。
?
以下是我根據(jù)資料編寫的結(jié)構(gòu)
// 文件引用 https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_reference.html
typedef union _FILE_REFERENCE
{uint64_t Data;struct {uint64_t RecordNumber : 48; // 文件記錄號(hào)uint64_t SequenceNumber : 16; // 序號(hào)};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;// 文件記錄標(biāo)頭 https://flatcap.github.io/linux-ntfs/ntfs/concepts/file_record.html
typedef struct _NTFS_FILE_RECORD_HEADER
{uint32_t MagicNumber; // 幻數(shù)‘FILE’uint16_t UpdateSequenceOffset; // 更新到更新序列的偏移量uint16_t UpdateSequenceSize; // 更新序號(hào)和隊(duì)列的長度(總共為S個(gè)字),為表格最后兩項(xiàng)的大小和(以字為單位),此處值為S的話,最后兩項(xiàng)的大小總和為2S個(gè)字節(jié)。uint64_t LogFileSerialNumber; // 日志文件序列號(hào)($LogFile Sequence Number,LSN)uint16_t SequenceNumber; // 序列號(hào)uint16_t HardLinkCount; // 硬連接數(shù)uint16_t FirstAttributeOffset; // 第一個(gè)屬性的偏移union {uint16_t Data;struct {uint16_t Use : 1; // 記錄正在使用中uint16_t Directory : 1; // 記錄是目錄uint16_t Exension : 1; // 一個(gè) exensionuint16_t SpecialIndex : 1; // 存在特殊索引};}Flags; // 標(biāo)志uint32_t RealSize; // 文件記錄的真實(shí)大小uint32_t AllocatedSize; // 文件記錄的分配大小FILE_REFERENCE BaseFileRecordReference; // 對(duì)基本 FILE 記錄的文件引用uint16_t NextAttributeId; // 下一屬性 IDuint16_t Padding; // 填充uint32_t RecordNumber; // 此 MFT 記錄的編號(hào)uint16_t UpdateSequenceNumber; // 更新序列號(hào)uint16_t UpdateSequenceArray[487]; // 更新序列數(shù)組
}NTFS_FILE_RECORD_HEADER, * PNTFS_FILE_RECORD_HEADER;// 文件標(biāo)志 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 系統(tǒng)uint32_t Unuse : 1; // 0x00000008 未使用uint32_t Directory : 1; // 0x00000010 目錄uint32_t Archive : 1; // 0x00000020 檔案uint32_t Device : 1; // 0x00000040 設(shè)備uint32_t Normal : 1; // 0x00000080 普通uint32_t Temporary : 1; // 0x00000100 臨時(shí)uint32_t SparseFile : 1; // 0x00000200 稀疏uint32_t ReparsePoint : 1; // 0x00000400 重解析點(diǎn)uint32_t Compressed : 1; // 0x00000800 壓縮uint32_t Offline : 1; // 0x00001000 脫機(jī)uint32_t NotContentIndexed : 1; // 0x00002000 無內(nèi)容索引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; // 文件創(chuàng)建時(shí)間uint64_t FileAlteredTime; // 文件修改時(shí)間uint64_t FileChangedTime; // 文件修改時(shí)間uint64_t FileReadTime; // 文件讀取時(shí)間uint64_t AllocatedSize; // 分配大小uint64_t RealSize; // 真實(shí)大小NTFS_FILE_FLAG Flags; // 文件標(biāo)志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;
?
?關(guān)鍵解析的大致邏輯如下:
uint64_t NTFS_Base::GetDataRunUint(uint8_t* pAddr, uint8_t size)
{uint64_t nLength = 0;// 計(jì)算值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;// 計(jì)算值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;}}// 負(fù)數(shù)轉(zhuǎn)換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;// 計(jì)算每簇字節(jié)數(shù)uint64_t BytesPerCluster = (uint64_t)pBootRecord->BytesPerSector * (uint64_t)pBootRecord->SectorsPerCluster;PNTFS_ATTRIBUTE_HEADER pHeader = (PNTFS_ATTRIBUTE_HEADER)pData;static std::mutex mtexAccess;// 主文件表的數(shù)據(jù)是非常駐屬性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;}// 解析數(shù)據(jù)運(yùn)行while (pDataRuns->LengthSize || pDataRuns->OffsetSize){// 計(jì)算長度值與偏移值位置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);// 計(jì)算數(shù)據(jù)偏移, 數(shù)據(jù)量FileRecordOffset += Offset * BytesPerCluster;// 分塊讀取uint64_t DataBlockSize = RecordBufSize;uint64_t BufBlockOffset = FileRecordOffset;uint64_t DataBufSize = Length * BytesPerCluster;// 遍歷解析文件記錄塊while (DataBufSize > 0){// 數(shù)據(jù)塊大小檢查if (DataBufSize < DataBlockSize){DataBlockSize = DataBufSize;}// 設(shè)置讀取偏移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;}// 剩余數(shù)據(jù)量更新DataBufSize -= DataBlockSize;// 數(shù)據(jù)塊偏移更新BufBlockOffset += DataBlockSize;}// 解析下一個(gè)數(shù)據(jù)運(yùn)行位置pDataRuns = (PNTFS_DATA_RUNS)((uint8_t*)pDataRuns + pDataRuns->LengthSize + pDataRuns->OffsetSize + 1);}}if (pRecordBufData){delete[] pRecordBufData;}return true;
}
二. 監(jiān)控 USN 日志
當(dāng)文件發(fā)生變動(dòng)時(shí),同步更新數(shù)據(jù)庫,這里只需要關(guān)注文件創(chuàng)建,刪除,更名即可。
?
可以定義這么一個(gè)結(jié)構(gòu)體存儲(chǔ)日志信息
// 日志更新信息
typedef struct _NTFS_USN_INFO
{_tstring strFileName; // 文件名FILE_REFERENCE ReferenceNumber; // 文件引用號(hào)FILE_REFERENCE ParentReferenceNumber; // 父文件引用號(hào)uint64_t UpdateSequenceNumber; // 更新序列號(hào)uint32_t uDriveIndex; // 卷索引號(hào) 如: 0: C 1: D 3:D 4:ENTFS_USN_REASON Reason; // 更改原因uint32_t FileAttributes; // 文件屬性u(píng)int8_t Namespace; // 命名空間_NTFS_USN_INFO() :ReferenceNumber{0},ParentReferenceNumber{0},UpdateSequenceNumber(0),uDriveIndex(0),Reason{ 0 }{}}NTFS_USN_INFO, * PNTFS_USN_INFO;
?
以下是部分關(guān)鍵邏輯:
// 替換記錄
#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);// 如果此條記錄是文件夾, 則更新子其所有子項(xiàng)路徑if (fDirectory){_UpdateChildPath(fileID, fileParentID);}}_SetUsn(usnInfo.uDriveIndex, usnInfo.UpdateSequenceNumber);if (m_fQuit){break;}}
}
三. 數(shù)據(jù)庫查詢
采用Sqlite3進(jìn)行數(shù)據(jù)庫操作, 以下是部分關(guān)鍵代碼
// 計(jì)數(shù)
#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;
)"// 創(chuàng)建索引
#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;
)"// 創(chuàng)建索引
#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;
)"// 創(chuàng)建表
#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)
);
)"// 建表更新數(shù)據(jù)
#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;}// 檢查搜索是否為全部(關(guān)鍵字全是 * 字符)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("*");}// 轉(zhuǎn)義處理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;
}
?
對(duì)比:
性能上重建數(shù)據(jù)庫耗時(shí)是everything的 3倍, 不過也差不多了, 以后慢慢優(yōu)化
?
?
?
搜索速度比不上everything, 耗時(shí)是其2倍左右,但是也算是秒速了
?
?
?
?
?
?
?
?
?