iOS开发中的HexString

作者:代培
地址:http://blog.csdn.net/dp948080952/article/details/53255906
转载请注明出处

写在前面

按照正常的时间来说,今天应该在写本月的第三篇博客,但这个月各种忙,主要是几场考试加上学校公司里的一些事情,有时候本打算写一篇博客却被朋友叫去聚餐,又或是受不住一些影视剧的诱惑,到了今天总算有时间把这个月第二篇博客写出来了。
前段时间在项目中遇到了HexString,既然遇到了,那么肯定要把它搞明白,所以今天就来说说在iOS开发中的HexString。

正文

HexString是什么?

无论什么事情,首先要搞明白它是什么,那么HexString到底是什么呢?首先看一下这个词的前半部分:hex,hex的意思是十六进制,那这个词的意思就很容易明白了,HexString的意思就是十六进制的字符串。
虽然这样说看起来很简单,但似乎还是不知道HexString到底是什么东西,那我们就来举个栗子,大家肯定就懂了。
假设下面是内存中的四个字节,由左往右地址变大

00010011 10101111 10010101 11000111

那么这段内存用HexString表示就是@”13af95c7”
其实很简单,就是将内存中的每个字节转换为两个十六进制的数,0001对应1,0011对应3以此类推,所以经过这样的转换一个字节的信息实际上要用两个字节来储存。

HexString的作用

根据我上面的例子,不难看出HexString的作用就是用来表示一段比特信息,实际上就是一段0101代码,用HexString可以用来表示一个程序,一段数据,一个字符串,甚至说可以表示一切,因为在计算机中任何东西都是以二进制代码存在的。
而且在计算机的内存地址的表示中一般都会用十六进制,因为两位表示一个字节清楚明了,尤其在汇编编程中,十六进制更是随处可见,而HexString就是将十六进制的数转换为字符串,用以表更长的内存信息。

为什么在传输时要用HexString

有人会说一段内存实际上就可以看成一段字符串,直接将内存转换为字符串不就可以了嘛,这样不仅占的空间小于HexString,而且还省去了转换的操作。
曾经我也是这样想,也这样去做了,却发现这样是行不通的,因为当你将一段内存转换为字符串时,你可能会遇到下面这些问题:

  • 转换出来字符串打印出来什么都没有
  • 无法转换成字符串(字符串对象为null)
  • 转换的字符串转换回去会不一样

当然第一个问题并不妨碍我用字符串来传递数据,虽然打印出来看不见,但是转换成内存后并没有变化,而第二种情况就不行了,根本就无法转换为字符串,第三种情况就更奇怪了,内存到字符串再到内存,结果不一样了。
经过我的一些实验发现了一个规律,那就是每个字节的最高位如果是1转换成字符串就会出问题,对于不同的解码方式出现的问题不同,如果用ASCII解码,内存到字符串看不出什么问题,但是转换回去以后就会变成0,如果用utf-8解码,那样生成的字符串就是null。
所以直接用字符串来传递数据就会遇到问题,那能不能直接传递比特信息,实际上传递的数据就是比特信息(高低电平),但是对于一段数据来说很难是单独存在的,它需要一个上下文,就拿http的post请求来说,不论是使用json或是html表单,他里面的value值都是无法单独存在的,因为你无法知道这个value是代表什么,所以要将其组织成字符串,这样人才能读懂,才能对这段数据进行处理。
而HexString的字面值不是我们需要的信息,他转换成的比特信息才是我们需要的,但HexString可以和其他数据更好的拼接。
但是为什么要用十六进制而不用其他进制呢,我想十六进制的特殊之处应该在于十六进制的每一位正好对应二进制中的四位,没两个十六进制字符对应一个字节,8进制对应三位,连一个字节用8进制都不好表示,10进制跟位数直接根本不好对应,而如果直接用二进制的化,所占空间是HexString的4倍,所以综上可以看出十六进制在这个地方是十分友好的。

大端系统&小端系统

大小端是针对CPU而言的,对于大端来说,高位储存在高地址,低位储存在小地址

下面这段地址从左往右地址增大

0x100 0x101
00000010 00000001
2 1


对大端系统而言,左边是低位,右边是高位,那么结果就是0x12
对小端系统而言,左边是高位,右边是低位,那么结果就是0x21
所以实际上小端是符合人们的书写习惯的。

而有人说HexString是为了让大小端系统兼容而使用的,而我觉得并不是这样,维基百科中有这样的解释

一个多位的整数将按照其存储地址的最低或最高字节排列。如果最低有效位在最高有效位的前面,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。

这个兼容不是我们自己做的,而是大家都转换为一个标准的字节序,这样就不存在兼容问题了。
而即使转换换为HexString,实际上我们只是将一种比特信息映射成另一种比特信息,地址的单位仍然是字节,仍然还是一段字节序,这段字节序从大端到小端还是要反序储存才正确,这个反序的过程是通过网络标准转化而达到的。

如何生成HexString

又到了贴代码的时间,Talk is cheap,show you the code:

+ (NSString *)hexStringWithData:(NSData *)data {
    const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
    if (!dataBuffer) {
        return [NSString string];
    }

    NSUInteger          dataLength  = [data length];
    NSMutableString     *hexString  = [NSMutableString stringWithCapacity:(dataLength * 2)];

    for (int i = 0; i < dataLength; ++i) {
        [hexString appendFormat:@"%02x", (unsigned char)dataBuffer[i]];
    }
    return [NSString stringWithString:hexString];
}

这是data转换为HexString的方法,至于如何将指针转换为NSData,自行百度,这里的核心代码只有一句[hexString appendFormat:@"%02x", (unsigned char)dataBuffer[i]];
这句话中比较重要的是两个点:

  • %02x,%x是按十六进制输出,%02x是将一个字节转换为两个十六进制输出
  • 强制类型转换(unsigned char),如果不进行这个转换你最后的HexString里可能就会多出6个f,就像这样:ffffff,这里牵涉到符号的问题而且有趣的是转换成unsigned int都不行,只有char可以
+ (NSData *)dataWithHexString:(NSString *)hexString {
    const char *chars = [hexString UTF8String];
    int i = 0;
    NSUInteger len = hexString.length;

    NSMutableData *data = [NSMutableData dataWithCapacity:len / 2];
    char byteChars[3] = {'\0','\0','\0'};
    unsigned long wholeByte;

    while (i < len) {
        byteChars[0] = chars[i++];
        byteChars[1] = chars[i++];
        wholeByte = strtoul(byteChars, NULL, 16);
        [data appendBytes:&wholeByte length:1];
    }

    return data;
}

这是StackOverflow上的代码,据说效率很高,不过确实,他每个循环能够完成一个字节的转换,然后用了C语言的strtoul函数,直接将字符串转换为无符号长整型。

总结

HexString就是将二进制换算为十六进制再转换为字符串表示,在计算机内存中这段数据的表示已经和转换前不同了,虽然有转换的开销,但是却很有必要,因为这样会更通用。
这些都是自己的一些理解,如果有错误的地方欢迎大家指正。

你可能感兴趣的:(ios开发)