source

데이터를 16진수 문자열로 빠르게 변환하는 방법

manycodes 2023. 9. 18. 22:33
반응형

데이터를 16진수 문자열로 빠르게 변환하는 방법

저는 스위프트에서 데이터 값을 16진수로 표현하고 싶습니다.

결국 이렇게 사용하고 싶습니다.

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

간단한 구현(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()각 바이트를 대응하는 16진수 문자열에 매핑합니다.%02xformat은 필요한 경우 선두 0으로 두 자리까지 채워지는 베이스 16에서 인수를 출력합니다.hhmodifier를 사용하면 인수(스택에서 정수로 전달됨)가 1바이트 수량으로 처리됩니다.여기에 수식어를 생략할 수 있는 이유는$0는 부호가 없는 숫자(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솔루션과 Nick Moore의 솔루션에서 영감을 받았지만 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)
        }
    }
}

이 코드는 다음을 확장합니다.Data계산된 속성을 가진 타자데이터의 바이트를 반복하고 바이트의 16진수 표현을 결과와 연결합니다.

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

제 버전.마틴 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)])
        })
    }
}

Swift 4 - 데이터에서 16진수 문자열로
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 - 16진수 문자열에서 데이터로
또한 16진수 문자열을 데이터(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)
  }
}

참고: 이 함수는 입력의 유효성을 검사하지 않습니다.문자가 짝수인 16진수 문자열에만 사용되는지 확인합니다.

역호환성 및 빠른 솔루션:

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물건.

그리고 스위프트의 내부가StringUTF16(그러나 될 수 있음)UTF8 5), 은 전체 느림)를합니다. Swift 5)후,된서든는은체및사다를도가지림을우및를e(지도을다우(ltdo,5가l사,rnsrdt림#available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *);-)

내 프로필에 언급된 바와 같이, 아래의 사용.Apache 2.0라이센스도 허용됩니다(귀속 필요 없음).

이것은 데이터 개체가 아닌 스위프트 바이트 배열에서 작동하기 때문에 OP의 질문에 실제로 답하지 않습니다.그리고 다른 대답들보다 훨씬 더 큽니다.그러나 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")

여기서 다른 답변과 약간 다릅니다.

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

그러나 기본 XCT 테스트 + 측정 설정에서는 시도한 4개 중 가장 빨랐습니다.

1000바이트의 (동일한) 랜덤 데이터를 각각 100번씩 경험하는 것:

이상: 시간평균: 0.028초, 상대표준편차: 1.3%

마틴 R:시간평균: 0.037초, 상대표준편차: 6.2%

자이프랙스: 시간평균: 0.032초, 상대표준편차: 2.9%

닉 무어:시간평균: 0.039초, 상대표준편차: 2.0%

검정을 반복하면 동일한 상대 결과가 반환됩니다.(닉과 마틴스는 가끔 서로 교환하기도 합니다.)

편집: 요즘은 다음을 사용합니다.

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

아마 가장 빠르지는 않겠지만, data.map({ String($0, radix: 16) }).joined() 그 일을 합니다. 댓글에 언급된 것처럼, 이 솔루션은 결함이 있었습니다.

언급URL : https://stackoverflow.com/questions/39075043/how-to-convert-data-to-hex-string-in-swift

반응형