vbscript - "Not enough storage is available to complete this operation"对 zip 文件进行 base64 编码时

标签 vbscript base64 adodb xmldom

以下代码用于将 zip 文件转换为 base64 格式。

Dim inByteArray, base64Encoded,
Const TypeBinary = 1
inByteArray = readBytes("F:path/file.zip")
base64Encoded = encodeBase64(inByteArray)

Private Function readBytes(file)
    Dim inStream
    ' ADODB stream object used
    Set inStream = CreateObject("ADODB.Stream")
    ' open with no arguments makes the stream an empty container 
    inStream.Open
    inStream.Type = TypeBinary
    inStream.LoadFromFile(file)
    readBytes = inStream.Read()
End Function

Private Function encodeBase64(bytes)
    Dim DM, EL
    Set DM = CreateObject("Microsoft.XMLDOM")
    ' Create temporary node with Base64 data type
    Set EL = DM.CreateElement("tmp")
    EL.DataType = "bin.base64"
    ' Set bytes, get encoded String
    EL.NodeTypedValue = bytes
    encodeBase64 = EL.Text
End Function

我首先尝试使用大小为 3MB 的 zip 文件,效果很好。但是当我尝试使用大小为 34 MB 的 zip 文件时,它说

Not enough storage is available to complete this operation!



在线
encodeBase64 = EL.Text

有什么方法可以处理所有大小的 zip 文件,因为我的文件大小大多为 30MB 或更大。

最佳答案

已编辑 2017/01/10 - (原始答案保留在底部)

已编辑 2017/01/10 -(再次) - 我的一些(不是全部)超时问题是由磁盘故障引起的。

通过拆分转换操作来处理输入数据的问题。现在代码已更改为以两种不同的方式处理缓冲:对于小文件(默认情况下为文件配置为 10MB )内存流用于存储输出,但对于大文件(大于 10MB )使用临时文件(请参阅代码后的注释)。

Option Explicit

Dim buffer
    buffer = encodeFileBase64( "file.zip" ) 

    WScript.StdOut.WriteLine( CStr(Len(buffer)) )


Private Function encodeFileBase64( file )
    ' Declare ADODB used constants
    Const adTypeBinary = 1
    Const adTypeText = 2

    ' Declare FSO constants
    Const TEMP_FOLDER = 2

    ' Initialize output
    encodeFileBase64 = ""

    ' Instantiate FileSystemObject
    Dim fso
    Set fso = WScript.CreateObject("Scripting.FileSystemObject")

    ' Check input file exists
    If Not fso.FileExists( file ) Then 
        Exit Function
    End If 

    ' Determine how we will handle data buffering.
    ' Use a temporary file for large files 
    Dim useTemporaryFile
    useTemporaryFile = fso.GetFile( file ).Size > 10 * 1048576

    ' Instantiate the B64 conversion component
    Dim b64 
    Set b64 = WScript.CreateObject("Microsoft.XMLDOM").CreateElement("tmp")
        b64.DataType = "bin.base64"

    Dim outputBuffer, outputBufferName
    If useTemporaryFile Then 
        ' Create a temporary file to be used as a buffer
        outputBufferName = fso.BuildPath( _ 
            fso.GetSpecialFolder( TEMP_FOLDER ), _ 
            fso.GetTempName() _ 
        )
        Set outputBuffer = fso.CreateTextFile( outputBufferName, True )
    Else 
        ' Instantiate a text stream to be used as a buffer to avoid string 
        ' concatenation operations that were generating out of memory problems
        Set outputBuffer = WScript.CreateObject("ADODB.Stream")
        With outputBuffer
            ' Two bytes per character, BOM prefixed buffer
            .Type = adTypeText
            .Charset = "Unicode"
            .Open
        End With 
    End If 

    ' Instantiate a binary stream object to read input file 
    With WScript.CreateObject("ADODB.Stream")
        .Open
        .Type = adTypeBinary
        .LoadFromFile(file)

        ' Iterate over input file converting the file, converting each readed
        ' block to base64 and appending the converted text into the output buffer
        Dim inputBuffer
        Do
            inputBuffer = .Read(3145716)
            If IsNull( inputBuffer ) Then Exit Do

            b64.NodeTypedValue = inputBuffer
            If useTemporaryFile Then 
                Call outputBuffer.Write( b64.Text )
            Else 
                Call outputBuffer.WriteText( b64.Text )
            End If 
        Loop 

        ' Input file has been readed, close its associated stream
        Call .Close()
    End With

    ' It is time to retrieve the contents of the text output buffer into a 
    ' string. 

    If useTemporaryFile Then 
        ' Close output file 
        Call outputBuffer.Close()
        ' Read all the data from the buffer file
        encodeFileBase64 = fso.OpenTextFile( outputBufferName ).ReadAll()
        ' Remove temporary file
        Call fso.DeleteFile( outputBufferName )

    Else 

        ' So, as we already have a Unicode string inside the stream, we will
        ' convert it into binary and directly retrieve the data with the .Read() 
        ' method.
        With outputBuffer
            ' Type conversion is only possible while at the start of the stream
            .Position = 0
            ' Change stream type from text to binary
            .Type = adTypeBinary
            ' Skip BOM
            .Position = 2
            ' Retrieve buffered data
            encodeFileBase64 = CStr(.Read())
            ' Ensure we clear the stream contents
            .Position = 0
            Call .SetEOS()
            ' All done, close the stream
            Call .Close()
        End With 
    End If 

End Function

内存会不会有问题?

是的。可用内存仍然是一个限制。无论如何,我已经用 cscript.exe 测试了代码以 90MB 文件的 32 位进程和 500MB 文件的 64 位模式运行,没有问题。

为什么有两种方法?
  • stream方法更快(所有操作都在内存中完成,没有字符串连接),但它需要更多的内存,因为在函数结束时它将有两个相同数据的副本:流中将有一个副本,字符串中将有一个副本将返回
  • 临时文件方法较慢,因为缓冲的数据将写入磁盘,但由于只有一份数据副本,因此需要较少的内存。
  • 10MB用于确定我们是否会使用临时文件的限制只是一种悲观的配置,以防止 32 位模式下出现问题。我在 32 位模式下处理了 90MB 文件没有问题,但只是为了安全。

    为什么stream配置为 Unicode并通过 .Read() 检索数据方法?

    因为stream.ReadText() .它在内部进行了大量字符串转换/检查(是的,建议在 documentation 中)使其在这种情况下无法使用。

    下面是原始答案。它更简单,避免了转换中的内存问题,但对于大文件,这还不够。

    拆分读取/编码过程
    Option Explicit
    Const TypeBinary = 1
    
    Dim buffer
        buffer = encodeFileBase64( "file.zip" ) 
        WScript.StdOut.WriteLine( buffer )
    
    Private Function encodeFileBase64( file )
    
        Dim b64 
        Set b64 = WScript.CreateObject("Microsoft.XMLDOM").CreateElement("tmp")
            b64.DataType = "bin.base64"
    
        Dim outputBuffer
        Set outputBuffer = WScript.CreateObject("Scripting.Dictionary")
    
        With WScript.CreateObject("ADODB.Stream")
            .Open
            .Type = TypeBinary
            .LoadFromFile(file)
    
            Dim inputBuffer
            Do
                inputBuffer = .Read(3145716)
                If IsNull( inputBuffer ) Then Exit Do
    
                b64.NodeTypedValue = inputBuffer
                outputBuffer.Add outputBuffer.Count + 1, b64.Text 
            Loop 
    
            .Close
        End With
    
        encodeFileBase64 = Join(outputBuffer.Items(), vbCrLf)
    End Function
    

    笔记:
  • 不,它不是防弹的。您仍然受到构造输出字符串所需空间的限制。对于大文件,您将需要使用输出文件,写入部分结果,直到处理完所有输入。
  • 3145716 只是低于 3145728 (3MB) 的最接近的 54 倍数(每个 base64 输出行的输入字节数)。
  • 关于vbscript - "Not enough storage is available to complete this operation"对 zip 文件进行 base64 编码时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41237920/

    相关文章:

    vbscript - 用相对路径vbs打开程序

    c# - 将 ADODB::Recordset^ 转换为 _RecordsetPtr

    c# - 如何从 COM 调用者调用 .Net 嵌套类的方法

    excel - 如何在 Microsoft 脚本控件中实现事件?

    vbscript - 为什么设置属性失败?

    javascript atob ("CDA=")未提供预期的行为(至少在 Chrome 32 上)

    postgresql - 如何在postgresql8.2中将base64字符串转换为bytea

    javascript - Javascript 中的最佳灰度图像压缩

    php 查询没有检索到任何数据?

    PHP SQLSRV 在 sqlsrv_fetch_array/sqlsrv_fetch 上有间歇性延迟