У меня есть байты для строки, закодированной с помощью utf16 big endian. Эти байты читаются мной из файла, которым поделился со мной мой коллега, который подтверждает, что строка является бигендианской utf16.
В демонстрационных целях я прочитал файл, чтобы интерпретировать строку. Код выглядит следующим образом:
let bundle = Bundle(for: ViewController.self)
guard let url = bundle.url(forResource: "TestBingEndian", withExtension: "txt") else { return }
let data = try! Data(contentsOf: url)
print(data)
let bigEndianString = String(bytes: data, encoding: .utf16BigEndian)
print("bigEndianString: \(bigEndianString!)")
let littleEndian = String(bytes: data, encoding: .utf16LittleEndian)
print("littleEndian: \(littleEndian!)")
let endiannessNotSpecifiedString = String(bytes: data, encoding: .utf16)
print("endiannessNotSpecifiedString: \(endiannessNotSpecifiedString!)")
Вывод для bigEndianString
такой, как и ожидалось.
Вывод для littleEndian
был бесполезен, так как в моем случае это был мусор.
Результат для endiannessNotSpecifiedString
также оказался ожидаемым и совпал с bigEndianString
.
Итак, мой вопрос: являются ли .utf16 и .utf16BigEndian одним и тем же?
PS: Моя машина с прямым порядком байтов. Я думал, что .utf16 должен быть тем, чем являются мои машины. Но, согласно моим тестам, это бигендиан.
Итак, мой вопрос: являются ли .utf16 и .utf16BigEndian одним и тем же?
Нет. Правильный код UTF-16 должен содержать спецификацию в начале файла.
let str = "Hello, World!"
let dataUTF16 = str.data(using: .utf16)!
print(dataUTF16 as NSData)
let dataUTF16BE = str.data(using: .utf16BigEndian)!
print(dataUTF16BE as NSData)
let dataUTF16LE = str.data(using: .utf16LittleEndian)!
print(dataUTF16LE as NSData)
Выход:
<fffe4800 65006c00 6c006f00 2c002000 57006f00 72006c00 64002100>
<00480065 006c006c 006f002c 00200057 006f0072 006c0064 0021>
<48006500 6c006c00 6f002c00 20005700 6f007200 6c006400 2100>
0xff, 0xfe представляет спецификацию с прямым порядком байтов. В обратном порядке это будет 0xfe, 0xff.
С помощью .utf16
вы можете читать правильные данные UTF-16 (я имею в виду правильную спецификацию) даже на платформе с несовпадением байтов.
Поставьте print(data as NSData)
и проверьте первые два байта вашего data
. Я предполагаю, что он содержит 0xfe, 0xff (BOM в обратном порядке.)
Кажется, мое предположение было неверным, и .utf16
в Apple Foundation предпочитает обратный порядок байтов, а не родной порядок байтов платформы, когда спецификация не найдена. (Возможно, есть какая-то историческая причина, поскольку Apple использовала платформы Big Endian, 68k или Power-PC.A с комментарием Мартина Р., он определен в стандарте Unicode. Кажется, мне нужно освежить свои знания.)
Но вам лучше указать .utf16BigEndian
, когда вы знаете, что ваши данные не содержат спецификацию и в Big Endian, .utf16
для данных, содержащих правильную спецификацию.
let str = "Hello, World!"
let dataUTF16 = str.data(using: .utf16)!
print(dataUTF16 as NSData)
let strUTF16asUTF16 = String(data: dataUTF16, encoding: .utf16)
debugPrint(strUTF16asUTF16) //->Optional("Hello, World!")
let strUTF16asUTF16BE = String(data: dataUTF16, encoding: .utf16BigEndian)
debugPrint(strUTF16asUTF16BE) //->Optional("䠀攀氀氀漀Ⰰ 圀漀爀氀搀℀")
let strUTF16asUTF16LE = String(data: dataUTF16, encoding: .utf16LittleEndian)
debugPrint(strUTF16asUTF16LE) //->Optional("Hello, World!")
Когда почти все символы состоят из символов ASCII, некоторое предсказание порядка байтов сработает, но когда большинство из них состоит из символов, отличных от ASCII, такие предсказания могут быть неверными. Это применимо, если вы прогнозируете порядок байтов.
Но в целом вы должны использовать стандарт Unicode, в котором говорится, что если нет спецификации, которую нужно найти, вы должны рассматривать байты как big endian.
Мой вывод: <00480061 00720065 00730068 00200050 00690073 0065> Здесь явно отсутствует спецификация.
@RohanBhale, спасибо за уточнение, мое предположение было неверным, но я могу показать вам пример того, что .utf16
и utf16BigEndian
работают по-разному.
Ты прав. Я дополнительно прочитал здесь en.wikipedia.org/wiki/Byte_order_mark.
Если спецификации нет, то можно определить, является ли текст UTF-16, и его порядок байтов путем поиска символов ASCII (т. е. 0 байт, примыкающий к байту в диапазоне 0x20-0x7E, а также 0x0A и 0x0D для CR и ЛФ). Большое число (т. е. гораздо большее, чем случайное совпадение) в одном и том же порядке является очень хорошим признаком UTF-16, и то, находится ли 0 в четных или нечетных байтах, указывает порядок байтов. Однако это может привести как к ложноположительным, так и к ложноотрицательным результатам. В целом есть вероятность, что в некоторых случаях .utf16 может дать сбой. Они предсказывают это, используя шаблоны, и предсказания могут не сработать.
Спасибо за подробный комментарий. На самом деле, я не знал, что Apple .utf16
использует Big Endian по умолчанию (не угадывая порядок следования байтов, не используя порядок байтов платформы). Кажется, теперь вы очень хорошо знаете, какую кодировку следует использовать для ваших данных.
По-видимому, это по-прежнему применяется к строкам Swift: «При создании объекта NSString из строки в кодировке UTF-16 (или потока байтов, интерпретируемого как UTF-16), если порядок байтов не указан иначе, NSString предполагает, что символы UTF-16 имеют обратный порядок байтов,, если нет BOM (знак порядка байтов), и в этом случае BOM диктует порядок байтов».
@MartinR Интересно, поэтому Apple не предсказывает (вики предполагает, что в таком случае должно быть предсказание). Предполагается, что это прямой порядок байтов.
@RohanBhale: В этой статье Wiki также цитируется стандарт Unicode: «Схема кодирования UTF-16 может начинаться со спецификации, а может и не начинаться. Однако при отсутствии спецификации и протокола более высокого уровня порядок байтов в схеме кодирования UTF-16 является обратным порядком байтов».
@MartinR Спасибо. Это также проясняет мои основные сомнения, почему из-за этого не происходит сбоев приложений. Таким образом, существует четкий стандарт для использования BigEndian в случае отсутствия спецификации.
Вы сгенерировали байты из строки программно. Я распечатал свои байты как NSData, как вы сказали, а 0xfe, 0xff отсутствуют. Спецификация отсутствует в байтах, которые я читал из файла. Итак, как в этом случае решается порядок следования байтов?