【问题标题】:Binary Search packet in binary file [closed]二进制文件中的二进制搜索包[关闭]
【发布时间】:2017-06-13 03:36:31
【问题描述】:

我有由许多 24 字节数据包组成的二进制文件,其中每个数据包中的前 8 个字节代表DateTime 类型的序列化时间戳。数据包都按时间戳升序排列。我想开发一种二进制搜索算法,它选择前 8 个字节,反序列化时间戳,并将其与所需的时间戳进行比较。

目标是在二进制文件中找到表示与所需时间戳匹配的序列化时间戳的起始位置的位置。

编辑 数据在二进制文件中,而不是在数据结构中,因此List<T>.BinarySearch() 对我不起作用。但是是否可以在 StreamCustomComparer 上使用 BinarySearch

该文件包含数以千万计的此类数据包,因此对文件的简单迭代将非常低效。我考虑使用二分搜索方法。

【问题讨论】:

  • 使用结构列表的问题是你必须在搜索之前读取所有数据,可能只是读取流直到你找到你想要的会更有效率
  • @Codor,也许颜色多一点?
  • 是的,它是按时间戳排序的
  • 我认为二进制搜索的自定义实现是必要的;您可能需要一个可以重新定位阅读光标的流,如here;也许所谓的memory-mapped file 也可以提供帮助。
  • 是的,这就是我的想法,在二进制阅读器的基本流中来回移动位置。我只是希望CustomComparer 可以为我节省二进制搜索实现部分。

标签: c# search serialization binary-search


【解决方案1】:

还没有测试过,但重点是在文件中间读取 8 个字节,而不是向右或向左中间移动并重复,具体取决于读取时间戳。 (不是最干净的代码)。复杂度为 Log(N)

public class BinaryFinder
{
    private readonly long _packagesCount;
    private readonly FileStream _reader;

    public BinaryFinder(FileStream reader, int packageSize)
    {
        _reader = reader;
        _packagesCount = reader.Length / packageSize;
    }


    public long Find(DateTime dateToSearch)
    {
        return Find(0, _packagesCount, dateToSearch);
    }

    private long Find(long minPosition, long maxPosition, DateTime dateToSearch)
    {
        while (minPosition<=maxPosition) {
            var newPosition = (minPosition + maxPosition) / 2;
            var readDate = ReadDateAt(newPosition);

            if (readDate == dateToSearch) {
                return newPosition;
            }

            if (dateToSearch < readDate){
                maxPosition = newPosition-1;
            }
            else {
                minPosition = newPosition+1;
            }
        }

        return -1;
    }

    private DateTime ReadDateAt(long middlePosition)
    {
        var buffer = new byte[8];

        _reader.Seek(middlePosition, SeekOrigin.Begin);
        _reader.Read(buffer, 0, buffer.Length);

        var currentDate = ConvertBytesToDate(buffer);
        return currentDate;
    }

    private static DateTime ConvertBytesToDate(byte[] dateBytes)
    {
        throw new NotImplementedException();
    }
}

【讨论】:

  • 这是递归的,文件中声明的用户可能是数百万个项目,在最坏的情况下不会导致 StackOverflow 吗?
  • @Gusman 它也可以在没有递归的情况下完成。好点
  • 是的,我知道它可以在没有递归和循环的情况下完成,只是在用户尝试的情况下说明问题。
  • @Gusman 不,它不会导致 StackOverflow 异常。二进制搜索的时间复杂度为O(Log n),Log 1000000 为 6,这意味着最多 6 次调用,您就可以找到您的项目!是不是很神奇?
  • @M.kazemAkhgary 是 6,以 10 为基数。在上面的示例中,基数是 2。因此最多大约 20 步
【解决方案2】:

好的,这是代码中的疯狂想法,检查一下,它将返回您正在寻找的时间戳的结构索引。

只需实例化一个FileStructList(fileName),然后执行list.BinarySearchIndexOf(theTimeStamp);

您甚至可以将其传递给您自己的比较器 :)

这包括对代码的二进制搜索,但由于它是 IList,因此您可以使用任何可用于集合的搜索方法。

public class FileStructList : IList<long>
{

    Stream baseStream;
    BinaryReader reader;
    int length;
    int headerSize;

    public FileStructList(string FileName, int HeaderSize)
    {
        baseStream = File.OpenRead(FileName);
        reader = new BinaryReader(baseStream);
        length = (int)((baseStream.Length - HeaderSize) / 24);
        headerSize = HeaderSize;
    }

    public long this[int index]
    {
        get
        {
            baseStream.Seek(24 * index + headerSize, SeekOrigin.Begin);
            return reader.ReadInt64();
        }

        set
        {
            throw new NotImplementedException();
        }
    }

    public int Count
    {
        get
        {
            return length;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return true;
        }
    }

    public void Add(long item)
    {
        throw new NotImplementedException();
    }

    public void Clear()
    {
        throw new NotImplementedException();
    }

    public bool Contains(long item)
    {
        return BinarySearchIndexOf(item) != -1;
    }

    public void CopyTo(long[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public IEnumerator<long> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public int IndexOf(long item)
    {
        return BinarySearchIndexOf(item);
    }

    public void Insert(int index, long item)
    {
        throw new NotImplementedException();
    }

    public bool Remove(long item)
    {
        throw new NotImplementedException();
    }

    public void RemoveAt(int index)
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public Int32 BinarySearchIndexOf(long value, IComparer<long> comparer = null)
    {
        comparer = comparer ?? Comparer<long>.Default;

        Int32 lower = 0;
        Int32 upper = length - 1;

        while (lower <= upper)
        {
            Int32 middle = lower + (upper - lower) / 2;
            Int32 comparisonResult = comparer.Compare(value, this[middle]);
            if (comparisonResult == 0)
                return middle;
            else if (comparisonResult < 0)
                upper = middle - 1;
            else
                lower = middle + 1;
        }

        return -1;
    }
}

【讨论】:

  • 如何计算偏移量? (我将头信息存储在前 10,000 个字节中。)
  • 让我修改代码,很简单,在构造函数中传递偏移量,并将其与seek偏移量相加
  • 这是一个非常简洁的解决方案。谢谢!!!
  • 很高兴你喜欢它:)。您甚至可以扩展它以修改实现集合的时间戳或实现 GetEnumerator 以允许在元素上使用 foreach()。我知道添加 foreach() 支持等对您的最终目标来说效率不高,但如果您想操作文件上的数据,这可能是一个很好的功能。
  • 是的,这就是我喜欢的解决方案,它的可扩展性很强。我之所以这么说是因为我也希望实现数据包插入,但需要检查是否可以调整打开的流的大小,或者如果我想插入会扩大文件大小的元素,是否需要创建一个新文件跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-28
  • 2011-01-24
  • 2016-02-28
  • 2012-01-10
  • 2020-06-09
  • 2014-11-02
  • 1970-01-01
相关资源
最近更新 更多