2016-12-20 6 views
4

Данный код предназначен для преобразования zip-файла в формат base64.«Недостаточно памяти для завершения этой операции», когда base64-кодирование zip-файла

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 

Я пробовал сначала с zip-файлом размером 3 МБ. Он отлично работал. Но когда я пытаюсь с zip-файлом размером 34 МБ, он говорит

Недостаточно места для хранения этой операции!

на линии

encodeBase64 = EL.Text 

Есть ли способ, что я могу обрабатывать почтовые файлы любого размера, потому что мои размеры файлов в основном 30 МБ или больше.

+0

Как [Base64 работы] (https: //en.wikipedia .org/вики/Base64). – Paul

+1

Как это может помочь? Я прочитал некоторые связанные проблемы на Java, и они говорят о делении файла и чтении его. –

ответ

2

отредактировал 2017/01/10 - (оригинальный ответ keeped снизу)

отредактировал 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, работающим как 32-битный процесс с файлами 90 МБ, и в 64-битном режиме с 500 МБ файлами без проблем.

Почему два метода?

  • Метод stream быстрее (все операции выполняются в памяти без конкатенации строк), но требует больше памяти, как это будет иметь две копии тех же данных в конце функции: будет одна копия внутри потока и одна в строке, которая будет возвращена

  • Временный метод файла медленнее, поскольку буферные данные будут записаны на диск, но поскольку есть только одна копия данных, для этого требуется меньше памяти ,

10MB предел используется для определения, если мы будем использовать или не временный файл, просто pesimistic конфигурации для предотвращения проблем в 32-битном режиме. Я обработал 90 МБ файлов в 32-битном режиме без проблем, но только для того, чтобы быть safe.

Почему stream сконфигурирован как Unicode, а данные извлекаются методом .Read()?

Поскольку stream.ReadText() является slow. Внутри он делает много преобразований строк/проверок (да, в documentation), что делает его непригодным для использования в этом случае.


Ниже оригинального ответа. Это проще и позволяет избежать проблемы с памятью при конверсии, но для больших файлов этого недостаточно.


Split процесс чтения/кодирования

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 - это всего лишь ближайшее число 54 (количество входных байтов для каждой выходной линии base64) ниже 3145728 (3 МБ).

+0

Я пробовал это, но дал мне ошибку Из строки: 'Join' Линия (91): "encodeBase64 = Join (outputBuffer.Items(), vbCrLf)". Требование - передать закодированное значение в следующий тест API, который я делаю сейчас, после присвоения значения переменной и ее передачи. Запись на внешний файл не помогла бы мне, я думаю, поскольку тест API не считывает обновленные значения времени выполнения из листа данных excel в UFT во время выполнения. Пожалуйста, поправьте меня, если я ошибаюсь –

+0

Я запустил его снова, и теперь ошибка закончилась, когда я делаю MsgBox конечную кодированную переменную. –

+0

'Dim readBytes ли readBytes = inStream.Read (3145716) Если IsNull (readBytes) Then Exit Do " Установить байты, получить закодированные строки EL.NodeTypedValue = readBytes outputBuffer.Add outputBuffer.Count + 1, EL.Text Loop inStream.Close encodeBase64 = Join (outputBuffer.Items(), vbCrLf) MsgBox (encodeBase64) «encodeBase64 = EL.Text MsgBox (encodeBase64) RunAPITest "Demoservice", encodeBase64, fieldvalue2 \t следующий objExcel.Quit' –

0

22/02/2017 - Я пробовал с файлом объемом 34 МБ - получаю сообщение об ошибке «Ошибка при запуске». Ниже приведен снимок экрана. enter image description here

encodeBase64 имеет матричную структуру, как показано ниже, который не может в какой-то point.If вы можете увидеть 1074th позицию он говорит неправильное выражение

The encodeBase64 has array structure like below which fails at some point.If you can see 1074th position it says incorrect expression