大文件的 UTF-8 BOM 到 UTF-8 的转换

UTF-8 BOM to UTF-8 Conversion for a large file

根据这个帖子的建议,我已经使用 powershell 进行了 UTF-8 转换,现在我 运行 遇到了另一个问题,我有一个大约 18 GB 的非常大的文件,我正在尝试在一台有大约 50GB 空闲内存的机器上进行转换,但是这个转换过程会耗尽所有内存并且编码失败,有没有办法限制 RAM 使用或分块进行转换?

Using PowerShell to write a file in UTF-8 without the BOM

下面是准确的代码

foreach ($file in ls -name $Path\CM*.csv)
{
   $file_content = Get-Content "$Path$file";
   [System.IO.File]::WriteAllLines("$Path$file", $file_content);
   
   echo "encoding done : $file"

}

不要将文件内容存储在内存中。如前所述 here,这样做需要 RAM 中文件大小的 3-4 倍。 Get-Content 很慢但内存效率很高。所以一个简单的解决方案可能是

Get-Content -Path <FilePath> | Out-File -FilePath <FilePath> -Encoding UTF8

注意:虽然我没有尝试过,但您可能希望使用 Add-Content 而不是 Out-File。后者有时会根据控制台宽度重新格式化。 Out-* cmdlet 的特征,它们遍历用于显示的格式化系统。

因为内容是通过管道流式传输的,所以一次只有一行存储在 RAM 中。 .Net 内存垃圾收集 运行 在后台释放和管理 RAM。

注意:[System.IO.StreamReader][System.IO.StreamWriter] 可能也可以解决这个问题。它们可能更快,并且内存效率一样高,但是它们带来了可能不值得的语法负担,特别是如果这是一次性的......也就是说,你可以用 System.Text.Encoding枚举,所以理论上可以使用它们进行转换。

您可以使用 StreamReader and StreamWriter 进行转换。

StreamWriter 默认输出 UTF8NoBOM。

这将需要很多磁盘操作,但会占用内存。

请记住,.Net 需要完整的绝对路径。

$sourceFile      = 'D:\Test\Blah.txt'  # enter your own in- and output files here
$destinationFile = 'D:\Test\out.txt'

$reader = [System.IO.StreamReader]::new($sourceFile, [System.Text.Encoding]::UTF8)
$writer = [System.IO.StreamWriter]::new($destinationFile)

while ($null -ne ($line = $reader.ReadLine())) {
    $writer.WriteLine($line)
}
# clean up
$writer.Flush()
$reader.Dispose()
$writer.Dispose()

以上代码将在输出文件中添加最后一个换行符。如果不需要,请改为执行此操作:

$sourceFile      = 'D:\Test\Blah.txt'
$destinationFile = 'D:\Test\out.txt'

$reader = [System.IO.StreamReader]::new($sourceFile, [System.Text.Encoding]::UTF8)
$writer = [System.IO.StreamWriter]::new($destinationFile)

while ($null -ne ($line = $reader.ReadLine())) {
    if ($reader.EndOfStream) {
        $writer.Write($line)
    }
    else {
        $writer.WriteLine($line)
    }
}
# clean up
$writer.Flush()
$reader.Dispose()
$writer.Dispose()

当您知道输入文件始终是带 BOM 的 UTF-8 时,您只需从文件中去除前三个字节(BOM)。

使用缓冲流,您只需将文件的一小部分加载到内存中。

为了获得最佳性能,我会使用 FileStream。这是原始二进制流,因此开销最少。

$streamIn = $streamOut = $null
try {
    $streamIn = [IO.FileStream]::new( $fullPathToInputFile, [IO.FileMode]::Open )
    $streamOut = [IO.FileStream]::new( $fullPathToOutputFile, [IO.FileMode]::Create )

    # Strip 3 bytes (the UTF-8 BOM) from the input file
    $null = $streamIn.Seek( 3, [IO.SeekOrigin]::Begin )

    # Copy the remaining bytes to the output file
    $streamIn.CopyTo( $streamOut )

    # You may try a custom buffer size for better performance:
    # $streamIn.CopyTo( $streamOut, 1MB )
}
finally {
    # Make sure to close the files even in case of an exception
    if( $streamIn ) { $streamIn.Close() }
    if( $streamOut ) { $streamOut.Close() }
}

您可以尝试 FileStream.CopyTo() overload that has a bufferSize parameter. In ,较大的缓冲区大小(例如 1 MiB)可以显着提高性能,但是当它太大时,性能将再次受到影响,因为缓存不好使用。