如何将NSData转换为NSString的十六进制字符串?

57

调用-description方法时,会看到一个NSData对象的十六进制字节的漂亮字符串,例如:

<f6e7cd28 0fc5b5d4 88f8394b af216506 bc1bba86 4d5b483d>
我想把数据的这个表示(去掉尖括号)存储到内存中的 NSString 中以便处理。我不想调用 -[NSData description] 然后再修剪 lt/gt 引号(因为我认为这不是 NSData 的公共接口的保证部分,可能会在未来更改)。
除了调用 -description 之外,最简单的方法是什么,可以将 NSData 对象的此表示转换为 NSString 对象?

1
那么,为什么不加上描述呢?(真的很好奇。我猜我会学到一些东西。) - Steven Fisher
1
@Steven:description的输出可能会因系统版本而异。例如,-[NSDate description]在Lion之前的文档中被记录为返回一个与-[NSDate initWithString:]所需格式完全相同的字符串;但这种保证不再存在。这里有一个简短的讨论:http://stackoverflow.com/questions/5837777/nsdates-initwithstring-is-returning-nil/5837852 - jscs
3
“description”仅用于调试目的,因为您无法保证“description”方法返回的值在未来的版本中不会发生更改(即使可能性很小)。例如,没有任何东西告诉您苹果公司不会有一天决定如果“[NSData length]>200”,则“description”返回的字符串将被截断在中间...或类似的情况。这就是为什么依赖于“description”返回的内容进行除调试/日志记录外的其他操作可能不是一个好的做法。 - AliSoftware
我写了一个将NSData转换为十六进制NSString的类别,你可以在这里找到:https://dev59.com/o3M_5IYBdhLWcg3wmkeu#9084784 它与AliSoftware的答案类似。 - Dave
调试时:快速查看十六进制作为可读字符串的方法(如果适用):https://dev59.com/SGw15IYBdhLWcg3wbLHU#38225931 - Leonard Pauli
显示剩余2条评论
9个回答

93

请记住,任何String(format: ...)的解决方案在处理大量数据时都会非常缓慢。

NSData *data = ...;
NSUInteger capacity = data.length * 2;
NSMutableString *sbuf = [NSMutableString stringWithCapacity:capacity];
const unsigned char *buf = data.bytes;
NSInteger i;
for (i=0; i<data.length; ++i) {
  [sbuf appendFormat:@"%02X", (NSUInteger)buf[i]];
}

如果你需要更高效的东西,请尝试这个:

更高性能的

static inline char itoh(int i) {
    if (i > 9) return 'A' + (i - 10);
    return '0' + i;
}

NSString * NSDataToHex(NSData *data) {
    NSUInteger i, len;
    unsigned char *buf, *bytes;
    
    len = data.length;
    bytes = (unsigned char*)data.bytes;
    buf = malloc(len*2);
    
    for (i=0; i<len; i++) {
        buf[i*2] = itoh((bytes[i] >> 4) & 0xF);
        buf[i*2+1] = itoh(bytes[i] & 0xF);
    }
    
    return [[NSString alloc] initWithBytesNoCopy:buf
                                          length:len*2
                                        encoding:NSASCIIStringEncoding
                                    freeWhenDone:YES];
}

Swift 版本


private extension Data {
    var hexadecimalString: String {
        let charA: UInt8 = 0x61
        let char0: UInt8 = 0x30
        func byteToChar(_ b: UInt8) -> Character {
            Character(UnicodeScalar(b > 9 ? charA + b - 10 : char0 + b))
        }
        let hexChars = flatMap {[
            byteToChar(($0 >> 4) & 0xF),
            byteToChar($0 & 0xF)
        ]}
        return String(hexChars)
    }
}

我相信 var p = UnsafeMutablePointer<UInt8>.alloc(length * 2) 可以改为 let p = ...(通过 Swift 2 编译器警告) - Ilias Karim
1
这真值得来这里学习String(bytesNoCopy:length:encoding:)。它的性能非常出色...我从中学到了一些东西。 - definitelyokay
如果您是使用Realm的开发人员,您可以使用此方法从Keychain获取加密密钥。 - Pedro Paulo Amorim

30

我同意这个解决方案,不调用description方法,因为它是保留给调试的,所以这是一个好观点和好问题 :)

最简单的解决方案是循环遍历NSData字节并从中构建NSString。使用[yourData bytes]来访问字节,并将字符串构建到一个NSMutableString中。

下面是一个通过实现NSData类别来实现此功能的示例:

@interface NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces;
@end

@implementation NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces
{
    const unsigned char* bytes = (const unsigned char*)[self bytes];
    NSUInteger nbBytes = [self length];
    //If spaces is true, insert a space every this many input bytes (twice this many output characters).
    static const NSUInteger spaceEveryThisManyBytes = 4UL;
    //If spaces is true, insert a line-break instead of a space every this many spaces.
    static const NSUInteger lineBreakEveryThisManySpaces = 4UL;
    const NSUInteger lineBreakEveryThisManyBytes = spaceEveryThisManyBytes * lineBreakEveryThisManySpaces;
    NSUInteger strLen = 2*nbBytes + (spaces ? nbBytes/spaceEveryThisManyBytes : 0);

    NSMutableString* hex = [[NSMutableString alloc] initWithCapacity:strLen];
    for(NSUInteger i=0; i<nbBytes; ) {
        [hex appendFormat:@"%02X", bytes[i]];
        //We need to increment here so that the every-n-bytes computations are right.
        ++i;

        if (spaces) {
            if (i % lineBreakEveryThisManyBytes == 0) [hex appendString:@"\n"];
            else if (i % spaceEveryThisManyBytes == 0) [hex appendString:@" "];
        }
    }
    return [hex autorelease];
}
@end

使用方法:

NSData* data = ...
NSString* hex = [data hexRepresentationWithSpaces_AS:YES];

1
吹毛求疵:如果你想匹配描述,你应该使用“%02x”,而不是“%02X”。(区别在于小写和大写字母。) - Steven Fisher
1
不错的代码片段。我稍微修改后已经在这里作为gist可用:https://gist.github.com/1443455 - Johan Kool
当我使用这个解决方案时,十六进制值90会变成ffffff90。这是怎么回事? - Daniel T.
你的0x90输入值可能是有符号整数,而不是无符号的吗?请解释一下符号位传播和你得到的表示形式,看起来像是2的补码十六进制形式?你的确切代码是什么? - AliSoftware

26

我想补充一下,@PassKits的方法可以在Swift 3中非常优雅地编写,因为Data现在是一个集合。

extension Data { 
    var hex: String {
        var hexString = ""
        for byte in self {
            hexString += String(format: "%02X", byte)
        }

        return hexString
    }
}

或者...

extension Data {
    var hex: String {
        return self.map { b in String(format: "%02X", b) }.joined()
    }
}

甚至可以通过将数据发送到云端来实现此功能。在那里,它可以使用更强大的计算机进行处理,并且结果可以发送回移动设备。

extension Data {
    var hex: String {
        return self.reduce("") { string, byte in
            string + String(format: "%02X", byte)
        }
    }
}

2
一个计算属性可能更适合Swift的风格。 - David Lawson

22

我最喜欢 @Erik_Aigner 的回答。我稍微重构了一下:

NSData *data = [NSMutableData dataWithBytes:"acani" length:5];
NSUInteger dataLength = [data length];
NSMutableString *string = [NSMutableString stringWithCapacity:dataLength*2];
const unsigned char *dataBytes = [data bytes];
for (NSInteger idx = 0; idx < dataLength; ++idx) {
    [string appendFormat:@"%02x", dataBytes[idx]];
}

8

在Swift中,您可以创建一个扩展。

extension NSData {

    func toHexString() -> String {

        var hexString: String = ""
        let dataBytes =  UnsafePointer<CUnsignedChar>(self.bytes)

        for (var i: Int=0; i<self.length; ++i) {
            hexString +=  String(format: "%02X", dataBytes[i])
        }

        return hexString
    }
}

然后您可以简单地使用:

let keyData: NSData = NSData(bytes: [0x00, 0xFF], length: 2)

let hexString = keyData.toHexString()
println("\(hexString)") // Outputs 00FF

3

很遗憾,NSData没有内置的方法生成十六进制数,但是自己实现也很容易。简单的方法是将连续的字节传递给sprintf("%02x"),并将其累加到NSMutableString中。更快的方法是构建一个查找表,将4个位映射为一个十六进制字符,然后将连续的nibble传递到该表中。


1
ن¸چ需è¦پن½؟用sprintfï¼›NSMutableStringوœ‰appendFormat:م€‚ - Peter Hosey

1

既然评论中有Swift 1.2代码片段,现在提供Swift 2版本,因为C样式的for循环已经被弃用。

Gist带有MIT许可证和两个简单的单元测试(如果您关心)。

以下是代码,以方便查看:

import Foundation

extension NSData {
  var hexString: String {
    let pointer = UnsafePointer<UInt8>(bytes)
    let array = getByteArray(pointer)

    return array.reduce("") { (result, byte) -> String in
      result.stringByAppendingString(String(format: "%02x", byte))
    }
  }

  private func getByteArray(pointer: UnsafePointer<UInt8>) -> [UInt8] {
    let buffer = UnsafeBufferPointer<UInt8>(start: pointer, count: length)

    return [UInt8](buffer)
  }
}

1

虽然这可能不是最有效的方法,但如果您正在进行调试,则SSCrypto具有NSData类别,其中包含两种方法来执行此操作(一种用于创建原始字节值的NSString,另一种则显示更漂亮的表示形式)。

http://www.septicus.com/SSCrypto/trunk/SSCrypto.m


0

假设您已经设置好:

NSData *myData = ...;

简单的解决方案:

NSString *strData = [[NSString alloc]initWithData:myData encoding:NSUTF8StringEncoding];
NSLog(@"%@",strData);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接