Ich möchte die hexadezimale Darstellung eines Datenwerts in Swift.
Irgendwann möchte ich es so verwenden:
let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)
Eine einfache Implementierung (entnommen aus How to Hash NSString mit SHA1 in Swift ? , Mit einer zusätzlichen Option für die Ausgabe in Großbuchstaben) wäre
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()
}
}
Ich habe eine hexEncodedString(options:)
Methode im Stil der vorhandenen Methode gewählt base64EncodedString(options:)
.
Data
entspricht dem Collection
Protokoll, daher kann man
map()
jedes Byte der entsprechenden Hex-Zeichenfolge zuordnen. Das %02x
Format gibt das Argument in Basis 16 aus, das bei Bedarf bis zu zwei Ziffern mit einer führenden Null gefüllt ist. Der hh
Modifikator bewirkt, dass das Argument (das als Ganzzahl auf dem Stapel übergeben wird) als eine 1-Byte-Menge behandelt wird. Man könnte den Modifikator hier weglassen, da $0
es sich um eine vorzeichenlose
Zahl ( UInt8
) handelt und keine Vorzeichenerweiterung auftritt, aber es schadet nicht, sie zu belassen.
Das Ergebnis wird dann zu einer einzelnen Zeichenfolge verbunden.
Beispiel:
let data = Data(bytes: [0, 1, 127, 128, 255])
print(data.hexEncodedString()) // 00017f80ff
print(data.hexEncodedString(options: .upperCase)) // 00017F80FF
Die folgende Implementierung ist um den Faktor 50 schneller (getestet mit 1000 zufälligen Bytes). Es ist inspiriert
RenniePet-Lösung
und Nick Moores Lösung , sondern nutzt
String(unsafeUninitializedCapacity:initializingUTF8With:)
die mit Swift 5.3 / Xcode 12 eingeführt wurde und auf macOS 11 und iOS 14 oder höher verfügbar.
Diese Methode ermöglicht es, einen Swift-String effizient aus UTF-8-Einheiten zu erstellen, ohne unnötiges Kopieren oder Neuzuweisen.
Eine alternative Implementierung für ältere MacOS / iOS-Versionen wird ebenfalls bereitgestellt.
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)
}
}
}
extension
Apple-Klasse mag, wenn einefunc
verwendet werden kann, liebe ich die Symmetrie mitbase64EncodedString
.Dieser Code erweitert den
Data
Typ um eine berechnete Eigenschaft. Es durchläuft die Datenbytes und verkettet die Hex-Darstellung des Bytes mit dem Ergebnis:extension Data { var hexDescription: String { return reduce("") {$0 + String(format: "%02x", $1)} } }
quelle
return (self as NSData).description
return map { String(format: "%02hhx", $0) }.joined()
Meine Version. Es ist ungefähr zehnmal schneller als die [ursprüngliche] akzeptierte Antwort von Martin R.
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)]) }) } }
quelle
Swift 4 - Von Daten zu Hex-Zeichenfolgen
Basierend auf der Lösung von Martin R, aber noch ein bisschen schneller.
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 - Von Hex-String zu Daten
Ich habe auch eine schnelle Lösung zum Konvertieren eines Hex-Strings in Daten hinzugefügt (basierend auf einer C-Lösung ).
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) } }
Hinweis: Diese Funktion validiert die Eingabe nicht. Stellen Sie sicher, dass es nur für hexadezimale Zeichenfolgen mit (einer geraden Anzahl von) Zeichen verwendet wird.
quelle
Dies beantwortet die Frage des OP nicht wirklich, da es auf einem Swift-Byte-Array und nicht auf einem Datenobjekt funktioniert. Und es ist viel größer als die anderen Antworten. Es sollte jedoch effizienter sein, da die Verwendung von String (Format :) vermieden wird.
Wie auch immer, in der Hoffnung, dass jemand dies nützlich findet ...
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 } }
Testfall:
// Test the byteArrayToHexString() method let byteArray : [UInt8] = [ 0x25, 0x99, 0xf3 ] assert(StringMisc.byteArrayToHexString(byteArray) == "2599F3")
quelle
Vielleicht nicht der schnellste, macht aberWie in den Kommentaren erwähnt, war diese Lösung fehlerhaft.data.map({ String($0, radix: 16) }).joined()
den Job.quelle
Data(bytes: [0x11, 0x02, 0x03, 0x44])
wird die Zeichenfolge "112344" anstelle von "11020344" zurückgegeben.Ein bisschen anders als andere Antworten hier:
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() } }
In meinem grundlegenden XCTest + -Mess-Setup war dies jedoch das schnellste der 4, die ich ausprobiert habe.
Durchlaufen von 1000 Bytes (derselben) Zufallsdaten jeweils 100 Mal:
Oben: Zeitdurchschnitt: 0,028 Sekunden, relative Standardabweichung: 1,3%
MartinR: Zeitdurchschnitt: 0,037 Sekunden, relative Standardabweichung: 6,2%
Zyphrax: Zeitdurchschnitt: 0,032 Sekunden, relative Standardabweichung: 2,9%
NickMoore: Zeitdurchschnitt: 0,039 Sekunden, relative Standardabweichung: 2,0%
Das Wiederholen des Tests ergab die gleichen relativen Ergebnisse. (Nick und Martins tauschten manchmal)
quelle