将NSData字节转换为NSString?

50
我正在尝试使用BEncoding ObjC类来解码一个.torrent文件。
NSData *rawdata = [NSData dataWithContentsOfFile:@"/path/to/the.torrent"];
NSData *torrent = [BEncoding objectFromEncodedData:rawdata];

当我使用 NSLog 打印 torrent 时,我会得到以下输出:

{
    announce = <68747470 3a2f2f74 6f727265 6e742e75 62756e74 752e636f 6d3a3639 36392f61 6e6e6f75 6e6365>;
    comment = <5562756e 74752043 44207265 6c656173 65732e75 62756e74 752e636f 6d>;
    "creation date" = 1225365524;
    info =     {
        length = 732766208;
        name = <7562756e 74752d38 2e31302d 6465736b 746f702d 69333836 2e69736f>;
        "piece length" = 524288;
....

如何将名称转换为NSString?我尝试了...
NSData *info = [torrent valueForKey:@"info"];
NSData *name = [info valueForKey:@"name"];
unsigned char aBuffer[[name length]];
[name getBytes:aBuffer length:[name length]];
NSLog(@"File name: %s", aBuffer);

...它检索数据,但似乎后面还有额外的Unicode垃圾:

File name: ubuntu-8.10-desktop-i386.iso)

我也尝试过 (从这里)..

NSString *secondtry = [NSString stringWithCharacters:[name bytes] length:[name length] / sizeof(unichar)];

...但是这似乎返回了一堆随机字符:

扵湵畴㠭ㄮⴰ敤歳潴⵰㍩㘸椮潳

根据苹果文档提供的第一种方式,大部分数据都得到了正确返回,但同时也包含了一些额外的字节,这让我认为这可能是BEncoding库存在错误。不过更有可能的是由于我的ObjC知识不足而导致的问题。


“扵湵畴㠭ㄮⴰ敤歳潴⵰㍩㘸椮潳”不是中文。至少对于一个以中文为母语的人来说,这不是可以被认出的中文。 - Tyler Liu
@TylerLong 说得好,已经编辑过了! - dbr
10个回答

100

这是一个非常重要的点,我认为应该再次强调。事实证明,

NSString *content = [NSString stringWithUTF8String:[responseData bytes]];

不是同一个东西,

NSString *content = [[NSString alloc]  initWithBytes:[responseData bytes]
              length:[responseData length] encoding: NSUTF8StringEncoding];

第一个期望一个以NULL结尾的字节字符串,而第二个则不需要。在上面两种情况下,如果字节字符串没有正确终止,content 在第一个例子中将为NULL。


2
+1 我一直被一个我无法解决的错误困扰着。感谢你的有见地的评论,你让我免受了数小时的挫败。 - hyuan
非常好的观点。我已经编辑了自己的答案来涵盖它;这是一个关键细节,所以很抱歉我之前漏掉了它。 - Peter Hosey
1
您的回答非常直接并且有效。非常感谢! - amateur barista
到目前为止,找到的最佳答案是进行此转换的。干得好! - sdjuan

20
怎么样?
NSString *content = [[[NSString alloc] initWithData:myData
                                           encoding:NSUTF8StringEncoding] autorelease];

19
NSData *torrent = [BEncoding objectFromEncodedData:rawdata];

当我使用NSLog打印torrent时,我得到以下输出:

{
    ⋮
}
那应该是NSDictionary,而不是NSData。
unsigned char aBuffer[[name length]];
[name getBytes:aBuffer length:[name length]];
NSLog(@"File name: %s", aBuffer);

...这段代码能够获取数据,但似乎在获取后还有额外的Unicode垃圾字符:

File name: ubuntu-8.10-desktop-i386.iso)
不,它成功地检索了文件名;您只是打印不正确。 %s 接受 C 字符串,该字符串以空字符为结尾;数据对象的字节没有以空字符结尾(它们只是字节,不一定是任何编码中的字符,并且0——作为字符为 null——是一个完全有效的字节)。您需要分配一个额外的字符,并将数组中的最后一个字符设置为 0:
size_t length = [name length] + 1;
unsigned char aBuffer[length];
[name getBytes:aBuffer length:length];
aBuffer[length - 1] = 0;
NSLog(@"File name: %s", aBuffer);

但是在NSData对象中使用空字符终止数据是错误的(除非你确实需要一个C字符串)。我很快就会讲到正确的方法。

我也尝试过[...]。

NSString *secondtry = [NSString stringWithCharacters:[name bytes] length:[name length] / sizeof(unichar)];

...但是这似乎返回随机的中文字符:

扵湵畴㠭ㄮⴰ敤歳潴⵰㍩㘸椮潳
这是因为你的字节是UTF-8编码,通常一个字符在一个字节中被编码。
unichar是UTF-16编码的, stringWithCharacters:length:接受UTF-16编码字符串。在UTF-16编码中,一个字符通常占两个字节。(这就是为什么要除以sizeof(unichar):它将字节数除以2来获取字符数。)
所以你说“这里有一些UTF-16数据”,然后它会从每两个字节生成字符;每对字节应该是两个字符,而不是一个,所以你得到垃圾数据(大部分是CJK汉字)。
你回答了自己的问题得很好,但是对于UTF-8编码的字符串,stringWithUTF8String:stringWithCString:encoding:更简单。
然而,当你有长度时(就像你有NSData时),使用initWithBytes:length:encoding:甚至更简单、更合适。这更容易,因为它不需要以null结尾的数据;它只是使用你已经有的长度。(别忘了释放或自动释放它。)

stringWithBytes:length:encoding: 应该改为 initWithBytes:length:encoding:,对吗? - Brad Cupit
@Brad Cupit:是的。对此我感到抱歉;我总是忘记没有 stringWith… 版本(因为为什么没有呢?!)。 - Peter Hosey

7

一种简单而快速的方法是使用NSStringstringWithFormat初始化程序来帮助您。 字符串格式化的较少使用功能之一是在输出字符串时指定最大字符串长度的能力。 使用这个方便的功能可以轻松地将NSData转换为字符串:

NSData *myData = [self getDataFromSomewhere];
NSString *string = [NSString stringWithFormat:@"%.*s", [myData length], [myData bytes]];

如果您想将其输出到日志中,这甚至可以更加容易:
NSLog(@"my Data: %.*s", [myData length], [myData bytes]);

你确定 NSLog 的例子没问题吗?我在使用长度参数时并没有太好的运气,但是 NSLog(@"my Data: %*s", [myData bytes]); 却非常有效。谢谢! - Elise van Looij
非常酷。你能解释一下日志语句发生了什么吗? - Max MacLeod
星号允许您在字符串格式中插入一个参数,而您通常会在其中有一个常量。该参数出现在普通参数之前。在这种情况下,星号被替换为数据长度,而"s"接收字节数组。如果数组长度为123个字节,则等效的字符串将是NSLog(@“my data:%.123s”,[myData bytes]);这将输出mydata的前123个字节作为字符串。 - Ethan

6

啊哈,NSString 方法 stringWithCString 工作正常:

在您的项目中添加bencoding.h/.m文件后,完整的.m文件如下:

#import <Foundation/Foundation.h>
#import "BEncoding.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // Read raw file, and de-bencode
    NSData *rawdata = [NSData dataWithContentsOfFile:@"/path/to/a.torrent"];
    NSData *torrent = [BEncoding objectFromEncodedData:rawdata];

    // Get the file name
    NSData *infoData = [torrent valueForKey:@"info"];
    NSData *nameData = [infoData valueForKey:@"name"];
    NSString *filename = [NSString stringWithCString:[nameData bytes] encoding:NSUTF8StringEncoding];
    NSLog(@"%@", filename);

    [pool drain];
    return 0;
}

... 和输出:

ubuntu-8.10-desktop-i386.iso

2

使用NSData类别:

NSData+NSString.h

@interface NSData (NSString)

- (NSString *)toString;

@end

NSData+NSString.m

#import "NSData+NSString.h"

@implementation NSData (NSString)

- (NSString *)toString
{
    Byte *dataPointer = (Byte *)[self bytes];
    NSMutableString *result = [NSMutableString stringWithCapacity:0];
    NSUInteger index;
    for (index = 0; index < [self length]; index++)
    {
        [result appendFormat:@"0x%02x,", dataPointer[index]];
    }
    return result;
}

@end

那么只需这样写:NSLog(@"数据是 %@", [nsData toString])

嘿..如何从您的类别方法中获取NSData? - Saru
1
@CoderSaru 使用NSScanner的scanCharactersFromSet:intoString:方法,使用NSCharacterSet为@"0123456789abcdefABCDEF",然后参考https://dev59.com/EXA75IYBdhLWcg3wkaCA进行操作。 - Sparky

2

在我无法控制将数据转换为字符串的情况下,例如从网络读取时,我更喜欢使用 NSString -initWithBytes:length:encoding:,这样我就不依赖于需要一个空终止字符串才能获得定义的结果。请注意,苹果的文档说,如果cString不是一个空终止字符串,则结果是未定义的。


2
你可以尝试这个。对我来说没问题。
DLog(@"responeData: %@", [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSASCIIStringEncoding] autorelease]);

0

这将会起作用。

NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

0
有时候你需要从NSData创建Base64编码的字符串。例如,当你创建一个电子邮件MIME时。在这种情况下,请使用以下方法:

#import "NSData+Base64.h"
NSString *string = [data base64EncodedString];

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