【问题标题】:How do I convert encoding of a large file (>1 GB) in size - to Windows 1252 without an out-of-memory exception?如何在没有内存不足异常的情况下将大文件 (>1 GB) 的编码转换为 Windows 1252?
【发布时间】:2023-03-11 06:48:01
【问题描述】:

考虑:

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
    }
}

以上代码对大文件返回内存不足异常,仅适用于小文件。

【问题讨论】:

  • 不能逐行做吗?
  • 您无需使用 ReadToEnd 阅读全部内容。读取块、转换、写入、重复。
  • 使用foreach(string line in File.ReadLines(filePath)) ... process line ...
  • 旁注:不要写throw e;,而只写throw;,您将通过这种方式保持您的堆栈跟踪。请Dispose你的一次性用品(Streams
  • 当在具有大量可用内存的机器上看到 OutOfMemoryException 时,这表明 .Net 运行时无法分配足够大的单个连续内存块来满足请求。随着 List 等容器的增长,底层数组的大小每次都会翻倍。我在运行 X86(32 位)代码时看到过这种情况,因为地址空间被限制为 4GB。

标签: c# encoding filestream


【解决方案1】:

我认为仍然使用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 方法,如果有,这将基本上是一个单行!)

【讨论】:

  • 谢谢@Matti。这个问题有助于我完成任务。我可以毫无例外地转换超过 1.5GB 的文件的编码。
【解决方案2】:

不要 readToEnd 并像一次一行地读取它或 X 个字符。如果您读到最后,您会立即将整个文件放入缓冲区。

【讨论】:

    【解决方案3】:

    试试这个:

    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" />
    

    【讨论】:

    • 这不适用于所有输入。输入采用 UTF8 格式,并且无法保证通过准确读取 4K 字节,您不会读取以超过一个字节编码的部分字符。如果发生这种情况,它将无法正确读取,并且您将获得无效数据。
    • 我在提到 UTF8 的问题中看不到任何地方,源编码不是作为参数传入的吗?是的,它需要针对 UTF8 进行调整,但是,如果您的文件都在一行中(通过不使用不必要的空格或新行,例如 XML 来节省空间),那么逐行执行将不起作用,这是我知道的唯一方法of 正在流式传输文件。缓冲区大小始终可以在每次迭代中根据正在读取的部分数据进行调整。
    • OP 使用的StreamReader(string path) 构造函数以UTF8 格式打开输入流。请参阅链接的文档。万一所有文本都在一行上,那么正确的方法是使用StreamReader.Read() 重载,从文件中读取指定数量的字符。切勿从字符可能具有可变长度编码的文件中读取固定大小的缓冲区。这几乎总是一个错误。
    • 作为一个实验,用这样的文件来试试你的代码:File.WriteAllText(filePath, new string('x', 4095) + "ÿ");
    • 如果格式允许,您会惊讶于单行中有多少 HUGE 文件(当然制表符或逗号分隔不起作用),但我处理的大多数 XML 文件都保存到一行以节省存储成本和传输成本(尤其是缩进)。还可以检查一个字节是单个 UTF8 字符还是多字节字符的一部分。此处发布的答案显然没有这样做,并且该问题从未明确提出过。因此,这不是错误的方法,并且使用 UTF8 字节检查将是处理巨大的单行 XML 文件的好方法。
    猜你喜欢
    • 2021-05-27
    • 1970-01-01
    • 2012-08-06
    • 1970-01-01
    • 2014-07-25
    • 1970-01-01
    • 2022-11-21
    • 2012-05-13
    • 2014-07-18
    相关资源
    最近更新 更多