Порядок следования байтов StreamWriter и UTF-8 - PullRequest
53 голосов
/ 11 марта 2011

У меня проблема с StreamWriter и метками порядка байтов.В документации, похоже, говорится, что в кодировке Encoding.UTF8 включены метки порядка байтов, но при записи файлов одни имеют метки, а другие - нет.

Я создаю потоковую запись следующим образом:

this.Writer = new StreamWriter(this.Stream, System.Text.Encoding.UTF8);

Будем благодарны за любые идеи о том, что может произойти.

Ответы [ 8 ]

83 голосов
/ 25 июля 2012

Как уже отмечалось, вызов без аргумента кодирования делает свое дело.Однако, если вы хотите быть явным, попробуйте следующее:

using (var sw = new StreamWriter(this.Stream, new UTF8Encoding(false)))

Ключ заключается в создании нового UTF8Encoding (false) вместо использования Encoding.UTF8Encoding.Это нужно для контроля того, должна ли быть добавлена ​​спецификация.

Это то же самое, что вызов StreamWriter без аргумента кодирования, внутренне это просто делает то же самое.

17 голосов
/ 23 марта 2013

Проблема связана с тем, что вы используете статическое UTF8 свойство для Encoding класса .

Когда GetPreamble метод вызывается для экземпляра класса Encoding, возвращаемого свойством UTF8, он возвращает метку порядка байтов (байтовый массив из трех символов) и записывается в поток перед любым другим содержимымзаписывается в поток (предполагается, что новый поток).

Вы можете избежать этого, создав собственный экземпляр UTF8Encoding класса , например:

// As before.
this.Writer = new StreamWriter(this.Stream, 
    // Create yourself, passing false will prevent the BOM from being written.
    new System.Text.UTF8Encoding());

Согласно документации для конструктора по умолчанию без параметров (выделено):

Этот конструктор создает экземпляр, который не обеспечивает метку порядка байтов Unicode и не выдает исключение при обнаружении неверной кодировки.

Это означает, что вызов GetPreamble вернет пустой массив, и, следовательно, спецификация не будет записана в базовый поток.

13 голосов
/ 19 марта 2014

Мой ответ основан на ответе HelloSam, который содержит всю необходимую информацию.Только я полагаю, что OP запрашивает, как убедиться, что спецификация передается в файл.

Так что вместо передачи false в кодировщик UTF8Encoding вам нужно передать true.

    using (var sw = new StreamWriter("text.txt", new UTF8Encoding(true)))

Попробуйте приведенный ниже код, откройте получившиеся файлы в шестнадцатеричном редакторе и посмотрите, какой из них содержит спецификацию, а какой нет.

class Program
{
    static void Main(string[] args)
    {
        const string nobomtxt = "nobom.txt";
        File.Delete(nobomtxt);

        using (Stream stream = File.OpenWrite(nobomtxt))
        using (var writer = new StreamWriter(stream, new UTF8Encoding(false)))
        {
            writer.WriteLine("HelloПривет");
        }

        const string bomtxt = "bom.txt";
        File.Delete(bomtxt);

        using (Stream stream = File.OpenWrite(bomtxt))
        using (var writer = new StreamWriter(stream, new UTF8Encoding(true)))
        {
            writer.WriteLine("HelloПривет");
        }
    }
12 голосов
/ 11 марта 2011

Единственный раз, когда я видел, что конструктор не добавляет спецификацию UTF-8, это когда поток не находится в позиции 0, когда вы его вызываете. Например, в приведенном ниже коде спецификация не записана:

using (var s = File.Create("test2.txt"))
{
    s.WriteByte(32);
    using (var sw = new StreamWriter(s, Encoding.UTF8))
    {
        sw.WriteLine("hello, world");
    }
}

Как уже говорили другие, если вы используете конструктор StreamWriter(stream) без указания кодировки, то вы не увидите спецификацию.

5 голосов
/ 11 марта 2011

Используете ли вы один и тот же конструктор StreamWriter для каждого файла? Потому что в документации написано:

Чтобы создать StreamWriter с использованием кодировки UTF-8 и спецификации, рассмотрите возможность использования конструктора, который задает кодировку, например StreamWriter (String, Boolean, Encoding).

Я был в подобной ситуации некоторое время назад. В итоге я использовал метод Stream.Write вместо StreamWriter и написал результат Encoding.GetPreamble() перед тем, как написать Encoding.GetBytes(stringToWrite)

3 голосов
/ 02 декабря 2014

Я нашел этот ответ полезным (спасибо @Philipp Grathwohl и @Nik), но в моем случае я использую FileStream для выполнения задачи, поэтому код, генерирующий спецификацию, выглядит следующим образом:

using (FileStream vStream = File.Create(pfilePath))
{
    // Creates the UTF-8 encoding with parameter "encoderShouldEmitUTF8Identifier" set to true
    Encoding vUTF8Encoding = new UTF8Encoding(true);
    // Gets the preamble in order to attach the BOM
    var vPreambleByte = vUTF8Encoding.GetPreamble();

    // Writes the preamble first
    vStream.Write(vPreambleByte, 0, vPreambleByte.Length);

    // Gets the bytes from text
    byte[] vByteData = vUTF8Encoding.GetBytes(pTextToSaveToFile);
    vStream.Write(vByteData, 0, vByteData.Length);
    vStream.Close();
}
2 голосов
/ 23 июня 2011

Кажется, что если файл уже существовал и не содержал спецификацию, он не будет содержать спецификацию при перезаписи, другими словами StreamWriter сохраняет спецификацию (или ее отсутствие) при перезаписи файла.

1 голос
/ 11 марта 2011

Не могли бы вы показать ситуацию, когда он не производит это?Единственный случай, когда преамбулы нет, которую я могу найти, - это когда писателю ничего не пишется (Джим Мишель, похоже, нашел другую, логичную и более вероятную проблему, см. Ответ).

Мой тестовый код:

var stream = new MemoryStream();
using(var writer = new StreamWriter(stream, System.Text.Encoding.UTF8))
{
    writer.Write('a');
}
Console.WriteLine(stream.ToArray()
    .Select(b => b.ToString("X2"))
    .Aggregate((i, a) => i + " " + a)
    );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...