В книге «Эффективный STL» Скотта Мейерса есть хороший пример чтения всего текстового файла в объект std :: string:
std::string sData;
/*** Open the file for reading, binary mode ***/
std::ifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode
/*** Read in all the data from the file into one string object ***/
sData.assign (std::istreambuf_iterator <char> (ifFile),
std::istreambuf_iterator <char> ());
Обратите внимание, что он читает его как 8-байтовые символы. Это работает очень хорошо. Хотя недавно мне нужно было прочитать файл, содержащий текст в кодировке Unicode (то есть два байта на символ). Однако, когда я пытаюсь (наивно) изменить его, чтобы прочитать данные из текстового файла Unicode в объект std :: wstring, вот так:
std::wstring wsData;
/*** Open the file for reading, binary mode ***/
std::wifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode
/*** Read in all the data from the file into one string object ***/
wsData.assign (std::istreambuf_iterator <wchar_t> (ifFile),
std::istreambuf_iterator <wchar_t> ());
Строка, которую я получаю, хотя и имеет широкие символы, все еще имеет альтернативные нули. Например, если файл содержит строку Unicode «ABC», байты файла (без учета начальных байтов Unicode 0xFF, 0xFE):
<«A»> <0> <«B»> <0> <«C»> <0>
Первый фрагмент кода, приведенный выше, будет корректно приводить к следующему содержимому строки (char):
sData [0] = ‘A’
sData [1] = 0x00
sData [2] = ‘B’
sData [3] = 0x00
sData [4] = ‘C’
sData [5] = 0x00
Однако, когда запускается второй фрагмент кода, это нежелательно приводит к следующему содержанию строки (wchar_t):
wsData [0] = L‘A ’
wsData [1] = 0x0000
wsData [2] = L‘B ’
wsData [3] = 0x0000
wsData [4] = L‘C ’
wsData [5] = 0x0000
Как будто файл все еще читается побайтово, а затем просто переводится в отдельные символы wchar_t.
Я бы подумал, что std :: istreambuf_iterator, специализирующийся на wchar_t, должен был приводить к чтению файла по два байта за раз, не так ли? Если нет, тогда какова его цель?
Я проследил в шаблонах (нет простого навыка ;-), и итератор действительно все еще читает файл побайтно и передает его в свою внутреннюю процедуру преобразования, которая должным образом заявляет, что преобразование выполняется после каждого байта (не только после получения 2 байтов).
Я искал несколько сайтов в Интернете (включая этот) для этой, казалось бы, тривиальной задачи, но не нашел объяснения этому поведению или хорошей альтернативы, которая не включает в себя больше кода, чем я считаю нужным (например, Google поиск в Интернете производит тот же второй фрагмент кода, что и жизнеспособный фрагмент кода).
Единственное, что я обнаружил, что это работает, — это следующее, и я считаю, что это обман, поскольку он требует прямого доступа к внутреннему буферу wstring, а затем принудительно вводит его при этом.
std::wstring wsData;
/*** Open the file for reading, binary mode ***/
std::wifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode
wsData.resize (<Size of file in bytes> / sizeof (wchar_t));
ifFile.read ((char *) &wsData [0], <Size of file in bytes>);
О, и чтобы предупредить неизбежный вопрос «Зачем открывать файл в двоичном режиме, а не в текстовом режиме», это открытие является преднамеренным, как если бы файл был открыт в текстовом режиме (по умолчанию), это означает, что CR / LF («\ Последовательности r \ n «или 0x0D0A) будут преобразованы в последовательности LF (» \ n «или 0x0A), тогда как чтение файла в чистом байте сохранит их. Несмотря на это, для тех несгибаемых, изменение, которое, что неудивительно, не имело никакого эффекта.
Итак, два вопроса: почему второй случай не работает так, как можно было ожидать (то есть, что происходит с этими итераторами), и какой ваш любимый «кошерный STL-способ» загрузки файла символов Unicode в строку wstring? ?
Что мне здесь не хватает; это должно быть что-то глупое.
Крис
Вы должны быть разочарованы SO, чтобы не получить ответы на свой первый вопрос после
4-и с половиной месяцев. Это хороший вопрос, и ответы на большинство хороших вопросов
(хорошо или плохо) в течение нескольких минут. Две вероятные причины пренебрежения вашей:
Вы не пометили его как «C ++», поэтому многие программисты на C ++, которые могли бы помочь, никогда не получат
заметил это. (Я теперь отметил это «C ++».)
Ваш вопрос связан с обработкой потока Unicode, которая не является идеей классного кодирования.
Заблуждение, которое помешало вашим расследованиям, выглядит следующим образом:
считаю, что поток широких символов, std::wfstream
и строка широких символов, std::wstring
,
соответственно такие же, как «поток Unicode» и «строка Unicode», и, в частности, что
они соответственно такие же, как поток UTF-16 и строка UTF-16. Ни одна из этих вещей не является правдой.
std::wifstream
(std::basic_ifstream<wchar_t>
) является входным потоком, который преобразует
внешняя последовательность байтов для внутренней последовательности wchar_t
, в соответствии с указанным
или кодировка внешней последовательности по умолчанию.
Аналогично std::wofstream
(std::basic_ofstream<wchar_t>
) является выходным потоком, который
преобразует внутреннюю последовательность wchar_t
на внешнюю последовательность байтов, в соответствии с
заданная или заданная по умолчанию кодировка внешней последовательности.
И std::wstring
(std::basic_string<wchar_t>
) это строковый тип, который просто хранит
последовательность wchar_t
без знания кодировки, если таковой имеется, из которой они получены.
Unicode это семейство кодировок байтовых последовательностей — UTF-8 / -16 / -32 и некоторые другие неясные —
связано с тем, что UTF-N кодирует алфавиты, используя последовательность из 1 или более
N-битовые единицы на символ. UTF-16 — это кодировка, которую вы пытаетесь прочитать.
в std::wstring
, Ты говоришь:
Я бы подумал, что std :: istreambuf_iterator, специализирующийся на wchar_t, должен был приводить к чтению файла по два байта за раз, не так ли? Если нет, то какова его цель тогда?
Но как только ты это знаешь wchar_t
не обязательно 2 байта в ширину (это в библиотеках C Microsoft,
32- и 64-разрядные, но в GCC он имеет ширину 4 байта), а также кодовую точку UTF-16 (символ)
не нужно вписываться в 2 байта (это может потребовать 4), вы увидите, что это указывает на извлечение
единица измерения wchar_t
не может быть все, что нужно для декодирования потока UTF-16.
Когда вы создаете и открываете входной поток с помощью:
std::wifstream ifFile ("MyFile.txt", std::ios_base::binary);
Он подготовлен для извлечения символов (некоторого алфавита) из «MyFile.txt» в значения
типа wchar_t
и он извлечет эти символы из последовательности байтов в
файл в соответствии с кодировкой, указанной std::locale
это работает в потоке, когда это делает извлечение.
Ваш код не указывает std::locale
для вашего потока, поэтому библиотека по умолчанию вступает в силу.
По умолчанию используется глобальная локаль C ++, которая по умолчанию является
«C» локаль; и «C» локаль предполагает
«кодирование идентичности» последовательностей байтов ввода / вывода, т.е. 1 байт = 1 символ (
за исключением исключения новой строки для ввода-вывода в текстовом режиме).
Таким образом, когда вы используете свой std::istreambuf_iterator<wchar_t>
в
извлекать символы, извлечение продолжается путем преобразования каждого байта
в файле к wchar_t
который он добавляет к std::wstring wsData
, Байты
в файле, как вы говорите:
0xFF, 0xFE, «A», 0x00, «B», 0x00, «C», 0x00
Первые два, которые вы игнорируете как «ведущие байты Юникода», действительно
UTF-16 метка порядка байтов (BOM), но в кодировке по умолчанию они просто такие, какие они есть.
Соответственно широкие символы, назначенные wsData
как вы заметили:
0x00FF, 0x00FE, L’A ‘, 0x0000, L’B’, 0x0000, L’C ‘, 0x0000
Это как если бы файл все еще читался побайтово, а затем просто переводился в отдельные символы wchar_t.
потому что это именно то, что происходит.
Чтобы это не произошло, вам нужно что-то сделать, прежде чем вы начнете извлекать символы из потока.
сказать ему, что он должен декодировать последовательность символов UTF-16. Способ сделать это
концептуально довольно извилистый. Вам нужно imbue
поток с std::locale
который обладает
std::locale::facet
это экземпляр
std::codecvt<InternT, ExternT, StateT>
(или происходит от такого)
который предоставит потоку правильные методы из декодирования UTF-16 в wchar_t
,
Но суть в том, что вам нужно подключить правильный кодер / декодер UTF-16 к потоку и
на практике это (или должно быть) достаточно просто. Я предполагаю, что ваш компилятор является недавним MS VC ++.
Если это так, то вы можете исправить свой код:
#include <locale>
а также #include <codecvt>
к вашим заголовкамДобавление строки:
ifFile.imbue(std::locale(ifFile.getloc(),new std::codecvt_utf16<wchar_t,0x10ffff,std::little_endian>));
сразу после:
std::wifstream ifFile ("MyFile.txt", std::ios_base::binary);
Эффект этой новой линии заключается в том, чтобы «наполнить» ifFile
с новым языком, который такой же
как тот, который он уже имел — ifFile.getloc()
— но с измененным аспектом кодера / декодера
— std::codecvt_utf16<wchar_t,0x10ffff,std::little_endian>
, это codecvt
аспект
тот, который будет декодировать символы UTF-16 с максимальным значением 0x10ffff
в порядке байтов
wchar_t
ценности (0x10ffff
максимальное значение кодовых точек UTF-16).
Когда вы выполните отладку в коде, в который внесены поправки, вы обнаружите wsData
только 4 широких символа
и что эти персонажи:
0xFEFF, L'A', L'B', L'C'
как и следовало ожидать, первым из которых является спецификация UTF-16 с прямым порядком байтов.
Обратите внимание, что порядок FE
,FF
обратное тому, что было до применения
из codecvt
фасет, показывающий, что декодирование с прямым порядком байтов выполнено в соответствии с запросом.
И это должно было быть. Просто отредактируйте новую строку, удалив std::little_endian
,
отладить его снова, и вы обнаружите, что первый элемент wsData
становится 0xFFFE
и что три других широких символа становятся пиктограммами
IICore пиктографический
набор символов (если ваш отладчик может их отобразить). (Теперь, когда коллега
с изумлением жалуется, что их код превращает английский Unicode в «китайский»,
вы будете знать вероятное объяснение.)
Если вы хотите заселить wsData
без ведущей спецификации вы можете сделать это
внесение изменений в новую строку и замена std::little_endian
с
std::codecvt_mode(std::little_endian|std::consume_header)
Наконец, вы, возможно, хорошо заметили ошибку в новом коде, а именно, что 2-байтовый wchar_t
недостаточно широк для представления кодовых точек UTF-16 между 0x100000 и 0x10ffff
это можно прочитать.
Вам это сойдет с рук до тех пор, пока все кодовые точки, которые вы должны прочитать, лежат в
UTF-16 Базовая многоязычная плоскость,
который охватывает [0,0xffff], и вы можете знать, что все входные данные будут всегда подчиняться
ограничение. В противном случае 16-битный wchar_t
не подходит для цели. Заменить:
wchar_t
с char32_t
std::wstring
с std::basic_string<char32_t>
std::wifstream
с std::basic_ifstream<char32_t>
и код полностью подходит для считывания произвольного файла в кодировке UTF-16 в строку.
(Читатели, которые работают с библиотекой GNU C ++, обнаружат, что начиная с v4.7.2
это еще не обеспечивает <codecvt>
стандартный заголовок. Заголовок <bits/codecvt.h>
существует и, предположительно, когда-нибудь станет <codecvt>
, но на данный момент это только
экспортирует специализации class codecvt<char, char, mbstate_t>
а также
class codecvt<wchar_t, char, mbstate_t>
, которые соответственно идентичности
преобразование и преобразование между ASCII / UTF-8 и wchar_t
, Чтобы решить проблему ОП
вам нужно подкласс std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type>
себя согласно этот ответ)
Других решений пока нет …