如何比较二进制格式(字节数组)中的两个“long”值类型?

时间:2018-02-23 11:19:01

标签: c# serialization binary compare

我有大量(> 5gb)二进制文件,其中包含每个文件中固定偏移量的二进制格式的长数据类型(以及其他数据)。我有一个很长的查找值,并希望实现二进制搜索算法,以查找文件中查找值匹配,大于或小于最近的长值的位置。自定义二进制搜索算法不是问题,

...但我正在寻找帮助来比较两个二进制数组,以确定等效的长值是等于,大于还是小于查找值。显然,我想摆脱不必将二进制数组反序列化为长,否则问题就没有实际意义了。

我可以比较二进制数组并确定代表性的长值是等于,小于还是更大而无需从二进制数组反序列化/转换为long?解决方案应该比类型转换更快,并且占用内存更小。

2 个答案:

答案 0 :(得分:2)

我真正的答案是:

  • 为了获得足够好的效果,请使用BitConverter.ToInt64
  • 为了获得更好的性能,请滚动您自己的逐字节比较

这是第二种选择,这似乎相当快。您将获得从所有CompareTo方法获得的正常-1,0,+ 1类型的值,这意味着:

  • a vs. b - > -1表示a小于b
  • a vs. b - > 0表示a等于b
  • a vs. b - > +1表示a大于b

代码:

public static int Compare(byte[] a, byte[] b)
{
    bool aIsNegative = (a[7] & 0x80) != 0;
    bool bIsNegative = (b[7] & 0x80) != 0;

    if (aIsNegative != bIsNegative)
    {
        if (aIsNegative)
            return -1;
        return +1;
    }

    var a7 = a[7] & 0x7f;
    var b7 = b[7] & 0x7f;

    if (a7 < b7) return -1;
    if (a7 > b7) return +1;

    var a6 = a[6]; var b6 = b[6];
    if (a6 < b6) return -1;
    if (a6 > b6) return +1;

    var a5 = a[5]; var b5 = b[5];
    if (a5 < b5) return -1;
    if (a5 > b5) return +1;

    var a4 = a[4]; var b4 = b[4];
    if (a4 < b4) return -1;
    if (a4 > b4) return +1;

    var a3 = a[3]; var b3 = b[3];
    if (a3 < b3) return -1;
    if (a3 > b3) return +1;

    var a2 = a[2]; var b2 = b[2];
    if (a2 < b2) return -1;
    if (a2 > b2) return +1;

    var a1 = a[1]; var b1 = b[1];
    if (a1 < b1) return -1;
    if (a1 > b1) return +1;

    var a0 = a[0]; var b0 = b[0];
    if (a0 < b0) return -1;
    if (a0 > b0) return +1;

    return 0;
}

显然如果您想使用此代码,您应该编写一些非常好的单元测试。在你做之前不要相信这段代码。

请注意,只有在使用little-endian字节顺序对数据进行编码时,此方法才能正常工作。

如果你需要使用big-endian字节顺序,反转方法中的字节顺序,使7变为0,6变为1,5变为2,等等。如果你读取方法,它从7变为0 0,这是小端的。对于big-endian,它从0到7。

有符号的64位数字,如Int64,以第64位(位#63)表示符号的方式编码,1表示负数,0表示正数。这就是为什么在检查符号位后,处理的第一个字节需要被屏蔽到只有7位。

我测试了以下方法:

  • 使用带[StructLayout(LayoutKind.Explicit, Pack=1)]的结构,并在8字节字段之上显式覆盖Int64字段。出乎我的意料,这确实比所有替代方案都要糟糕。我猜测所有字节到结构中的混乱都会给它带来更多的开销。
  • 使用BitConvert.ToInt64,令我惊讶的是,这比我预期的要好得多,所以我的建议实际上是为了这个,除非你发现真的有性能问题,这就是最顶层的瓶颈
  • 字节比较,如果第一个字节相等,则仅转到下一个字节。这似乎表现最好

使用Benchmark.NET的整个代码示例可以在this gist中找到。

以下是10000个测试用例的基准测试结果,上面的要点还有更多,包括完整的基准测试日志:

              Method |     N |          Mean |         Error |        StdDev |
-------------------- |------ |--------------:|--------------:|--------------:|
    StructConversion | 10000 | 171,900.82 ns |   544.7975 ns |   482.9487 ns |
         ByteForByte | 10000 | 140,844.09 ns | 1,263.6139 ns | 1,181.9851 ns |
 ByteForByteUnrolled | 10000 | 125,728.87 ns |   377.4937 ns |   315.2243 ns |
   UsingBitConverter | 10000 | 130,397.25 ns |   497.1728 ns |   465.0557 ns |

注意对于1000个元素,ByteForByte和ByteForByteUnrolled结果相反。我在运行时确实使用了我的计算机,因此这可能会对结果产生影响。买家要小心。

答案 1 :(得分:1)

如何使用结构?

public class Program
{

    [StructLayout(LayoutKind.Explicit)]
    struct ValueStruct
    {
        [FieldOffset(0)]
        public byte byte1;

        [FieldOffset(1)]
        public byte byte2;

        [FieldOffset(2)]
        public byte byte3;

        [FieldOffset(3)]
        public byte byte4;

        [FieldOffset(0)]
        public uint uint1;

    }

    static void Main(string[] args)
    {
        var value1 = new ValueStruct() { byte1 = 0x88, byte2 = 0x99, byte3 = 0xAA, byte4 = 0xBB };
        var value2 = new ValueStruct() { byte1 = 0x11, byte2 = 0x22, byte3 = 0x33, byte4 = 0x44 };
        Console.WriteLine(value1.uint1);
        Console.WriteLine(value2.uint1);
        if (value1.uint1 > value2.uint1)
        {
            Console.WriteLine("value1 is greater than value2");
        }

        Console.ReadLine();
    }
}