如何将大文件 (>1 GB) 的编码转换为 Windows 1252 而不会出现内存不足异常?

How do I convert encoding of a large file (>1 GB) in size - to Windows 1252 without an out-of-memory exception?

考虑:

public static void ConvertFileToUnicode1252(string filePath, Encoding srcEncoding)
{
    try
    {
        StreamReader fileStream = new StreamReader(filePath);
        Encoding targetEncoding = Encoding.GetEncoding(1252);

        string fileContent = fileStream.ReadToEnd();
        fileStream.Close();

        // Saving file as ANSI 1252
        Byte[] srcBytes = srcEncoding.GetBytes(fileContent);
        Byte[] ansiBytes = Encoding.Convert(srcEncoding, targetEncoding, srcBytes);
        string ansiContent = targetEncoding.GetString(ansiBytes);

        // Now writes contents to file again
        StreamWriter ansiWriter = new StreamWriter(filePath, false);
        ansiWriter.Write(ansiContent);
        ansiWriter.Close();
        //TODO -- log success  details
    }
    catch (Exception e)
    {
        throw e;
        // TODO -- log failure details
    }
}

以上代码returns大文件内存不足异常,只对小文件有效

不要 readToEnd 而是逐行阅读或一次阅读 X 个字符。如果读到结尾,您会立即将整个文件放入缓冲区。

试试这个:

using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
{
    int size = 4096;
    Encoding targetEncoding = Encoding.GetEncoding(1252);
    byte[] byteData = new byte[size];

    using (FileStream outputStream = new FileStream(outputFilepath, FileMode.Create))
    {
        int byteCounter = 0;

        do
        {
            byteCounter = fileStream.Read(byteData, 0, size);

            // Convert the 4k buffer
            byteData = Encoding.Convert(srcEncoding, targetEncoding, byteData);

            if (byteCounter > 0)
            {
                outputStream.Write(byteData, 0, byteCounter);
            }
        }
        while (byteCounter > 0);

        inputStream.Close();
    }
}

可能有一些语法错误,因为我是凭记忆完成的,但这是我处理大文件的方式,一次读取一个块,进行一些处理并将块保存回来。这确实是唯一的方法(流式传输),不依赖于读取所有内容的大量 IO 开销和存储所有内容的大量 RAM 消耗,将其全部转换到内存中,然后将其全部保存回来。

您可以随时调整缓冲区大小。

如果您希望您的旧方法在不抛出 OutOfMemoryException 的情况下工作,您需要告诉 垃圾收集器 允许非常大的对象。

在 App.config 中,在 <runtime> 下添加以下行(我的代码不需要它,但值得了解):

<gcAllowVeryLargeObjects enabled="true" />

我认为仍然使用 StreamReaderStreamWriter 但最优雅的解决方案是读取字符块而不是一次或逐行读取所有字符。它不会武断地假设文件由可管理长度的行组成,也不会破坏多字节字符编码。

public static void ConvertFileEncoding(string srcFile, Encoding srcEncoding, string destFile, Encoding destEncoding)
{
    using (var reader = new StreamReader(srcFile, srcEncoding))
    using (var writer = new StreamWriter(destFile, false, destEncoding))
    {
        char[] buf = new char[4096];
        while (true)
        {
            int count = reader.Read(buf, 0, buf.Length);
            if (count == 0)
                break;

            writer.Write(buf, 0, count);
        }
    }
}

(我希望 StreamReader 有一个像 Stream 那样的 CopyTo 方法,如果有,这基本上就是一个单行!)