更喜欢使用Stream到byte[]

本文探讨了在处理大文件时,如何避免滥用byte[] API,转而使用StreamAPI以实现逐批处理,从而减少内存消耗并提高代码效率。通过计算文件哈希和比较文件内容的示例,作者展示了StreamAPI的优势,并提醒开发者在提供API时考虑支持批量处理的Stream选项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

介绍

例子

结论


在审查和重构现实世界的代码库时,我注意到byte[] API是如何被滥用的。这就是为什么在本文中我要分享一些关于为什么不应该在代码中逃避Stream API的想法的原因。

介绍

在处理文件时,通常有两个API同时操作byte[]Stream,因此人们经常选择byte[]对应的API ,因为它需要较少的仪式或直观地更清晰。

您可能认为这个结论牵强,但我决定在审查和重构一些实际生产代码后写下它。因此,您可能会发现在您的代码库中忽略了这个简单的技巧,就像我在之前的文章中提到的其他一些简单的事情一样。

例子

让我们看一个像计算文件哈希一样简单的例子。尽管它很简单,但有些人认为唯一的方法是将整个文件读入内存。

有经验的读者可能已经预见到这种方法的问题。让我们看看对900MB文件做一些基准测试,看看问题是如何表现出来的,以及我们如何规避它。

基线将是从byte[]源计算哈希的天真解决方案:

public static Guid ComputeHash(byte[] data)
{
    using HashAlgorithm algorithm = MD5.Create();
    byte[] bytes = algorithm.ComputeHash(data);
    return new Guid(bytes);
}

因此,按照文章标题的建议,我们将添加另一个方法,该方法将接受Stream将其转换为byte数组并计算哈希。

public async static Task<Guid> ComputeHash(Stream stream, CancellationToken token)
{
    var contents = await ConvertToBytes(stream, token);
    return ComputeHash(contents);
}

private static async Task<byte[]> ConvertToBytes(Stream stream, CancellationToken token)
{
    using var ms = new MemoryStream();
    await stream.CopyToAsync(ms, token);
    return ms.ToArray();
}

然而,从byte[]计算哈希并不是唯一的选择。还有一个接受Stream 的重载。让我们使用它。

public static Guid ComputeStream(Stream stream)
{
    using HashAlgorithm algorithm = MD5.Create();
    byte[] bytes = algorithm.ComputeHash(stream);
    stream.Seek(0, SeekOrigin.Begin);
    return new Guid(bytes);
}

结果很有说服力:

那么这里发生了什么?虽然我们试图盲目地遵循文章中的建议,但并没有帮助。这些图中的关键要点是使用Stream允许我们以块的形式处理文件,而不是天真地将它们加载到内存中。虽然您可能不会在小文件上注意到这一点,但是一旦您必须处理大文件,立即将它们加载到内存中就会变得非常昂贵。

大多数与.NET一起使用byte[]的方法已经展示了Stream对应方法,因此使用它应该没有问题。当您提供自己的API时,您应该考虑提供一种以强大的逐批方式运行Stream的方法。

让我们使用以下代码检查两个stream是否相等来作为另一个示例:

private const int bufferSize = 2048;

public static bool IsEqual(this Stream stream, Stream otherStream)
{
    if (stream is null) return false;

    if (otherStream is null) return false;

    if (stream.Length != otherStream.Length) return false;

    if (stream == otherStream) return true;

    byte[] buffer = new byte[bufferSize];
    byte[] otherBuffer = new byte[bufferSize];

    while (stream.Read(buffer, 0, buffer.Length) > 0)
    {
        otherStream.Read(otherBuffer, 0, otherBuffer.Length);

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

    stream.Seek(0, SeekOrigin.Begin);
    otherStream.Seek(0, SeekOrigin.Begin);

    return true;
}

在这里,我们没有将两个潜在的大文件加载到内存中,而是使用2KB的块来比较它们。一旦块不同,我们就退出。

结论

Stream API允许逐批处理,这使我们能够减少大文件的内存消耗。虽然乍一看,Stream API似乎需要更多的仪式,但它绝对是一个人工具箱中的有用工具。

https://www.codeproject.com/Tips/5308853/Prefer-using-Stream-to-byte

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值