使用 c# 将 FileStream 编码为 base64
Posted
技术标签:
【中文标题】使用 c# 将 FileStream 编码为 base64【英文标题】:Encode a FileStream to base64 with c# 【发布时间】:2013-10-08 16:02:04 【问题描述】:我知道如何将一个简单的字符串编码/解码到/从 base64。
但是,如果数据已经写入 FileStream 对象,我该怎么做。假设我只能访问 FileStream 对象,而不能访问其中先前存储的原始数据。在将 FileStream 刷新到文件之前,如何将 FileStream 编码为 base64。
Ofc 我可以在将 FileStream 写入文件后打开我的文件并对其进行编码/解码,但我想一步完成这一切,而不是一个接一个地执行两个文件操作。该文件可能更大,并且在刚刚保存不久之后再次加载、编码和保存它也需要双倍的时间。
也许你们当中有人知道更好的解决方案?例如,我可以将 FileStream 转换为字符串,对字符串进行编码,然后将字符串转换回 FileStream,或者我会做什么以及这样的代码会是什么样子?
【问题讨论】:
我不确定我是否完全理解您的问题,但可以使用内置类来提供将二进制数据转换为基数 64 数据或从基数 64 数据转换的流。然后,您可以在写入和文件输出流之间插入这样的流(例如通常通过压缩流和加密流来完成)。一个例子在这里:netpl.blogspot.co.uk/2011/05/builtin-base64-streaming.html How to convert an Stream into a byte[] in C#?的可能重复 Is there a Base64Stream for .NET? where?的可能重复 this 不是答案吗? 别忘了:stream.Seek(0, SeekOrigin.Begin);在方法的开头... ;-) 【参考方案1】:一个简单的 Stream 扩展方法就可以完成这项工作:
public static class StreamExtensions
public static string ConvertToBase64(this Stream stream)
if (stream is MemoryStream memoryStream)
return Convert.ToBase64String(memoryStream.ToArray());
var bytes = new Byte[(int)stream.Length];
stream.Seek(0, SeekOrigin.Begin);
stream.Read(bytes, 0, (int)stream.Length);
return Convert.ToBase64String(bytes);
读取(以及写入)的方法并针对相应的类(无论是文件流、内存流等)进行了优化,并将为您完成工作。对于这样简单的任务,不需要阅读器等。
唯一的缺点是流被复制到字节数组中,但不幸的是,这就是通过 Convert.ToBase64String 转换为 base64 的方式。
【讨论】:
这不是一个通用的解决方案,因为许多流类型不支持 Length 或 Seek() Rhys Bevilaqua,通常您需要寻找到流的开头来阅读全部内容,或者“知道”您在开头(这违反了 SOLID 原则)。只有流不实现这两种方法 - 几乎所有其他方法(内存、文件等)都有它。你总是可以有第二个实现,你逐个缓冲区读取直到流的“结束”,但这不是那么有效和直接。 我厌倦了在 .NET 中将所有内容缓冲到 byte[] 中。这是非常浪费的。现在是端到端流 API 的最佳时机。 更新了一点以支持 MemoryStream 类的 ToArray() 方法。【参考方案2】:在处理大型流时,例如超过 4GB 的文件 - 您不想将文件加载到内存中(作为 Byte[]
),因为它不仅非常慢,而且甚至可能导致崩溃在 64 位进程中,Byte[]
不能超过 2GB(或 4GB,gcAllowVeryLargeObjects
)。
幸运的是,.NET 中有一个名为ToBase64Transform
的简洁助手,它以块的形式处理流。出于某种原因,Microsoft 将其放入 System.Security.Cryptography
并实现了 ICryptoTransform
(与 CryptoStream
一起使用),但忽略这一点(“任何其他名称的玫瑰......”)只是因为你没有执行任何密码学任务。
您可以像这样将它与CryptoStream
一起使用:
using System.Security.Cryptography;
using System.IO;
//
using( FileStream inputFile = new FileStream( @"C:\VeryLargeFile.bin", FileMode.Open, FileAccess.Read, FileShare.None, bufferSize: 1024 * 1024, useAsync: true ) ) // When using `useAsync: true` you get better performance with buffers much larger than the default 4096 bytes.
using( CryptoStream base64Stream = new CryptoStream( inputFile, new ToBase64Transform(), CryptoStreamMode.Read ) )
using( FileStream outputFile = new FileStream( @"C:\VeryLargeBase64File.txt", FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 1024 * 1024, useAsync: true ) )
await base64Stream.CopyToAsync( outputFile ).ConfigureAwait(false);
【讨论】:
注意 leaveOpen 属性在 netstandard2.0 中无效,但在 472 中被接受 这几乎是完美的,除了生成的流不支持我需要的 Seek。我发誓我每天都在使用 C# System lib,我发现我需要正确地重新实现一些东西。 @YarekT 如果您使用的是流,那么您永远不需要寻找(唯一的例外是FileStream
)。如果您发现自己需要在非磁盘流中查找,那么您的系统可能设计错误。
@Dai 有趣。我正在使用FluentFtp
的UploadAsync
方法,该方法采用流。我以为它不会寻找,但它确实如此。我猜他们是为常规文件流设计的。
@YarekT 根据这个问题,这是因为 FluentFTP 需要事先知道流的长度(这是合理的),但是它通过要求流可搜索来做到这一点(这是错误的 - 但结果System.IO.Stream
的设计不会让它暴露 Length
除非 CanSeek == true
, grrr): github.com/robinrodricks/FluentFTP/issues/668【参考方案3】:
一个简单的扩展方法
public static class Extensions
public static Stream ConvertToBase64(this Stream stream)
byte[] bytes;
using (var memoryStream = new MemoryStream())
stream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
string base64 = Convert.ToBase64String(bytes);
return new MemoryStream(Encoding.UTF8.GetBytes(base64));
【讨论】:
这会导致stream
被完全缓冲到内存中(也有多个副本,因为您没有设置初始capacity
)。对于大于几兆字节的文件,这不是一个实用的解决方案 - 并且对于大于 2GB 的文件肯定会中断(因为MemoryStream
在内部使用单个Byte[]
)。人们还报告 MemoryStream
超过 256MB 的大小:***.com/questions/15595061/…
@Dai 我认为,如果您尝试对大流进行 base64 编码,也许有比 base64 编码更好的选择?我这样做是为了在 JSON Web 请求中提供一个文件,如果有一个大文件 (MBs+),那么这样做对我来说没有意义。
这个解决方案浪费了大量的内存(几次)和cpu时间。它可以做得非常优化!
@VasilPopov 请发布您的最佳解决方案。
@PhillipCopley 我在下面做了几篇文章【参考方案4】:
你可以尝试类似的东西:
public Stream ConvertToBase64(Stream stream)
Byte[] inArray = new Byte[(int)stream.Length];
Char[] outArray = new Char[(int)(stream.Length * 1.34)];
stream.Read(inArray, 0, (int)stream.Length);
Convert.ToBase64CharArray(inArray, 0, inArray.Length, outArray, 0);
return new MemoryStream(Encoding.UTF8.GetBytes(outArray));
【讨论】:
1.34从何而来? 一个字节包含 8 位。 base64 不使用字节,而是使用字符。不是任何字符,而是可以转换为 6 位的特定字符。所以 in-array 比 our-array 小了 6/8 倍。 8 除以 6 是 1,33333,所以如果你取 1.34,输出数组总是足够大。 您需要从Convert.ToBase64CharArray
获取新尺寸,然后执行Array.Resize<Char>(ref base64Chars, newSize);
。否则,最终输出中会有额外的字节。
1.34 错了!额外的 0.0333 为您提供了一些空间,这对于小长度来说太小了,而对于大数组来说则不必要的大。你应该做一个天花板(int)Math.Ceiling(stream.Length * 8.0 / 6.0)
,而不是地板(铸成int),这样你就可以得到确切的长度。
请注意,如果stream
恰好是MemoryStream
,您可以只使用stream.ToArray()
并避免8/6 计算。【参考方案5】:
您还可以将字节编码为 Base64。如何从流中获取此信息,请参见此处:How to convert an Stream into a byte[] in C#?
或者我认为应该也可以使用 .ToString() 方法并对其进行编码。
【讨论】:
由于输入是流,更有用的答案是将流转换为另一个(B64 编码)流的答案。【参考方案6】:由于文件会更大,因此您在如何执行此操作方面没有太多选择。您无法就地处理文件,因为这会破坏您需要使用的信息。我可以看到您有两个选择:
-
读入整个文件,base64编码,重写编码数据。
以较小的片段读取文件,同时进行编码。编码为同一目录中的临时文件。完成后,删除原始文件,并重命名临时文件。
当然,流的全部意义在于避免这种情况。与其创建内容并将其填充到文件流中,不如将其填充到内存流中。然后对其进行编码,然后保存到磁盘。
【讨论】:
以上是关于使用 c# 将 FileStream 编码为 base64的主要内容,如果未能解决你的问题,请参考以下文章