使用c#将FileStream编码为base64

fee*_*all 29 c# string base64 file filestream

我知道如何编码/解码一个简单的字符串到/从base64.

但是,如果数据已经写入FileStream对象,我该怎么做呢?假设我只能访问FileStream对象,而不能访问之前存储的原始数据.在将FileStream刷新到文件之前,如何将FileStream编码为base64.

在我将FileStream写入文件之后,我可以打开我的文件并对其进行编码/解码,但我想一步一步完成所有这一切,而不是一个接一个地执行两个文件操作.该文件可能更大,并且在刚刚保存一段时间后再加载,编码并再次保存它也需要两倍的时间.

也许有人认识一个更好的解决方案?我可以将FileStream转换为字符串,对字符串进行编码,然后将字符串转换回FileStream,例如,我将做什么以及这样的代码将如何?

Dai*_*Dai 30

在处理大流时,例如大小超过 4GB 的文件 - 您不想将文件加载到内存中(作为Byte[]),因为它不仅非常慢,而且可能导致崩溃,即使在 64 位进程中Byte[]不能超过 2GB(或 4GB 与gcAllowVeryLargeObjects)。

幸运的是,.NET 中有一个巧妙的帮助程序ToBase64Transform,它可以分块处理流。出于某种原因,微软将它放入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);
}
Run Code Online (Sandbox Code Playgroud)

  • 注意leaveOpen属性在netstandard2.0中无效,但在472中被接受 (3认同)
  • @YarekT如果您使用流,那么您永远不需要寻找(唯一的例外是“FileStream”)。如果您发现自己需要在非磁盘流中查找,那么您的系统可能设计错误。 (2认同)
  • @YarekT根据这个问题,这是因为FluentFTP需要事先知道流的长度(这是合理的),但是它通过要求流是可搜索的来做到这一点(这是错误的 - 而是“System.IO”设计的结果.Stream` 不会让它暴露 `Length` 除非 `CanSeek == true`,grrr):https://github.com/robinrodricks/FluentFTP/issues/668 (2认同)

chr*_*389 18

一个简单的扩展方法

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这会导致“stream”完全缓冲到内存中(也有多个副本,因为您没有设置初始“capacity”)。对于大于几兆字节的文件来说,这不是一个实用的解决方案 - 并且对于大于 2GB 的文件肯定会崩溃(因为“MemoryStream”在内部使用单个“Byte[]”)。人们还报告“MemoryStream”在大小超过 256MB 时中断:/sf/ask/1091654301/ (5认同)
  • 这个解决方案浪费了大量的内存(几次)和CPU时间。它可以做得非常优化! (3认同)
  • @VasilPopov 请发布您的最佳解决方案。 (2认同)

Jac*_*cob 15

您可以尝试一些类似的是:

    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));
    }
Run Code Online (Sandbox Code Playgroud)

  • 1.34来自哪里? (7认同)
  • 1.34错了!额外的0.0333为您提供了一些空间,这个空间对于小长度来说太小而对于大型阵列来说是不必要的.你应该做一个天花板`(int)Math.Ceiling(stream.Length*8.0/6.0)`而不是地板(强制转换为int),这样你就得到了确切的长度. (6认同)
  • 一个字节保存8位.base64不使用字节而是使用字符.不是任何字符,而是可以转换为6位的特定字符.因此,in-array比我们的数组小6/8.8除以6是1,33333所以如果你拿1.34,那么out数组总是足够大. (4认同)
  • 你需要从`Convert.ToBase64CharArray`获得新的大小,然后执行`Array.Resize <Char>(ref base64Chars,newSize);`.否则,在最终输出中有额外的字节. (2认同)
  • 请注意,如果“stream”恰好是“MemoryStream”,则可以仅使用[`stream.ToArray()`](https://learn.microsoft.com/en-us/dotnet/api/system.io。 memorystream.toarray?view=netframework-4.7.2) 并避免 8/6 计算。 (2认同)

alp*_*am8 11

您还可以将字节编码为Base64.如何从流中获取此信息,请参见此处:如何在C#中将Stream转换为byte []?

或者我认为也应该使用.ToString()方法并对其进行编码.


Vas*_*pov 9

一个简单的 Stream 扩展方法就可以完成这项工作:

public static class StreamExtensions
{
    public static string ConvertToBase64(this Stream stream)
    {
        var bytes = new Byte[(int)stream.Length];

        stream.Seek(0, SeekOrigin.Begin);
        stream.Read(bytes, 0, (int)stream.Length);

        return Convert.ToBase64String(bytes);
    }
}
Run Code Online (Sandbox Code Playgroud)

读取(以及写入)方法和针对相应类(无论是文件流、内存流等)进行优化的方法将为您完成工作。对于像这样的简单任务,不需要读者等等。

唯一的缺点是流被复制到字节数组中,但这就是通过 Convert.ToBase64String 转换为 Base64 的工作原理。

  • 我厌倦了在 .NET 中将所有内容缓冲到 byte[]。这是非常浪费的。现在是端到端流 API 的时候了。 (3认同)

tac*_*cos 6

建议使用的答案ToBase64Transform是有效的,但有一个很大的问题。不确定这是否应该是一个答案,但如果我知道这一点,它会节省我很多时间。

我遇到的问题ToBase64Transform是它被硬编码为一次读取 3 个字节。如果对构造函数中指定的输入流的每次写入都CryptoStream类似于 websocket 或具有不小的开销或延迟的任何内容,那么这可能是一个巨大的问题。

底线 - 如果你正在做这样的事情:

using var cryptoStream = new CryptoStream(httpRequestBodyStream, new ToBase64Transform(), CryptoStreamMode.Write);
Run Code Online (Sandbox Code Playgroud)

可能值得分叉该类ToBase64Transform以将硬编码的 3/4 字节值修改为更大的值,从而减少写入次数。在我的例子中,使用默认的 3/4 值,传输速率约为 100 KB/s。更改为 768/1024(相同比率)有效,并且由于写入次数减少,传输速率约为 50-100 MB/s。

    public class BiggerBlockSizeToBase64Transform : ICryptoTransform
    {
        // converting to Base64 takes 3 bytes input and generates 4 bytes output
        public int InputBlockSize => 768;
        public int OutputBlockSize => 1024;
        public bool CanTransformMultipleBlocks => false;
        public virtual bool CanReuseTransform => true;

        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
        {
            ValidateTransformBlock(inputBuffer, inputOffset, inputCount);

            // For now, only convert 3 bytes to 4
            byte[] tempBytes = ConvertToBase64(inputBuffer, inputOffset, 768);

            Buffer.BlockCopy(tempBytes, 0, outputBuffer, outputOffset, tempBytes.Length);
            return tempBytes.Length;
        }

        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            ValidateTransformBlock(inputBuffer, inputOffset, inputCount);

            // Convert.ToBase64CharArray already does padding, so all we have to check is that
            // the inputCount wasn't 0
            if (inputCount == 0)
            {
                return Array.Empty<byte>();
            }

            // Again, for now only a block at a time
            return ConvertToBase64(inputBuffer, inputOffset, inputCount);
        }

        private byte[] ConvertToBase64(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            char[] temp = new char[1024];
            Convert.ToBase64CharArray(inputBuffer, inputOffset, inputCount, temp, 0);
            byte[] tempBytes = Encoding.ASCII.GetBytes(temp);
            if (tempBytes.Length != 1024)
                throw new Exception();

            return tempBytes;
        }

        private static void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            if (inputBuffer == null) throw new ArgumentNullException(nameof(inputBuffer));
        }

        // Must implement IDisposable, but in this case there's nothing to do.

        public void Dispose()
        {
            Clear();
        }

        public void Clear()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing) { }

        ~BiggerBlockSizeToBase64Transform()
        {
            // A finalizer is not necessary here, however since we shipped a finalizer that called
            // Dispose(false) in desktop v2.0, we need to keep it in case any existing code had subclassed
            // this transform and expects to have a base class finalizer call its dispose method.
            Dispose(false);
        }
    }
Run Code Online (Sandbox Code Playgroud)


Dav*_*nan 5

由于文件较大,因此您无法选择如何执行此操作.您无法处理该文件,因为这会破坏您需要使用的信息.您可以看到两个选项:

  1. 读入整个文件,base64编码,重写编码数据.
  2. 以较小的片段读取文件,随着编码进行编码.编码到同一目录中的临时文件.完成后,删除原始文件,然后重命名临时文件.

当然,流的全部意义在于避免这种情况.而不是创建内容并将其填充到文件流中,而是将其填充到内存流中.然后编码,然后保存到磁盘.