如何快速将数据转换为十六进制字符串


78

我想要Swift中Data值的十六进制表示形式。

最终,我想像这样使用它:

let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)

Answers:


194

一种替代实现(取自“如何使用Swift将字符串加密为sha1?,并带有大写输出的附加选项)”

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)
    }
}

1
虽然我通常不喜欢extension苹果课堂上的func可以使用a的情况,但我喜欢与的对称性base64EncodedString
zaph

2
@reza_khalafi:Objective-C有很多解决方案,例如:stackoverflow.com/questions/1305225/…
Martin R

1
您还可以提供从字符串到十六进制数据的反向操作

1
@MiladFaridnia:Swift字符串在内部使用UTF-16 上面的函数返回一个仅包含数字0 ... 9和字母A ... F的字符串,因此应该没有问题。
Martin R

1
好的答案马丁
伍德斯托克

27

此代码Data使用计算属性扩展类型。它遍历数据字节,并将字节的十六进制表示连接到结果:

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

1
尽管此代码可能有助于解决问题,但并未解释为什么和/或如何回答问题。提供这种额外的环境将大大提高其长期价值。请编辑您的答案以添加解释,包括适用的限制和假设。
Toby Speight,

6
您只需将它转换为NSData的,并得到其描述return (self as NSData).description
狮子座Dabus

6
我最喜欢的(从stackoverflow.com/a/25762128/1187415):return map { String(format: "%02hhx", $0) }.joined()
马丁- [R

1
“对于每个字节,都将执行字符串分配和复制操作。” 我认为那是不对的。我认为Swift在优化String操作方面做得相当不错。stackoverflow.com/a/36397955/253938
RenniePet

我同意您的修改,删除了有关效率低下的警告。因此,我的上述评论应被忽略,但是为了后代,我将其保留在此处。
RenniePet

22

我的版本。它比Martin R最初接受的答案快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)])
        })
    }
}

尼克,很优雅。谢谢!
Vadim

1
如果您想将我的更改纳入此答案中,可以使它变得更好一点:gist.github.com/BenLeggiero/916bf788000736a7c0e6d1cad6e54410
Ben Leggiero

1
@BenLeggiero-完成!
尼克·摩尔

12

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-从十六进制字符串到数据
我还添加了一个快速解决方案,用于将十六进制字符串转换为Data(基于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)
  }
}

注意:此功能不验证输入。确保仅将其用于具有(偶数个)字符的十六进制字符串。


5

这实际上并不能回答OP的问题,因为它适用于Swift字节数组,而不是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")


0

也许不是最快的,但可以data.map({ String($0, radix: 16) }).joined()做到。如评论中所述,此解决方案存在缺陷。


1
这是有问题的,因为没有为单个十六进制数字插入前导零。示例:因为Data(bytes: [0x11, 0x02, 0x03, 0x44])它返回字符串“ 112344”而不是“ 11020344”。
马丁R

0

与这里的其他答案有些不同:

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%

Zyphrax:平均时间:0.032秒,相对标准偏差:2.9%

NickMoore:平均时间:0.039秒,相对标准偏差:2.0%

重复测试返回相同的相对结果。(尼克和马丁斯有时会交换)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.