【问题标题】:Most efficient way to compare a memorystream to a file C# .NET将内存流与文件 C# .NET 进行比较的最有效方法
【发布时间】:2010-06-05 01:19:51
【问题描述】:

我有一个包含 PNG 编码图像字节的 MemoryStream,并且想检查磁盘上的目录中是否存在与该图像数据完全相同的副本。第一个明显的步骤是仅查找与确切长度匹配的文件,但在此之后,我想知道将内存与文件进行比较的最有效方法是什么。我在处理流方面不是很有经验。

我对这件事有几个想法:

首先,如果我可以获得文件的哈希码,那么(可能)比较哈希码而不是图像的每个字节会更有效。同样,我可以只比较图像的一些字节,给出“足够接近”的答案。

然后我当然可以只比较整个流,但我不知道会多快。

将 MemoryStream 与文件进行比较的最佳方法是什么?在 for 循环中逐字节?

【问题讨论】:

  • "...仅查找与确切长度匹配的文件..." 警告:磁盘上文件的大小可能会与流的大小不同...磁盘文件可能有一个嵌入的缩略图,内存流没有....图像文件可能有点傻:)
  • 在我的情况下,我也在磁盘上创建图像文件,所以这应该是安全的吗?
  • 是 FileStream.Length == FileInfo.Length... 但是如果您使用 Image.FromFile 并将其保存到 MemoryStream ,它们的长度将不一样...我通常使用 Image 对象,因此我很担心。
  • 有趣。好吧,它似乎到目前为止工作。如果事情开始出现问题,我会记住您的担忧。 :) 谢谢!

标签: c# .net image file comparison


【解决方案1】:

另一种解决方案:

private static bool CompareMemoryStreams(MemoryStream ms1, MemoryStream ms2)
{
    if (ms1.Length != ms2.Length)
        return false;
    ms1.Position = 0;
    ms2.Position = 0;

    var msArray1 = ms1.ToArray();
    var msArray2 = ms2.ToArray();

    return msArray1.SequenceEqual(msArray2);
}

【讨论】:

  • 内存非常关键,但完全适合我对小流的需求。 ;)
  • 为什么设置Position = 0? MemoryStream.ToArray() 文档说“将流内容写入字节数组,无论 Position 属性如何。”
  • 我想只是习惯,在操作流之前一点一点设置位置。
  • 这是一个糟糕的解决方案,除了它需要分配与文件的整个长度一样多的内存(乘以 2)这一事实之外。如果您有两个大文件并且它们在流的开头不同,这将等到两个文件的全部内容都被读取以检测并中止。
  • @MahmoudAl-Qudsi 请求是比较两个内存流,根据定义,它们已经在内存中,而不是文件中。
【解决方案2】:

首先,获取两个流的哈希码无济于事 - 要计算哈希码,您需要读取整个内容在读取时执行一些简单的计算。如果您逐字节比较文件或使用缓冲区,那么您可以更早地停止不匹配的文件(在找到前两个字节/块之后)。

但是,如果您需要将 MemoryStream 与多个文件进行比较,这种方法会很有意义,因为您只需循环一次 MemoryStream(以计算哈希码)并遍历所有文件。

无论如何,您都必须编写代码来读取整个文件。正如您所提到的,这可以逐字节或使用缓冲区来完成。将数据读入缓冲区是个好主意,因为从 HDD 读取数据(例如读取 1kB 缓冲区)可能会更有效。此外,如果您需要并行处理多个文件,您可以使用异步BeginRead 方法。

总结

  • 如果需要比较多个文件,请使用哈希码
  • 读取/比较单个文件的内容:
    • 从两个流中将 1kB 的数据读入缓冲区
    • 看看有没有区别(如果有,退出)
    • 继续循环

如果您需要并行处理多个文件,请使用BeginRead 异步执行上述步骤。

【讨论】:

  • 了解哈希冲突的(不太可能的)可能性很重要。为了避免这个问题,需要进行字节比较。
  • 所以要清楚,我会从文件中读取 1 kb 块到缓冲区中,然后将这些缓冲区逐字节与 memstream 进行比较?
  • BufferedStream 作为 FileStream 的包装器应该解决缓冲问题。
  • 由于磁头的重新定位,同时从同一个 HDD 读取多个文件不一定比一次更有效。
  • @chaiguy:是的,这应该是最有效的选择,尽管如果您使用BufferedStream,逐字节读取也应该可以。您还可以运行一些性能测试来确定最佳缓冲区大小。
【解决方案3】:

我们已经在 NeoSmart Technologies 开源了 a library 来解决这个问题,因为我们不得不多次比较不透明的 Stream 对象的字节相等性。它在 NuGet 上以 StreamCompare 的形式提供,您可以了解它相对于现有方法 in the official release announcement 的优势。

用法非常简单:

var stream1 = ...;
var stream2 = ...;

var scompare = new StreamCompare();
var areEqual = await scompare.AreEqualAsync(stream1, stream2);

编写它是为了尽可能多地抽象出陷阱和性能缺陷,并包含许多优化以加快比较(并最大限度地减少内存使用)。包中还包含一个文件比较包装器FileCompare,可用于按路径比较两个文件。

StreamCompare 在 MIT 许可下发布,可在 .NET Standard 1.3 及更高版本上运行。适用于 .NET Standard 1.3、.NET Standard 2.0、.NET Core 2.2 和 .NET Core 3.0 的 NuGet 包可用。完整的文档在 README 文件中。

【讨论】:

  • 看起来相当复杂,但非常适合比较文件流。对于内存流,我认为这会不太有效
【解决方案4】:

首先,获取两个流的哈希码并没有帮助 - 要计算哈希码,您需要读取整个内容并在读取时执行一些简单的计算。

我不确定我是否误解了它,或者这根本不是真的。这是使用流计算哈希的示例

private static byte[] ComputeHash(Stream data)
{
    using HashAlgorithm algorithm = MD5.Create();
    byte[] bytes = algorithm.ComputeHash(data);
    data.Seek(0, SeekOrigin.Begin); //I'll use this trick so the caller won't end up with the stream in unexpected position
    return bytes;
}

我用 benchmark.net 测量了这段代码,它在 900Mb 文件上分配了 384 个字节。不用说在这种情况下将整个文件加载到内存中是多么低效。

不过,这是真的

了解哈希冲突的(不太可能的)可能性很重要。为了避免这个问题,需要进行字节比较。

因此,如果哈希值不匹配,您必须执行额外的检查以确保文件 100% 不同。在这种情况下,以下是一个很好的方法。

正如您所提到的,这可以逐字节或使用缓冲区来完成。将数据读入缓冲区是个好主意,因为从 HDD 读取数据(例如读取 1kB 缓冲区)可能会更有效。

最近我不得不执行这样的检查,所以我会将这个练习的结果作为 2 个实用方法发布

private bool AreStreamsEqual(Stream stream, Stream other)
{
    const int bufferSize = 2048;
    if (other.Length != stream.Length)
    {
        return false;
    }

    byte[] buffer = new byte[bufferSize];
    byte[] otherBuffer = new byte[bufferSize];
    while ((_ = stream.Read(buffer, 0, buffer.Length)) > 0)
    {
        var _ = other.Read(otherBuffer, 0, otherBuffer.Length);

        if (!otherBuffer.SequenceEqual(buffer))
        {
            stream.Seek(0, SeekOrigin.Begin);
            other.Seek(0, SeekOrigin.Begin);
            return false;
        }
    }
    stream.Seek(0, SeekOrigin.Begin);
    other.Seek(0, SeekOrigin.Begin);
    return true;
}

private bool IsStreamEuqalToByteArray(byte[] contents, Stream stream)
{
    const int bufferSize = 2048;
    var i = 0;
    if (contents.Length != stream.Length)
    {
        return false;
    }
    
    byte[] buffer = new byte[bufferSize];
    int bytesRead;
    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
    {
        var contentsBuffer = contents
            .Skip(i * bufferSize)
            .Take(bytesRead)
            .ToArray();

        if (!contentsBuffer.SequenceEqual(buffer))
        {
            stream.Seek(0, SeekOrigin.Begin);
            return false;
        }
    }
    stream.Seek(0, SeekOrigin.Begin);
    return true;
}

【讨论】:

    【解决方案5】:

    使用Stream我们没有得到结果,每个文件都有一个唯一的标识,比如最后修改日期等等。所以每个文件都是不同的。此信息包含在流中

    【讨论】:

    • 如果您使用流读取文件,您只会读取其内容,而不是文件系统存储的其他元数据。这个问题也特别是关于比较文件的内容。
    猜你喜欢
    • 2018-05-10
    • 1970-01-01
    • 2012-09-16
    • 1970-01-01
    • 1970-01-01
    • 2017-12-25
    相关资源
    最近更新 更多