【问题标题】:Binary search a byte array二进制搜索字节数组
【发布时间】:2021-12-01 05:17:04
【问题描述】:

我有一个包含键/值对排序列表的文件。键是固定长度的字符串,值是整数的二进制表示。因此,如果密钥大小为 6,则每条记录正好是 10 个字节(6+sizeof(int))。密钥大小可以是任何(合理的)值,但对于文件中的每条记录都是相同的。

例如,数据文件可能如下所示(其中 'xxxx' 是 int 值的二进制表示):

ABLE  xxxxBAKER xxxxCARL  xxxxDELTA xxxxEDGAR xxxxGEORGExxxx

我想将此表加载到内存中(一个字节[])并对其进行二进制搜索。在 C++ 中,这很简单,bsearch() 函数接受一个“元素大小”参数,告诉它缓冲区中记录的大小,但我在 C# 中找不到类似的函数。

我也看过 Array.BinarySearch,但我不知道我会用什么作为 T。

【问题讨论】:

  • 如果要将它们组织成byte[]的数组(即字节数组的数组),那么Array.BinarySearch<T>()中的T参数就是byte[]
  • 我假设您的意思是“二进制搜索”是指寻找未排序的二进制数据,而不是 en.wikipedia.org/wiki/Binary_search_algorithm
  • 如果是前者,那么您可以使用 Boyer-Moore 的实现,例如我在此处发布的示例:stackoverflow.com/a/37500883/106159
  • @MatthewWatson,不,它已排序。我添加了一个文件外观示例。
  • 在 C# 中这不是一个简单的方法,因为这些字节的布局方式与托管类型的布局方式不对应。可以(比如说)声明一个struct,它或多或少准确地覆盖它并使用Spans 来访问它,但这充其量会减少内存使用,并且仍然会产生解码字符串和比较它们的开销,所以它通常不会胜过简单地解析文件——“二进制”并没有给它任何特殊的地位。

标签: c#


【解决方案1】:

您可能应该使用binary reader 将您的二进制数据转换为正确的.net 类型。由于 c# 是一种托管语言,并且类型的内存表示可能取决于运行时,因此读取和写入数据通常比将部分内存转储到磁盘更复杂。

您提到“固定长度字符串”,我假设您的意思是它是 ascii 编码的。 C# 字符串是 UTF16,因此您可能需要 convert it 以允许以任何有意义的方式使用它。另一种读取整数数据的方法请参见bitconverter

一旦您将数据转换为正确的 c# 类型,进行二分搜索应该相当简单,只需将键和值放在单独的数组中并使用 Array.BinarySearch。或者使用实现IComparable<T>的自定义类型。

或者将数据放入其他数据结构中,例如字典,以便快速搜索。

【讨论】:

    【解决方案2】:

    我们可以修改标准的二分搜索算法来做到这一点。

    我们需要修改它来引用Spans中你想要的宽度的数组。所以我们将索引乘以那个宽度。

    delegate int BytesComparer(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b);
    
    int BinarySearchRecord(byte[] array, int recordWidth, ReadOnlySpan<byte> value, BytesComparer comparer)
    {
        return BinarySearchRecord(array, 0, array.Length / recordWidth, value, recordWidth, comparer);
    }
    
    int BinarySearchRecord(byte[] array, int startRecord, int count, int recordWidth, ReadOnlySpan<byte> value, BytesComparer comparer)
    {
        int lo = startRecord;
        int hi = startRecord + count - 1;
        while (lo <= hi)
        {
            int i = lo + ((hi - lo) >> 1);
            int order = comparer(new ReadOnlySpan<byte>(array, i * recordWidth, recordWidth), value);
     
            if (order == 0) return i;
            if (order < 0)
                lo = i + 1;
            else
                hi = i - 1;
        }
     
        return ~lo;
    }
    

    您可以像这样使用它(它需要在 little-endian 系统上进行一些操作,以便正确排序字符串)

    var yourArray = Encoding.ASCII.GetBytes("ABLE  xxxxBAKER xxxxCARL  xxxxDELTA xxxxEDGAR xxxxGEORGExxxx");
    
    var valToFind = Encoding.ASCII.GetBytes("DELTA ").Concat(new byte[]{0x1, 0x2, 0x3, 0x4}).ToArray();
    
    var foundIndex = BinarySearchRecord(yourArray, valToFind.Length, new ReadOnlySpan<byte>(valToFind), (a, b) =>
        {
            return BinaryPrimitives.ReverseEndianness(BitConverter.ToInt64(a) & 0xffffffffffff)
                .CompareTo(
                   BinaryPrimitives.ReverseEndianness(BitConverter.ToInt64(b) & 0xffffffffffff));
        });
    

    dotnetfiddle

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多