使用PowerShell对大型Zip文件进行Base64编码

5
我正在尝试使用Powershell将一个大小约为66MB的zip文件进行base64编码,并将其写入文件。我的限制是,最终我必须直接将base64编码的文件字符串包含在Powershell脚本中,以便在不同位置运行脚本时,可以从中重新创建zip文件。我并不局限于使用Powershell来创建base64编码的字符串,只是我最熟悉它。
我目前正在使用的代码:
$file = 'C:\zipfile.zip'
$filebytes = Get-Content $file -Encoding byte
$fileBytesBase64 = [System.Convert]::ToBase64String($filebytes)
$fileBytesBase64 | Out-File 'C:\base64encodedString.txt'

之前我处理的文件都比较小,所以编码速度相对较快。但是现在我发现正在处理的文件占用了所有的内存,导致速度极慢。我感觉一定有更好的方法来解决这个问题,并且非常感谢任何建议。


1
这是C#代码,但可以很容易地转换为PowerShell代码。请注意,根据您在脚本中嵌入Base64字符串的方式,如果不特别小心,它在重新创建文件时可能同样低效。 - Jeroen Mostert
@JeroenMostert,在我的情况下,使用PowerShell会导致性能极慢。可能是因为InputBlockSizeOutputBlockSize太小而无法更改。 - filimonic
2个回答

3
⚠️ 2023-08-17 更新:有一个更好的解决方案,请点击这里
在我的情况下,编码或解码一个117-Mb的文件只需要不到1秒钟的时间。
Src file size: 117.22 MiB
Tgt file size: 156.3 MiB
Decoded size: 117.22 MiB
Encoding time: 0.294
Decoding time: 0.708

我正在制定的代码措施:

$pathSrc = 'D:\blend5\scena31.blend'
$pathTgt = 'D:\blend5\scena31.blend.b64'
$encoding = [System.Text.Encoding]::ASCII

$bytes = [System.IO.File]::ReadAllBytes($pathSrc)
Write-Host "Src file size: $([Math]::Round($bytes.Count / 1Mb,2)) MiB"
$swEncode = [System.Diagnostics.Stopwatch]::StartNew()
$B64String = [System.Convert]::ToBase64String($bytes, [System.Base64FormattingOptions]::None)
$swEncode.Stop()
[System.IO.File]::WriteAllText($pathTgt, $B64String, $encoding)

$B64String = [System.IO.File]::ReadAllText($pathTgt, $encoding)
Write-Host "Tgt file size: $([Math]::Round($B64String.Length / 1Mb,2)) MiB"
$swDecode = [System.Diagnostics.Stopwatch]::StartNew()
$bytes = [System.Convert]::FromBase64String($B64String)
$swDecode.Stop()
Write-Host "Decoded size: $([Math]::Round($bytes.Count / 1Mb,2)) MiB"

Write-Host "Encoding time: $([Math]::Round($swEncode.Elapsed.TotalSeconds,3)) s"
Write-Host "Decoding time: $([Math]::Round($swDecode.Elapsed.TotalSeconds,3)) s"

这个只能处理最大为2GB的文件大小。 - John Ranger
1
@JohnRanger,是的,因为使用的是.NET字符串,并且有2GB的限制。 - filimonic
1
@JohnRanger 添加了处理大文件的新答案。 - filimonic

2

2023-08-17 更新:针对大文件、内存使用和速度

正如 @JohnRanger 提到的,之前的答案 存在问题,它的源文件限制为约 1.5 GiB,并且消耗内存。

解决方案是使用文件流和 CryptoStream(... ToBase64Transform...)

$sourcePath = "C:\test\windows_11.iso"
$targetPath = "C:\test\windows_11.iso.b64"
$size = Get-Item -Path $sourcePath | Select -ExpandProperty Length
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

$converterStream = [System.Security.Cryptography.CryptoStream]::new(
    [System.IO.File]::OpenRead($sourcePath),
    [System.Security.Cryptography.ToBase64Transform]::new(), 
    [System.Security.Cryptography.CryptoStreamMode]::Read,
    $false) # keepOpen = $false => When we close $converterStream, it will close source file stream also
$targetFileStream = [System.IO.File]::Create($targetPath)
$converterStream.CopyTo($targetFileStream)
$converterStream.Close() # And it also closes source file stream because of keepOpen = $false parameter.
$targetFileStream.Close() # Flush() is called internally.

$stopwatch.Stop()
Write-Host "Elapsed: $($stopwatch.Elapsed.TotalSeconds) seconds for $([Math]::Round($size / 1MB))-Mbyte file"

⚠️ 这段代码在PS7上运行比PS5快30倍以上。
PS7: 在我的机器上,一个大小为5316 MiB的文件需要3.1秒。

使用[IO.File]::Copy(...) 进行文件复制需要1.8秒。


PS5(索尼 PlayStation 5):在我的机器上需要115秒。

我在[System.IO.File]::Create(...)$converterStream.CopyTo(...)中尝试了不同的缓冲区大小,但并没有获得任何合理的性能差异:对于5316 MiB,我在默认缓冲区大小下获得了最差的115秒结果,并在使用不同缓冲区大小时获得了112秒的最佳结果。也许对于慢速目标(比如与慢速磁盘或网络共享工作),缓冲区大小会产生更大的影响。

使用[IO.File]::Copy()进行文件复制只需要1.8秒。


1
哇!只用了7.8秒就完成了对一个3.7GB文件的编码。还要感谢您提供超快速的解决方案。 - John Ranger
我相信这个解决方案可以进行优化,以获得更好的性能和多线程支持(例如,同时读取文件并进行转换),但在一般情况下我看不到有必要这样做的理由。 - filimonic
1
依我看,速度相当快:用时7.8696603秒处理一个3654兆字节的文件。(运行该程序的机器具有关键的1TB SSD硬盘和32GB内存。看起来它几乎接近了磁盘的读写极限)。这是在最新的PowerShell 7下运行的。 - John Ranger
1
@JohnRanger 哇,我在PS5上需要115秒,而在PS7上只需要3.1秒。有趣的发现... - filimonic
1
巨大的差异(115秒到3.1秒)似乎来自于Pwsh 7中底层.NET Core框架的优化。我已经在其他情况下见过这种情况。微软确实在提高执行速度方面做了很多工作。 - John Ranger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接