Я хочу шістнадцяткове представлення значення даних у Swift.
Зрештою я хотів би використовувати його так:
let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)
Відповіді:
Проста реалізація (взята з " Як хеш NSString за допомогою 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
Наступна реалізація є швидшою приблизно в 50 разів (перевіряється з 1000 випадковими байтами). Він натхненний
рішення RenniePet в
і рішення Ніка Мура , але має перевагу
, String(unsafeUninitializedCapacity:initializingUTF8With:)
яке було введено з Swift 5.3 / Xcode 12 і доступна на MacOS 11 і прошивкою 14 або пізнішої версії.
Цей метод дозволяє ефективно створювати рядок Swift з блоків UTF-8, без зайвого копіювання або перерозподілу.
Також пропонується альтернативна реалізація для старих версій macOS / iOS.
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef"
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
let utf8Digits = Array(hexDigits.utf8)
return String(unsafeUninitializedCapacity: 2 * count) { (ptr) -> Int in
var p = ptr.baseAddress!
for byte in self {
p[0] = utf8Digits[Int(byte / 16)]
p[1] = utf8Digits[Int(byte % 16)]
p += 2
}
return 2 * count
}
} else {
let utf16Digits = Array(hexDigits.utf16)
var chars: [unichar] = []
chars.reserveCapacity(2 * count)
for byte in self {
chars.append(utf16Digits[Int(byte / 16)])
chars.append(utf16Digits[Int(byte % 16)])
}
return String(utf16CodeUnits: chars, count: chars.count)
}
}
}
Цей код розширює Data
тип із обчисленою властивістю. Він перебирає байти даних і об’єднує шістнадцяткове представлення байта з результатом:
extension Data {
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
return (self as NSData).description
return map { String(format: "%02hhx", $0) }.joined()
Моя версія. Це приблизно в 10 разів швидше, ніж [оригінальна] прийнята відповідь Мартіна Р.
public extension Data {
private static let hexAlphabet = Array("0123456789abcdef".unicodeScalars)
func hexStringEncoded() -> String {
String(reduce(into: "".unicodeScalars) { result, value in
result.append(Self.hexAlphabet[Int(value / 0x10)])
result.append(Self.hexAlphabet[Int(value % 0x10)])
})
}
}
Swift 4 - від даних до шістнадцяткової рядка на
основі рішення Мартіна Р, але навіть крихітно швидше.
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 - від шістнадцяткового рядка до даних
Я також додав швидке рішення для перетворення шістнадцяткового рядка в дані (на основі рішення C ).
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)
}
}
Примітка: ця функція не перевіряє введені дані. Переконайтесь, що він використовується лише для шістнадцяткових рядків із (парною кількістю) символів.
Це насправді не відповідає на питання OP, оскільки воно працює на байтовому масиві Swift, а не на об'єкті Data. І це набагато більше, ніж інші відповіді. Але це повинно бути більш ефективним, оскільки воно уникає використання рядка (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")
Можливо, не найшвидший, але Як зазначалося в коментарях, це рішення було недосконалим.data.map({ String($0, radix: 16) }).joined()
робить свою роботу.
Data(bytes: [0x11, 0x02, 0x03, 0x44])
нього повертається рядок "112344" замість "11020344".
Дещо відрізняється від інших відповідей тут:
extension DataProtocol {
func hexEncodedString(uppercase: Bool = false) -> String {
return self.map {
if $0 < 16 {
return "0" + String($0, radix: 16, uppercase: uppercase)
} else {
return String($0, radix: 16, uppercase: uppercase)
}
}.joined()
}
}
Однак у моїй базовій установці міри XCTest + це було найшвидше з 4-х, які я спробував.
Перебираючи 1000 байт (однакових) випадкових даних по 100 разів кожен:
Вгорі: Середнє за часом: 0,028 секунди, відносне стандартне відхилення: 1,3%
MartinR: Середнє значення часу: 0,037 секунди, відносне стандартне відхилення: 6,2%
Зифракс: Середній час: 0,032 секунди, відносне стандартне відхилення: 2,9%
NickMoore: Середнє значення часу: 0,039 секунди, відносне стандартне відхилення: 2,0%
Повторення тесту дало ті самі відносні результати. (Нік і Мартінс іноді міняються місцями)
extension
клас на Apple, колиfunc
можна використовувати a, я люблю симетріюbase64EncodedString
.