二进制搜索文件与不同的行长度

问题描述 投票:2回答:1

我有一些代码在每行上对带有排序十六进制值(SHA1哈希)的文件进行二进制搜索。这用于搜索HaveIBeenPwned数据库。最新版本包含每个密码哈希的查找次数,因此某些行末尾有额外的字符,格式为':###'

这个附加检查的长度不固定,并不总是存在。这会导致缓冲区读取不正确的值,并且无法找到实际存在的值。

当前代码:

static bool Check(string asHex, string filename)
{
    const int LINELENGTH = 40;  //SHA1 hash length

    var buffer = new byte[LINELENGTH];
    using (var sr = File.OpenRead(filename))
    {
        //Number of lines
        var high = (sr.Length / (LINELENGTH + 2)) - 1;
        var low = 0L;

        while (low <= high)
        {
            var middle = (low + high + 1) / 2;
            sr.Seek((LINELENGTH + 2) * ((long)middle), SeekOrigin.Begin);
            sr.Read(buffer, 0, LINELENGTH);
            var readLine = Encoding.ASCII.GetString(buffer);
            switch (readLine.CompareTo(asHex))
            {
                case 0:
                    return true;

                case 1:
                    high = middle - 1;
                    break;

                case -1:
                    low = middle + 1;
                    break;

                default:
                    break;
            }
        }
    }
    return false;
}

我的想法是从中间向前寻找直到找到换行符,然后向后寻找同一点,这应该给我一个完整的行,我可以通过':'分隔符分割。然后我比较分裂字符串数组的第一部分,它应该只是一个SHA1哈希。

我认为这仍然应该以正确的价值为中心,但我想知道是否有更简洁的方法来做到这一点?如果中点不是行尾字符之间的实际中点,是否应该在高值和低值之前进行调整?

c# binary-search
1个回答
2
投票

我认为这可能是一个更简单(更快)的解决方案,没有回溯到行的开头。我认为你可以使用字节文件索引,而不是尝试使用完整的“记录/行。因为中间索引不会总是在行/记录的开头,”readline“可以返回部分行/记录如果你要立即做第二个“readline”,你会得到一个完整的行/记录。这不是最佳的,因为你实际上会比中间索引略微一点。

我下载了pwned-passwords-update-1并在开始,结束和中间提取了大约30条记录,它似乎找到了所有记录。你怎么看?

const int HASHLENGTH = 40;

static bool Check(string asHex, string filename)
{
    using (var fs = File.OpenRead(filename))
    {
        var low = 0L;
        // We don't need to start at the very end
        var high = fs.Length - (HASHLENGTH - 1); // EOF - 1 HASHLENGTH

        StreamReader sr = new StreamReader(fs);

        while (low <= high)
        {
            var middle = (low + high + 1) / 2;
            fs.Seek(middle, SeekOrigin.Begin);

            // Resync with base stream after seek
            sr.DiscardBufferedData();

            var readLine = sr.ReadLine();

            // 1) If we are NOT at the beginning of the file, we may have only read a partial line so
            //    Read again to make sure we get a full line.
            // 2) No sense reading again if we are at the EOF
            if ((middle > 0) && (!sr.EndOfStream)) readLine = sr.ReadLine() ?? "";

            string[] parts = readLine.Split(':');
            string hash = parts[0];

            // By default string compare does a culture-sensitive comparison we may not be what we want?
            // Do an ordinal compare (0-9 < A-Z < a-z)
            int compare = String.Compare(asHex, hash, StringComparison.Ordinal);

            if (compare < 0)
            {
                high = middle - 1;
            }
            else if (compare > 0)
            {
                low = middle + 1;
            }
            else
            {
                return true;
            }
        }
    }
    return false;
}
© www.soinside.com 2019 - 2024. All rights reserved.