C#字节数组比较

21

我在使用 .NET 3.0 的 C# 中有两个字节数组。

如何最有效地比较这两个字节数组的每个元素是否包含相同的内容?

例如,字节数组 {0x1, 0x2}{0x1, 0x2} 是相同的。但是字节数组 {0x1, 0x2} 和字节数组 {0x2, 0x1} 不相同。


2
重复的问题:https://dev59.com/c3VD5IYBdhLWcg3wO5ED - Hafthor
6个回答

47

好的,您可以使用:

public static bool ByteArraysEqual(byte[] b1, byte[] b2)
{
    if (b1 == b2) return true;
    if (b1 == null || b2 == null) return false;
    if (b1.Length != b2.Length) return false;
    for (int i=0; i < b1.Length; i++)
    {
        if (b1[i] != b2[i]) return false;
    }
    return true;
}

(我通常对所有内容都使用大括号,但是我想尝试这种布局风格,只是为了改变一下...)

这种方法有一些优化,SequenceEqual不能(或者不会)执行 - 比如提前进行的长度检查。直接访问数组也比使用枚举器更加高效。

诚然,在大多数情况下,这可能不会产生显着差异...

你可以通过在非托管代码中使其每次比较32或64位而不是8位来可能使其更快 - 但我不想即兴编写该代码。


1
对于大型数组,比如10k,是否并行化是一种有效的优化方式,还是线程开销太大了? - Darren
你认为使用 IStructuralEquatable 是否合适? - LCJ
2
@Lijo:我承认我从来没有完全理解过IStructuralEquatable以及何时使用它是合适的。 - Jon Skeet

29
你可以使用 SequenceEqual 方法:
bool areEqual = firstArray.SequenceEqual(secondArray);

如评论中所提到的,SequenceEqual需要 .NET 3.5(或LINQBridge如果您正在使用VS2008并针对较早版本的框架进行目标设置)。


1
我认为最有效的方式。 - Vitaliy Ulantikov
1
不是指执行时间,它并不是。 - Jon Skeet
1
@Veton:这确实是最少打字的方法!请参考Jon的答案,了解一些额外的优化。 - LukeH
7
尝试使用SequenceEqual方法比较一个包含1,000,000个条目的字节数组和一个包含1,000,001个条目的字节数组。 它会一直执行到结尾,然后才注意到它们长度不同... - Jon Skeet
1
这在.NET 3.0中也不起作用(没有像LINQBridge这样的东西),正如问题所需...尽管OP可能是指.NET 3.5。 - Jon Skeet
显示剩余2条评论

5

Jon提到使用不安全的代码一次比较多个字节,所以我不得不试一试:

public unsafe bool ByteArraysEqual(byte[] b1, byte[] b2) {
   if (b1 == b2) return true;
   if (b1 == null || b2 == null) return false;
   if (b1.Length != b2.Length) return false;
   int len = b1.Length;
   fixed (byte* p1 = b1, p2 = b2) {
      int* i1 = (int*)p1;
      int* i2 = (int*)p2;
      while (len >= 4) {
         if (*i1 != *i2) return false;
         i1++;
         i2++;
         len -= 4;
      }
      byte* c1 = (byte*)i1;
      byte* c2 = (byte*)i2;
      while (len > 0) {
         if (*c1 != *c2) return false;
         c1++;
         c2++;
         len--;
      }
   }
   return true;
}

安全代码得到了很好的优化(编译器知道不必检查索引边界),因此我不会指望不安全的代码更快。任何显著的差异都来自于能够一次比较多个字节。


好的概念,虽然代码无法编译:“无法分配给'p1',因为它是一个'固定变量'”。 - Edward Brey
@Edward Brey:你说得对,那样行不通。你需要在块内声明新的指针才能使它们可变。我已经更正了代码。 - Guffa
即使在32位机器上,使用long(8字节)在计算中也更快。 - Joe

4

如果您不太关心性能,可以考虑使用IStructuralEquatable

.NET Framework支持版本:4.5、4

结构相等意味着两个对象相等,因为它们具有相等的值。这与引用相等不同。

示例:

static bool ByteArrayCompare(byte[] a1, byte[] a2) 
{
  IStructuralEquatable eqa1 = a1;
  return eqa1.Equals(a2, StructuralComparisons.StructuralEqualityComparer);
}

参考资料

  1. IStructuralEquatable和IStructuralComparable解决了什么问题?
  2. 为什么IStructuralEquatable和IStructuralComparable不是泛型的?
  3. IStructuralEquatable接口

1
给这篇文章点个赞:当我们使用最近版本的框架时,这可能是一种做事情的方式。 - timmi4sa

2

.NET 6 更新。

目前来看,Enumerable.SequenceEqual 方法仍然是最佳选择

var byteArray1 = new[]{0x01, 0x02};
var byteArray2 = new[]{0x01, 0x02};

bool isEqual = byteArray1.SequenceEqual(byteArray2);

尽管另一种选择也可以直接使用—— ReadOnlySpan<T>.SequenceEqual,例如:
bool isEqual = new Span<byte>(byteArray1).SequenceEqual(new Span<byte>(byteArray2));

在 .NET 6 之前,Span 方法曾经更快,但是微软的开发团队大幅度优化了Enumerable.SequenceEqual 的实现,在底层使用 ReadOnlySpan<T>PR 1PR 2)。有关更多信息,请参见 .NET 6 中的性能改进

2

如果你想让它运行得更快,你可以使用不安全的代码(这并不总是可行):

    public static bool ArraysEqual(byte[] b1, byte[] b2)
    {
        unsafe
        {
            if (b1.Length != b2.Length)
                return false;

            int n = b1.Length;

            fixed (byte *p1 = b1, p2 = b2)
            {
                byte *ptr1 = p1;
                byte *ptr2 = p2;

                while (n-- > 0)
                {
                    if (*ptr1++ != *ptr2++)
                        return false;
                }
            }

            return true;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接