Everything实现,快速搜索文件

news2024/12/22 6:58:28

最近编写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;       // 一个 exension
            uint16_t    SpecialIndex : 1;   // 存在特殊索引
        };
    }Flags;                                     // 标志

    uint32_t        RealSize;                   // 文件记录的真实大小
    uint32_t        AllocatedSize;              // 文件记录的分配大小
    FILE_REFERENCE  BaseFileRecordReference;    // 对基本 FILE 记录的文件引用
    uint16_t        NextAttributeId;            // 下一属性 ID
    uint16_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: POSIX
        struct {
            uint8_t         Win32 : 1;  // 0x01: Win32
            uint8_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:E
    NTFS_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 path
  FROM file_list
  WHERE id = %llu AND parent_id = %llu
  
  UNION ALL
  
  SELECT c.id, c.parent_id, c.name, p.path || '\' || c.name
  FROM file_list c
  INNER 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, name
    FROM file_list
    WHERE id = %llu AND parent_id = %llu AND name = "%s"

    UNION ALL

    SELECT f.id, f.parent_id, f.name, f.name || '\' ||p.path 
    FROM file_list f
    INNER 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 path
  FROM file_list
  WHERE parent_id = 0
  
  UNION ALL
  
  SELECT c.id, c.parent_id, c.name, p.path || '\' || c.name
  FROM file_list c
  INNER 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.coloradmin.cn/o/2263636.html

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

相关文章

Linux环境下使用tomcat+nginx部署若依项目

Linux Tomcat MySQL Java 是构建动态网站系统的完美解决方案之一&#xff0c;具有免费、高 效、扩展性强且资源消耗低等优良特性。 Java Web 凭借其优秀的开发框架和良好的生态被广 泛应用于社会各行业的信息化系统构建。 本实验以若依管理系统&#xff08; http://ruo…

.NET重点

B/S C/S什么语言 B/S&#xff1a; 浏览器端&#xff1a;JavaScript&#xff0c;HTML&#xff0c;CSS 服务器端&#xff1a;ASP&#xff08;.NET&#xff09;PHP/JSP 优势&#xff1a;维护方便&#xff0c;易于升级和扩展 劣势&#xff1a;服务器负担沉重 C/S java/.NET/…

前端HTTP协议传输以及背后的原理总结

一、HTTP在前端的地位 HTTP 是一种用作获取诸如 HTML 文档这类资源的协议。它是 Web 上进行任何数据交换的基础&#xff0c;同时&#xff0c;也是一种客户端—服务器&#xff08;client-server&#xff09;协议&#xff0c;也就是说&#xff0c;请求是由接受方——通常是…

城市应急指挥系统

城市应急指挥系统的重要性 随着现代化城市的高速发展&#xff0c;我们面临着多种应急突发情景&#xff0c;如自然灾害、事故灾难、公共卫生事件以及社会安全事件等。这些事件对城市的安全稳定构成严重威胁&#xff0c;因此&#xff0c;建立一套高效、全面的城市应急指挥系统显…

【软考高级】系统架构设计师复习笔记-精华版

文章目录 前言0 系统架构设计师0.1 考架构还是考系分0.2 架构核心知识0.3 架构教材变化 1 计算机操作系统1.1 cpu 组成1.2 内核的五大功能1.3 流水线技术1.4 段页式存储1.5 I/O 软件1.6 文件管理1.7 系统工程相关 2 嵌入式2.1 嵌入式技术2.2 板级支持包&#xff08;BSP&#xf…

NSDT 3DConvert:高效实现大模型文件在线预览与转换

NSDT 3DConvert 作为一个 WebGL 展示平台&#xff0c;能够实现多种模型格式免费在线预览&#xff0c;并支持大于1GB的OBJ、STL、GLTF、点云等模型进行在线查看与交互&#xff0c;这在3D模型展示领域是一个相当强大的功能。 平台特点 多格式支持 NSDT 3DConvert兼容多种3D模型…

STL 剖析

STL 六大组件 「STL 六大组件的交互关系」 Container 通过 Allocator 取得数据储存空间Algorithm 通过 Iterator 存取 Container 内容Functor 可以协助 Algorithm 完成不同的策略变化Adapter 可以修饰或套接 Functor、Iterator 配置器(allocator) 配置器&#xff1a;负责空间…

买卖股票的最佳时机 IV - 困难

************* C topic&#xff1a;188. 买卖股票的最佳时机 IV - 力扣&#xff08;LeetCode&#xff09; ************* Stock angin: Still stocks. Intuitively, it feels hard. For once: class Solution { public:int maxProfit(vector<int>& prices) {in…

#{ }和${ } 、参数处理

目录 #{ }和${ } 参数处理 入参-parameterType 使用Map 使用Bean / List<Bean> 使用多参数 Param注解&#xff08;命名参数&#xff09; 出参-resultType 返回Map / List<Map> 返回Map<String,Map> 属性名不一致-resultMap结果映射 #{ }和${ }…

机器学习探索之旅:开启智能预测的新篇章!!! 笔记 ! ! !)

目录 一 . 机器学习基础&#xff1a; 1. 什么是机器学习&#xff1a; Langley&#xff08;1996&#xff09;的定义&#xff1a; Tom Mitchell&#xff08;1997&#xff09;的定义&#xff1a; 冷雨泉&#xff08;等&#xff09;的观点&#xff1a; 2. 机器学习与人工智能…

【RAII | 设计模式】C++智能指针,内存管理与设计模式

前言 nav2系列教材&#xff0c;yolov11部署,系统迁移教程我会放到年后一起更新&#xff0c;最近年末手头事情多&#xff0c;还请大家多多谅解。 上一节我们讲述了C移动语义相关的知识&#xff0c;本期我们来看看C中常用的几种智能指针&#xff0c;并看看他们在设计模式中的运…

基于单片机的病房呼叫系统设计

摘 要&#xff1a; 文章基于 51 系列的单片机设计的病房呼叫系统 。 在以 AT89C51 单片机为核心&#xff0c;以 74HC573 锁存器 、数码管显示模块、 矩阵按键模块等为辅组成的&#xff0c;按键分布在各个病床的床头&#xff0c;可以节约接口资源&#xff0c;当按下按键&a…

编译原理复习---目标代码生成

适用于电子科技大学编译原理期末考试复习。 1. 目标代码 是目标机器的汇编代码或机器码&#xff0c;在本课程中指的是类似于汇编代码的一种形式&#xff0c;由一条条的指令构成目标代码。 抽象机指令格式&#xff1a;OP 目的操作数&#xff0c;源操作数。 我们要做的&…

Redis数据对象

基本结构图 key和value指向的是redisObject对象 type&#xff1a;标识该对象用的是什么类型&#xff08;String、List Redis数据结构 SDS SDS有4个属性&#xff1a; len&#xff1a;记录了字符串长度&#xff0c;因此获取字符串长度的时候时间复杂度O&#xff08;1&#xff…

Gale-Shapley算法

一. 设计目的 盖尔-沙普利算法&#xff08;Gale-Shapley算法&#xff09;的设计目的是为了解决稳定匹配问题&#xff0c;即在给定一组男性和女性的偏好列表的情况下&#xff0c;找到一个稳定的匹配。这里的“稳定”指的是不存在任何一对男性和女性&#xff0c;他们彼此都比当前…

JWT令牌与微服务

1. 什么是JWT JWT&#xff08;JSON Web Token&#xff09;是一种开放标准(RFC 7519)&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于作为JSON对象在各方之间安全地传输信息。JWT通常用于身份验证和信息交换。 以下是JWT的一些关键特性&#xff1a; 紧凑&#xff…

视频点播系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…

docker部署Melody开源音乐管理工具

Melody是一款非常实用的开源音乐管理工具。它不仅功能强大、操作简便&#xff0c;还支持多平台检索和一键下载/上传功能。更重要的是&#xff0c;它还支持一键“解锁”无法播放的歌曲和多端适配。如果你也是音乐爱好者&#xff0c;不妨试试Melody&#xff0c;让你的音乐生活更加…

问题小记-达梦数据库报错“字符串转换出错”处理

最近遇到一个达梦数据库报错“-6111: 字符串转换出错”的问题&#xff0c;这个问题主要是涉及到一条sql语句的执行&#xff0c;在此分享下这个报错的处理过程。 问题表现为&#xff1a;一样的表结构和数据&#xff0c;执行相同的SQL&#xff0c;在Oracle数据库中执行正常&…

day4:tomcat—maven-jdk

一&#xff0c;java项目部署过程 编译&#xff1a;使用javac命令将.java源文件编译成.class宇节码文件打包&#xff1a;使用工具如maven或Gradle将项目的依赖、资源和编译后的字节码打包成一个分发格式&#xff0c;如.jar文件&#xff0c;或者.war文件(用于web应用&#xff09…