Как преобразовать кодировку большого файла (> 1 ГБ) в размере - в Windows 1252 без исключения из памяти?

Рассмотрим:

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

Вышеупомянутый фрагмент кода возвращает исключение из памяти для больших файлов и работает только для файлов небольшого размера.

Ответы

Ответ 1

Я все еще использую StreamReader и StreamWriter, но чтение блоков символов вместо всех сразу или по одной строке является самым элегантным решением. Он не произвольно предполагает, что файл состоит из строк управляемой длины, а также не разбивается на многобайтовые кодировки символов.

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 имел метод CopyTo, такой как Stream, если бы он был, это было бы по существу однострочным!)

Ответ 2

Не читатьToEnd и читать его, как строка за строкой или 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();
    }
}

Возможно, есть некоторые синтаксические ошибки, как я сделал это из памяти, но так я работаю с большими файлами, читаю в куске за раз, делаю некоторую обработку и сохраняю кусок назад. Это действительно единственный способ сделать это (потоковая передача), не полагаясь на массивные издержки ввода-вывода на чтение всего и огромное количество оперативной памяти, хранящее все это, преобразовывая все это в память и затем сохраняя все это обратно.

Вы всегда можете настроить размер буфера.

Если вы хотите, чтобы ваш старый метод работал, не бросая OutOfMemoryException, вам нужно сообщить сборщику мусора, чтобы разрешить очень большие объекты.

В App.config, под <runtime> добавьте следующую строку (вам не нужно это с моим кодом, но это стоит знать):

<gcAllowVeryLargeObjects enabled="true" />