신속한 데이터를 16 진수 문자열로 변환하는 방법


81

Swift에서 데이터 값의 16 진수 표현을 원합니다.

결국 다음과 같이 사용하고 싶습니다.

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

답변:


198

간단한 구현 ( 대문자 출력에 대한 추가 옵션과 함께 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 map { String(format: format, $0) }.joined()
    }
}

나는 hexEncodedString(options:)기존 방식의 방식을 선택했다 base64EncodedString(options:).

DataCollection프로토콜을 준수 하므로 map()각 바이트를 해당 16 진수 문자열에 매핑 하는 데 사용할 수 있습니다 . %02x형식은 필요한 경우는 앞에 0이 두 자리까지 채워베이스 (16)의 인수를 인쇄합니다. hh개질제 (스택상의 정수로 전달된다) 한 바이트로서 처리 될 인수시킨다. 부호$0없는 숫자 ( UInt8)이고 부호 확장이 발생하지 않기 때문에 여기에서 수정자를 생략 할 수 있지만 그대로 두는 데 아무런 해가 없습니다.

그런 다음 결과는 단일 문자열로 결합됩니다.

예:

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

다음 구현은 약 50 배 더 빠릅니다 (1000 개의 임의 바이트로 테스트 됨). RenniePet의 솔루션Nick Moore의 솔루션 에서 영감을 얻었 지만 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 * 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)
        }
    }
}

1
나는 일반적으로 extensiona를 func사용할 수있을 때 Apple 클래스에서를 좋아하지 않지만 base64EncodedString.
zaph

2
@reza_khalafi : Objective-C에 대한 많은 솔루션이 있습니다 (예 : stackoverflow.com/questions/1305225/…) .
Martin R

1
또한 문자열에서 16 진수 데이터로의 역 동작을 제공 할 수 있습니까?
nkp

1
@MiladFaridnia : UTF-16은 Swift 문자열이 내부적으로 사용하는 것 입니다. 위의 함수는 숫자 0 ... 9와 문자 A ... F 만 포함하는 문자열을 반환하므로 문제가되지 않습니다.
Martin R

1
그레이트 대답 Martin
Woodstock

28

이 코드 Data는 계산 된 속성으로 유형을 확장합니다 . 데이터 바이트를 반복하고 바이트의 16 진 표현을 결과에 연결합니다.

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

1
이 코드는 문제를 해결하는 데 도움이 될 수 있지만 질문에 대한 이유 및 / 또는 답변 방법을 설명하지 않습니다 . 이 추가 컨텍스트를 제공하면 장기적인 가치가 크게 향상됩니다. 제발 편집 제한 및 가정이 적용되는 것을 포함하여, 설명을 추가 답변을.
Toby Speight

6
당신은을 NSData로 캐스팅하고 설명을 얻을 수 있습니다return (self as NSData).description
레오 버스들이시길

6
내가 가장 좋아하는 것 ( stackoverflow.com/a/25762128/1187415에서 ) :return map { String(format: "%02hhx", $0) }.joined()
Martin R

1
"모든 바이트에 대해 수행되는 문자열 할당 및 복사 작업이 있습니다." 나는 그것이 옳지 않다고 생각합니다. 저는 Swift가 String 조작을 최적화하는 데 상당히 잘했다고 생각합니다. stackoverflow.com/a/36397955/253938
RenniePet 2017 년

귀하의 편집에 동의하며 효율성 저하에 대한 경고를 제거합니다. 따라서 위의 설명은 무시해야하지만 후손을 위해 남겨 두겠습니다.
RenniePet

23

내 버전. 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)])
        })
    }
}

1
우아 해요, 닉 감사!
Vadim

1
이 대답에 나의 변화를 가져 싶다면 더 나은 이것을 재미있는 이야기를 할 수 있었다 : gist.github.com/BenLeggiero/916bf788000736a7c0e6d1cad6e54410
벤 Leggiero

12

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 진수 문자열에만 사용되는지 확인하십시오.


5

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

다음은이 답변보다 '스위프 티 버전입니다 : gist.github.com/BenLeggiero/ce1e62fe0194ca969eb7cdda6639a011는
벤 Leggiero

0

가장 빠르지는 않지만 data.map({ String($0, radix: 16) }).joined()작업을 수행합니다. 의견에서 언급했듯이이 솔루션에는 결함이 있습니다.


1
한 자리 16 진수에 선행 0이 삽입되지 않기 때문에 문제가됩니다. 예 : Data(bytes: [0x11, 0x02, 0x03, 0x44])"11020344"대신 문자열 "112344"를 반환합니다.
Martin 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 %

테스트를 반복하면 동일한 상대 결과가 반환되었습니다. (Nick과 Martins는 때때로 바꿨습니다)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.