Ошибка [System.IO.File] ::ReadAllText ($File) OutOfMemory

#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 аргумент, который можно использовать для загрузки файла без загрузки всего в память?