如何在swift中将数据转换为十六进制字符串

n3ipq98p  于 2023-01-29  发布在  Swift
关注(0)|答案(8)|浏览(180)

我想要Swift中数据值的十六进制表示。
最后我想这样使用它:

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

hm2xizp91#

一个简单的实现(摘自如何在Swift中使用SHA1散列NSString?,带有用于大写输出的附加选项)如下所示

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 self.map { String(format: format, $0) }.joined()
    }
}

我选择了一个hexEncodedString(options:)方法,其样式与现有方法base64EncodedString(options:)相同。
Data符合Collection协议,因此可以使用map()将每个字节Map到相应的十六进制字符串。%02x以16为基数打印参数,如有必要,最多可填充两位数,并在前面加上零。hh修饰符导致参数(在堆栈上作为整数传递)被视为单字节量。可以省略这里的修饰符,因为$0是一个 * unsigned * 数(UInt8),不会发生符号扩展,但保留它没有坏处。
然后将结果联接到单个字符串。
示例:

let data = Data([0, 1, 127, 128, 255])
// For Swift < 4.2 use:
// let data = Data(bytes: [0, 1, 127, 128, 255])
print(data.hexEncodedString()) // 00017f80ff
print(data.hexEncodedString(options: .upperCase)) // 00017F80FF

以下实现的速度提高了约50倍(测试时使用了1000个随机字节),它的灵感来自RenniePet's solutionNick Moore's solution,但利用了String(unsafeUninitializedCapacity:initializingUTF8With:),后者随Swift 5.3/Xcode 12推出,可在macOS 11和iOS 14或更高版本上使用。
这种方法允许高效地从UTF-8单元创建Swift字符串,而无需不必要的复制或重新分配。
此外,还提供了适用于较旧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 * self.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 * self.count
            }
        } else {
            let utf16Digits = Array(hexDigits.utf16)
            var chars: [unichar] = []
            chars.reserveCapacity(2 * self.count)
            for byte in self {
                chars.append(utf16Digits[Int(byte / 16)])
                chars.append(utf16Digits[Int(byte % 16)])
            }
            return String(utf16CodeUnits: chars, count: chars.count)
        }
    }
}
nnt7mjpx

nnt7mjpx2#

这段代码用一个calculated属性扩展了Data类型,它遍历数据的字节,并将字节的十六进制表示连接到结果中:

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

bqf10yzr3#

我的版本。它比马丁·R·马丁(MartinR.)(最初的)公认答案快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)])
        })
    }
}
0wi1tuuw

0wi1tuuw4#

Swift 4 -从数据到十六进制字符串

基于Martin R's solution,但速度更快。

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 -从十六进制字符串到数据

我还添加了一个将十六进制String转换为Data的快速解决方案(基于C solution)。

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

**注意:**此函数不验证输入。请确保它仅用于具有(偶数个)字符的十六进制字符串。

ghhaqwfi

ghhaqwfi5#

向后兼容且快速的解决方案:

extension Data {
    /// Fast convert to hex by reserving memory (instead of mapping and join).
    public func toHex(uppercase: Bool = false) -> String {
        // Constants (Hex has 2 characters for each Byte).
        let size = self.count * 2;
        let degitToCharMap = Array((
            uppercase ? "0123456789ABCDEF" : "0123456789abcdef"
        ).utf16);
        // Reserve dynamic memory (plus one for null termination).
        let buffer = UnsafeMutablePointer<unichar>.allocate(capacity: size + 1);
        // Convert each byte.
        var index = 0
        for byte in self {
            buffer[index] = degitToCharMap[Int(byte / 16)];
            index += 1;
            buffer[index] = degitToCharMap[Int(byte % 16)];
            index += 1;
        }
        // Set Null termination.
        buffer[index] = 0;
        // Casts to string (without any copying).
        return String(utf16CodeUnitsNoCopy: buffer,
                      count: size, freeWhenDone: true)
    }
}
    • 请注意**上述内容将buffer的所有权传递给返回的String对象。

还要知道,因为Swift的内部String数据是UTF16(但从Swift 5开始可以是UTF8),在接受的答案中提供的所有解决方案都执行完整复制(并且速度较慢),至少如果不是#available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *);-)
正如我的个人资料中提到的,使用Apache 2.0许可证也是允许的(没有归属需要)。

u0sqgete

u0sqgete6#

这并没有真正回答OP的问题,因为它是在Swift字节数组上工作的,而不是Data对象。而且它比其他答案大得多。但它应该更有效,因为它避免了使用String(格式:).
不管怎样,希望有人觉得这个有用...

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")
9jyewag0

9jyewag07#

与其他答案有点不同:

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秒,相对标准偏差:百分之一点三
MartinR:时间平均值:0.037秒,相对标准偏差:百分之六点二
Zyphrax:时间平均值:0.032秒,相对标准偏差:百分之二点九
尼克·摩尔:时间平均值:0.039秒,相对标准偏差:百分之二
重复测试得到了相同的相对结果(尼克和马丁斯有时会互换)。
编辑:现在我用这个:

var hexEncodedString: String {
        return self.reduce(into:"") { result, byte in
            result.append(String(byte >> 4, radix: 16))
            result.append(String(byte & 0x0f, radix: 16))
        }
    }
cetgtptt

cetgtptt8#

也许不是最快的,但是data.map({ String($0, radix: 16) }).joined()完成了这项工作。正如评论中提到的,这个解决方案是有缺陷的。

相关问题