webentwicklung-frage-antwort-db.com.de

Wie konvertiert man Daten schnell in einen Hex-String?

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)
51
marius

Eine alternative Implementierung (übernommen aus Wie man den String mit shift zu sha1? verschlüsselt, 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 wählte eine hexEncodedString(options:)-Methode im Stil der vorhandenen Methode base64EncodedString(options:).

Data entspricht dem Collection-Protokoll. Daher kann mit __.map() jedes Byte der entsprechenden Hex-Zeichenfolge ..__ zugeordnet werden. Das %02x-Format druckt das Argument in der Basis 16, gefüllt mit zwei Ziffern mit einer führenden Null wenn erforderlich. Der hh-Modifizierer bewirkt, dass das Argument (Das als Ganzzahl im Stapel übergeben wird) als ein Byte Menge behandelt wird. Man könnte den Modifikator hier weglassen, weil $0 eine unsigned Nummer (UInt8) ist und keine Vorzeichenerweiterung auftritt, aber es schadet nicht.

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 120 schneller (getestet mit 1000 zufälligen Bytes). Es ist ähnlich wie RenniePets Lösung Und Nick Moore's Lösung , basiert aber auf UTF-16-Code-Units, die Swift -Strings (derzeit) als internen Speicher verwenden.

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)
    }
}
140
Martin R

Meine Version. Es ist nicht so elegant, aber es ist etwa zehnmal schneller als die akzeptierte Antwort von Martin R.

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)])
        }))
    }
}
14
Nick Moore

Dieser Code erweitert den Typ Data um eine berechnete Eigenschaft. Es iteriert durch die Bytes der Daten und verkettet die hexadezimale Darstellung des Bytes mit dem Ergebnis:

extension Data {
    var hexDescription: String {
        return reduce("") {$0 + String(format: "%02x", $1)}
    }
}
13
marius

Dies beantwortet die Frage des OP nicht wirklich, da es auf einem Swift-Byte-Array und nicht auf einem Data-Objekt arbeitet. Und es ist viel größer als die anderen Antworten. Es sollte jedoch effizienter sein, da es die Verwendung von String (format:) vermeidet. 

Wie auch immer, in der Hoffnung, jemand findet das nützlich ...

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")
5
RenniePet

Swift 4 - Von Daten zu Hex-String
Basierend auf Martin Rs Lösung 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 - Vom Hex-String zu Daten
Ich habe auch eine schnelle Lösung hinzugefügt, um einen Hex-String in Daten umzuwandeln (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.

4
Zyphrax

Nehmen Sie jedes Byte, konvertieren Sie es in Hex und hängen Sie es an den akkumulierten Wert an, der als leere Zeichenfolge beginnt:

extension Data {
    var hexString: String {
        return self.reduce("", { $0 + String(format: "%02x", $1) })
    }
}
1
lmbcosta

Vielleicht nicht der Schnellste, aber data.map({ String($0, radix: 16) }).joined() erledigt die Arbeit. Wie in den Kommentaren erwähnt, war diese Lösung fehlerhaft.

0
DeFrenZ