将包含UTF-8和空字节的NSData转换为字符串

4

我有一个__NSCFData对象。我知道它的内部内容。

61 70 70 6c 65 2c 74 79 70 68 6f 6f 6e 00 41 52 4d 2c 76 38 00

我尝试使用initWithData和stringWithUTF8String将其转换为字符串,但它给了我"apple,typhoon"。转换在00处终止。

实际数据是:

61 a
70 p
70 p
6c l
65 e
2c ,
74 t
79 y
70 p
68 h
6f o
6f o
6e n
00 (null)
41 A
52 R
4d M
2c ,
76 v
38 8
00 (null)

如何在不丢失信息的情况下正确转换这个?

这里有一个潜在的解决方案:https://pastebin.com/B8k1ywWZ。我使用了 rangeOfData:options:range:,它与 NSString 版本的方法类似 (rangeOfString:options:range:),并采用了一种类似于寻找迭代而不仅仅停留在第一个出现的逻辑。这是一个“高”级别的方法,不会太多地涉及字节。 - Larme
3个回答

4
stringWithUTF8String 的文档将其第一个参数描述为:

以 UTF8 编码的以 NULL 结尾的 C 字节数组。

这就是为什么您的转换会在第一个空字节处停止。

您似乎有一个打包成单个 NSData 的 C 字符串集合。您可以逐个转换每个字符串。使用 NSData 方法 byteslength 获取指向字节/第一个 C 字符串和总字节数的指针。标准的 C 函数 strlen() 将给出单个字符串的字节长度。结合这些和一些简单的指针算术,您可以编写一个循环,将每个字符串转换并存储到数组中或连接它们。

如果您在实现解决方案时遇到问题,请提出新问题,展示您的代码并解释问题。某人肯定会帮助您进行下一步操作。

希望对您有所帮助。


2
实际上,这些数据是两个字符串。我认为你的解决方案更加“合适”,因为你考虑到了数据可能是两个字符串的情况。这两个字符串都以空字符结尾。我编写了一个程序,结合了你的解决方案和greymouser的解决方案。 https://ideone.com/CxzBlv 请检查一下,并让我知道您是否有任何建议。 - Lord Stiltskin
在您的解决方案中,您使用了getBytes而不是bytes,这会使数据产生多余的副本。您可以直接使用bytes返回的指针和一些指针算术来将其推进到下一个字符串。此外,与其坚持使用stringWithUTF8String进行整个字符串转换,您选择了逐个字符转换和appendFormat:,这增加了更多的低效性 - 最好坚持原始的整个字符串方法。 - CRD
好多了。快速扫描表明,您处理空字符串的逻辑不太正确,您会丢失以下字符串,并且如果最后一个(或唯一的)字符串没有以 null 结尾,则会丢失该字符串 - 但是您正确地从未读取超出数据结尾。您必须决定是否需要处理空字符串和/或缺少的 null 值,这取决于您需要什么级别的输入验证。 - CRD

0

0或null是终止字符串的哨兵值,因此如果您想自动将字节转储到字符串中,则必须以某种方式处理它。如果不这样做,字符串或尝试打印它的东西(例如)将在达到NULL时假定字符串的结尾已经到达。

只需将出现的字节替换为可打印的内容,例如空格。使用适合您的任何值即可。

示例:

// original data you have from somewhere
char something[] = "apple,typhoon\0ARM,v8\0";
NSData *data = [NSData dataWithBytes:something length:sizeof(something)];

// Find each null terminated string in the data
NSMutableArray *strings = [NSMutableArray new];
NSMutableString *temp = [NSMutableString string];
const char *bytes = [data bytes];
for (int i = 0; i < [data length]; i++) {
    unsigned char byte = (unsigned char)bytes[i];
    if (byte == 0) {
        if ([temp length] > 0) {
            [strings addObject:temp];
            temp = [NSMutableString string];
        }
    } else {
        [temp appendFormat:@"%c", byte];
    }
}

// Results
NSLog(@"strings count: %lu", [strings count]);
[strings enumerateObjectsUsingBlock:^(NSString *string, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"%ld: %@", idx, string);
}];
// strings count: 2
// 0: apple,typhoon
// 1: ARM,v8

\00(而不是指针的null)用于终止C字符串。NSString实例并非C字符串。 - Amin Negm-Awad
C字符串是以0结尾的,而不是空结尾的。实际上,这并不重要。我把它放在括号里了。然而,在我的答案中,我确实在一个NSString实例中添加了一个0。这很简单,因为即使是NSString字面量也不是以0结尾的。只需查看我的答案。NSString是基于长度的。用空格或其他东西替换0是可怕的,因为这样做会丢失信息。 - Amin Negm-Awad
@AminNegm-Awad C字符串以空字符结尾。我不知道还能做什么,只能引用K&R的C指南 - “......字符'\0',空字符,其值为零)”(第28页 - “C程序设计语言”,Kernighan和Ritche。Prentice Hall. 1988)。我明白您希望人们理解空指针和空终止是语义上不同的概念,但每个人都将C字符串称为以空字符结尾的字符数组。这就是它们的本质。 - greymouser
我根据CRD的建议写了类似的东西。你的解决方案也有效。我对这里关于C字符串的讨论还有点困惑。\0的值是0,它是空字符。当遇到空字符时,字符串终止。难道不能称之为“以空字符结尾”吗? - Lord Stiltskin
@greymouser 哇,K&R。你做了一些历史研究吗?看看C标准...然而,这不重要。重要的是,无论你如何调用,NSString的实例都不能使用0终止。这使得你的答案毫无意义。 - Amin Negm-Awad
显示剩余4条评论

0
与某些答案的意图相反,NSString实例中存储的字符串并非以0结尾。即使在将它们写出时可能会出现问题(因为底层C函数需要一个以0结尾的字符串),但实例本身可以包含\0
NSString *zeroIncluded = @"A\0B";
NSLog(@"%ld", [zeroIncluded length]);
// prints 3

要创建这样的实例,您可以使用具有byteslength参数的方法,例如-initWithBytes:length:encoding:。因此,类似以下代码应该可以工作:

NSData *data = …
[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding];

不过,正如CRD所预期的那样,您可以检查一下是否需要这样的字符串。


这很有趣,但并没有回答 OP 的问题。他想知道如何获取他的 NSData 中所有数据,即有效地是“apple,typhoon \ 0ARM,v8 \ 0”。它在“apple,typhoon”处终止,他希望获取其余部分。 - greymouser
我本可以使用这种技术,但正如CRD所建议的那样,它是将两个字符串打包成数据。因此,最好将它们作为单独的字符串提取出来。 感谢您提供的信息。我真的不知道NSStrings没有0终止符。然而,即使我在UIAlertView或UILabel中使用NSString,也没有使用完整的字符串。 - Lord Stiltskin
@greymouser 使用上述代码,他可以获得所有的数据。试一下吧。 - Amin Negm-Awad
@LordStiltskin 是的,这取决于您的问题,特别是您想用字符串做什么,是否有必要以这种方式进行操作。但是,您的问题是如何将整个文本获取为一个字符串。这就是该问题的答案。 - Amin Negm-Awad

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