📝【基础】HexString是什么?
HexString是什么?
HexString到底是什么?首先看一下这个词的前半部分:hex,hex的意思是十六进制,那这个词的意思就很容易明白了,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倍,所以综上可以看出十六进制在这个地方是十分友好的。
为什么总是能看到十六进制
首先已知的是计算机中的数据全都是以0和1传递
我们看到的任何“数字”,实际上对于计算机来说,其意义都只是变为二进制后的0和1。也就是说,我们所以为的“数字”,其实无意义
所有的信息以一个字节为最小传输单位。一个字节包含八位来存储。
在计算机底层,当然数据都是01,而开发者在上层传输数据的时候,是需要一个载体的,常用的就是一个字符串或者字符数组 。那么问题就出现了,使用字符数组这种东西来存储01显然效率是非常低的,一个字节就需要使用’10101010’这么长来表示。
于是就可以使用其他进制的数字来传输,反正不同进制下我们看到的数字是无意义的,我们实际要传输的是01。
显然要用更高进制来表示,这时候十六进制的优势就很明显。十六进制的一位,恰好对应四位二进制。所以两位十六进制就可以完美表示一个字节。
试想用八进制,一位八进制表示三位二进制,甚至都无法顺畅地表示一个字节(八位)。而三十二进制是同样的情况,一位三十二进制表示五位二进制,依然很别扭。
于是十六进制就顺理成章的出现在很多地方使用。且常出现诸如0x68这样的两位十六进制,它恰好表示一个字节,二进制是0110 1000,在ASCII码里,是h
大端系统&小端系统
大小端是针对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]];
这句话中比较重要的是两个点:
这是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;
}