簡體   English   中英

使用 c# 將 FileStream 編碼為 base64

[英]Encode a FileStream to base64 with c#

我知道如何將一個簡單的字符串編碼/解碼到base64或從中解碼。

但是,如果數據已經寫入FileStream object,我該怎么做。假設我只能訪問 FileStream object 而不能訪問其中先前存儲的原始數據。 在將 FileStream 刷新到文件之前,我如何將FileStream 編碼為 base64

Ofc 我可以在將 FileStream 寫入文件后打開我的文件並對其進行編碼/解碼,但我想一步完成所有這些操作,而不是一個接一個地執行兩個文件操作。 文件可能會更大,並且在剛剛保存了很短的時間后,加載、編碼和再次保存也需要兩倍的時間。

也許你們中有人知道更好的解決方案? 我可以將 FileStream 轉換為字符串,對字符串進行編碼,然后將字符串轉換回 FileStream 嗎?或者我會做什么,這樣的代碼會是什么樣子?

一種簡單的擴展方法

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));
    }
}

在處理大流時,例如大小超過 4GB 的文件 - 您不想將文件加載到內存中(作為Byte[] ),因為它不僅非常慢,而且可能會導致崩潰,即使在 64- bit 處理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);
}

您可以嘗試類似的東西:

    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));
    }

您還可以將字節編碼為 Base64。 如何從流中獲取它,請參見此處: How to convert an Stream into a byte[] in C#?

或者我認為也應該可以使用 .ToString() 方法並對其進行編碼。

一個簡單的 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);
    }
}

讀取(以及寫入)和針對相應類(無論是文件流、內存流等)優化的方法將為您完成工作。 對於這樣的簡單任務,不需要讀者等。

唯一的缺點是流被復制到字節數組中,但不幸的是,這就是通過 Convert.ToBase64String 轉換為 base64 的工作方式。

由於文件會更大,因此您在如何執行此操作方面沒有太多選擇。 您無法就地處理文件,因為這會破壞您需要使用的信息。 我可以看到您有兩個選項:

  1. 讀入整個文件,base64編碼,重新寫入編碼后的數據。
  2. 以較小的部分讀取文件,並在進行過程中進行編碼。 編碼到同一目錄中的臨時文件。 完成后,刪除原始文件,並重命名臨時文件。

當然,流的全部意義在於避免這種情況。 不是創建內容並將其填充到文件流中,而是將其填充到內存流中。 然后對其進行編碼,然后才保存到磁盤。

建議使用ToBase64Transform的答案是有效的,但有一個很大的問題。 不確定這是否應該是一個答案,但如果我知道這一點,它會節省我很多時間。

我在使用ToBase64Transform時遇到的問題是它被硬編碼為一次讀取 3 個字節。 如果每次寫入CryptoStream構造函數中指定的輸入 stream 都類似於 websocket 或任何具有重要開銷或延遲的東西,這可能是一個大問題。

底線 - 如果你正在做這樣的事情:

using var cryptoStream = new CryptoStream(httpRequestBodyStream, new ToBase64Transform(), CryptoStreamMode.Write);

可能值得分叉 class 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);
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM