Мне нужно шестнадцатеричное представление значения данных в Swift.
В конце концов я бы хотел использовать его следующим образом:
let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)
Мне нужно шестнадцатеричное представление значения данных в Swift.
В конце концов я бы хотел использовать его следующим образом:
let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)
Альтернативная реализация (взятая из Как склеить строку в sha1 с помощью Swift? с дополнительной опцией для ввода в верхнем регистре) будет
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return map { String(format: format, $0) }.joined()
}
}
Я выбрал метод hexEncodedString(options:)
в стиле существующего метода base64EncodedString(options:)
.
Data
соответствует протоколу Collection
, поэтому можно использовать
map()
, чтобы сопоставить каждый байт с соответствующей шестнадцатеричной строкой.
Формат %02x
печатает аргумент в базе 16, заполненный до двух цифр
с нулевым при необходимости. Модификатор hh
вызывает аргумент
(который передается как целое число в стеке) для обработки как один байт
количество. Здесь можно опустить модификатор, потому что $0
является неподписанным
номер (UInt8
), и расширение знака не произойдет, но это не вредит
это в.
Затем результат объединяется в одну строку.
Пример:
let data = Data(bytes: [0, 1, 127, 128, 255])
print(data.hexEncodedString()) // 00017f80ff
print(data.hexEncodedString(options: .upperCase)) // 00017F80FF
Следующая реализация выполняется быстрее примерно в 120 раз (проверено с 1000 случайными байтами). Это похоже на Решение RenniePet и Nick Moore, но на основе кодовых единиц UTF-16, что является тем, что Swift строки (в настоящее время) используются как внутреннее хранилище.
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef").utf16)
var chars: [unichar] = []
chars.reserveCapacity(2 * count)
for byte in self {
chars.append(hexDigits[Int(byte / 16)])
chars.append(hexDigits[Int(byte % 16)])
}
return String(utf16CodeUnits: chars, count: chars.count)
}
}
Моя версия. Это не так элегантно, но это примерно в 10 раз быстрее, чем принятый ответ Мартина Р.
extension Data {
private static let hexAlphabet = "0123456789abcdef".unicodeScalars.map { $0 }
public func hexEncodedString() -> String {
return String(self.reduce(into: "".unicodeScalars, { (result, value) in
result.append(Data.hexAlphabet[Int(value/16)])
result.append(Data.hexAlphabet[Int(value%16)])
}))
}
}
Этот код расширяет тип Data
с вычисленным свойством. Он выполняет итерацию через байты данных и объединяет шестнадцатеричное представление байта с результатом:
extension Data {
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
Это не отвечает на вопрос OP, поскольку он работает с массивом Swift byte, а не с объектом Data. И это намного больше, чем другие ответы. Но он должен быть более эффективным, поскольку он избегает использования String (format:).
В любом случае, в надежды кто-то находит это полезным...
public class StringMisc {
// MARK: - Constants
// This is used by the byteArrayToHexString() method
private static let CHexLookup : [Character] =
[ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" ]
// Mark: - Public methods
/// Method to convert a byte array into a string containing hex characters, without any
/// additional formatting.
public static func byteArrayToHexString(_ byteArray : [UInt8]) -> String {
var stringToReturn = ""
for oneByte in byteArray {
let asInt = Int(oneByte)
stringToReturn.append(StringMisc.CHexLookup[asInt >> 4])
stringToReturn.append(StringMisc.CHexLookup[asInt & 0x0f])
}
return stringToReturn
}
}
Тестовый пример:
// Test the byteArrayToHexString() method
let byteArray : [UInt8] = [ 0x25, 0x99, 0xf3 ]
assert(StringMisc.byteArrayToHexString(byteArray) == "2599F3")
Swift 4 - от данных к шестнадцатеричной строке
На основе решения Martin R, но даже чуть-чуть быстрее.
extension Data {
/// A hexadecimal string representation of the bytes.
func hexEncodedString() -> String {
let hexDigits = Array("0123456789abcdef".utf16)
var hexChars = [UTF16.CodeUnit]()
hexChars.reserveCapacity(count * 2)
for byte in self {
let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
hexChars.append(hexDigits[index1])
hexChars.append(hexDigits[index2])
}
return String(utf16CodeUnits: hexChars, count: hexChars.count)
}
}
Swift 4 - от шестнадцатеричной строки к данным
Я также добавил быстрое решение для преобразования шестнадцатеричной строки в данные (на основе решения на языке Си).
extension String {
/// A data representation of the hexadecimal bytes in this string.
func hexDecodedData() -> Data {
// Get the UTF8 characters of this string
let chars = Array(utf8)
// Keep the bytes in an UInt8 array and later convert it to Data
var bytes = [UInt8]()
bytes.reserveCapacity(count / 2)
// It is a lot faster to use a lookup map instead of strtoul
let map: [UInt8] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // HIJKLMNO
]
// Grab two characters at a time, map them and turn it into a byte
for i in stride(from: 0, to: count, by: 2) {
let index1 = Int(chars[i] & 0x1F ^ 0x10)
let index2 = Int(chars[i + 1] & 0x1F ^ 0x10)
bytes.append(map[index1] << 4 | map[index2])
}
return Data(bytes)
}
}
Примечание: эта функция не проверяет ввод. Убедитесь, что он используется только для шестнадцатеричных строк с (четным количеством) символов.
Возможно, не самый быстрый, но Как уже упоминалось в комментариях, это решение было ошибочным.data.map({ String($0, radix: 16) }).joined()
делает свою работу.
Возьмите каждый байт и преобразуйте его в шестнадцатеричный код, затем добавьте его к накопленному значению, которое начинается с пустой строки:
extension Data {
var hexString: String {
return self.reduce("", { $0 + String(format: "%02x", $1) })
}
}