#powershell
#powershell
Вопрос:
Я пытаюсь загрузить zip-файл в репозиторий Nexus после преобразования zip-файла (1,6 ГБ) в байты. Проблема в том, что когда он считывает все байты zip-файла, если размер файла большой, он выдает ошибку system.outofmemory. он не выдает ошибку, если она составляет всего 3-600 МБ, есть ли способ прочитать байты и избежать ошибки oufofmemory?
Исключение, вызывающее «ReadAllText» с аргументами «1»: «Исключение типа ‘System.Было вызвано исключение OutOfMemoryException».
$fileBin = [System.IO.File]::ReadAllText($File)
Function Upload-File-To-Nexus {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True )][ValidateNotNullOrEmpty()][string]$File, # aka File
[parameter(Mandatory = $True )][ValidateNotNullOrEmpty()][string]$Repository,
[parameter(Mandatory = $True )][ValidateNotNullOrEmpty()][string]$Directory,
[Parameter(Mandatory = $False)][ValidateNotNullOrEmpty()][string]$FileName # destination filename, which can be derived from uploaded file
)
Begin {
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("fahq-ra-build:tEDlCI=0m9CES8l*lk?b"))
$header = @{authorization = "Basic $token" }
$Uri = "https://nexus-arps.corp.firstam.com/service/rest/v1/components?repository=${Repository}"
$boundary = [System.Guid]::NewGuid().ToString()
$LF = "`r`n"
}
Process {
If (!($FileName -ne $Null -And $FileName -ne "")) {
# if filename is null, then just use the filename from the input file
$FileName = Split-Path $File -Leaf
}
Try { $fileBin = [System.IO.File]::ReadAlltext($File) }
Catch {
throw $_.exception
Throw "Unable to read file $File. Aborted."
}
$bodyLines = (
"--${boundary}",
"Content-Disposition: form-data; name=`"raw.directory`"",
"",
"${Directory}",
"--${boundary}",
"Content-Disposition: form-data; name=`"raw.asset1`"; filename=`"${FileName}`"",
"Content-Type: application/octet-stream",
"",
$file,
"",
"--${boundary}",
"Content-Disposition: form-data; name=`"raw.asset1.filename`"",
"",
"${FileName}",
"--${boundary}--",
""
) -join $LF
$Response = Invoke-WebRequest -Uri $Uri -Method "POST" -Headers $header -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines
If ($Response.StatusCode -ge 200 -And $Response.StatusCode -lt 300) {
$Output = "https://${Server}/repository/${repository}/${directory}/${FileName}" -Replace "//", "/"
Write-Output $Output
}
Else {
Write-Output $Response
}
}
}
Ответ №1:
Вы, вероятно, имели в виду .ReadAllBytes()
, учитывая, что вы не имеете дело с текстовыми файлами.
Однако вам не нужно загружать файл в память — используйте Invoke-WebRequest
-InFile
параметр ‘s, чтобы указать файл для загрузки.
Комментарии:
1. Я рад слышать, что это было полезно, @wonderfulworldwithcharity; с удовольствием.
Ответ №2:
IIRC, способ ReadAllBytes()
работы заключается в том, что он не знает или не просматривает размер файла заранее. Вместо этого он угадывает размер буфера для результирующего массива байтов. Когда предположение не выполняется, оно удваивает предположение, выделяет новый массив и копирует то, что было прочитано до сих пор. Это повторяется до тех пор, пока файл не будет загружен.
Таким образом, вы получаете OutOfMemory не только из-за фактической нехватки памяти, но и из-за нехватки адресного пространства в текущем процессе из дополнительного буфера, где отдельные процессы по умолчанию ограничены только 2 ГБ.
Если это звучит плохо и неэффективно, вы правы и RealAllBytes()
действительно полезно только для небольших файлов.
При работе с файлами большего размера необходимо использовать потоковые API, такие как File.OpenRead()
. Здесь вы можете проверить фактический размер файла, использовать его для выделения массива байтов точного размера, а затем читать небольшими порциями (скажем, 4K) за раз.
Еще лучше, поскольку вы уже используете Invoke-WebRequest
, знаете ли вы, что он поддерживает -InFile
аргумент, который можно использовать для загрузки файла без загрузки всего в память?