отредактировал 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 МБ).
Как [Base64 работы] (https: //en.wikipedia .org/вики/Base64). – Paul
Как это может помочь? Я прочитал некоторые связанные проблемы на Java, и они говорят о делении файла и чтении его. –