一、數(shù)據(jù)大小端的介紹
網(wǎng)上關(guān)于數(shù)據(jù)大小端的介紹一大堆捂敌,為了讓文章全面點(diǎn),本文也就這方面簡(jiǎn)單說明一下既琴。
a. 大小端表示數(shù)據(jù)在計(jì)算機(jī)中的存放順序占婉。
b. 大端模式符合人類的正常思維,高字節(jié)保存在內(nèi)存的低地址甫恩。
c. 小端模式方便計(jì)算機(jī)處理逆济,高字節(jié)保存在內(nèi)存的高地址。
d. iOS中默認(rèn)的是小端存儲(chǔ)磺箕。
你可以在Xcode中運(yùn)行下面這兩行代碼奖慌,就會(huì)打印出大小端模式。
short int number = 0x8866;
NSLog(@"%@",[NSString stringWithFormat:@"%x",((char *)&number)[0]].intValue == 66 ? @"小端模式" : @"大端模式");
二松靡、大小端數(shù)據(jù)模式的轉(zhuǎn)換
藍(lán)牙通信的時(shí)候简僧,從硬件接收到的數(shù)據(jù)是NSData類型,我們需要對(duì)數(shù)據(jù)進(jìn)行解析才能拿到真正方便使用的數(shù)據(jù)雕欺。
但是接收到的數(shù)據(jù)在內(nèi)存中的保存順序可能與我們希望的相反岛马,所以在解析的過程中就涉及到了大小端的轉(zhuǎn)換問題。
其實(shí)iOS的大小端轉(zhuǎn)換非常方便阅茶,在蘋果的Core Fundation中就提供了進(jìn)行這些數(shù)據(jù)處理的方法蛛枚。Apple官方文檔
下面我就舉幾個(gè)例子,一起來看一下Fundation中與大小端有關(guān)方法的基本使用脸哀。
1蹦浦、CFByteOrderGetCurrent()
返回當(dāng)前電腦的大小端模式
CFByteOrderGetCurrent()
返回的值是一個(gè)如下的枚舉
enum __CFByteOrder {
CFByteOrderUnknown, // 未知的
CFByteOrderLittleEndian, // 小端模式
CFByteOrderBigEndian // 大端模式
};
2、CFSwapInt16()
轉(zhuǎn)換一個(gè)16位的整型數(shù)字
// 把數(shù)字15轉(zhuǎn)換模式
CFSwapInt16(15)
// 上面運(yùn)算得到的結(jié)果十進(jìn)制為3840撞蜂,十六進(jìn)制為0xF00盲镶。
// 而0xF00反轉(zhuǎn)過來就是0xF = 15,所以證明這個(gè)方法確實(shí)對(duì)15進(jìn)行了反轉(zhuǎn)蝌诡。
3溉贿、CFSwapInt16BigToHost()
把一個(gè)16位的整型數(shù)字從大端模式轉(zhuǎn)為本機(jī)數(shù)據(jù)存放模式。如果本機(jī)為大端模式浦旱,則原值不變宇色。
// 把大端模式的數(shù)字Number轉(zhuǎn)為本機(jī)數(shù)據(jù)存放模式
CFSwapInt16BigToHost(Number)
4、CFSwapInt32HostToBig()
把一個(gè)32位本機(jī)模式數(shù)據(jù)轉(zhuǎn)換為大端模式。如果本機(jī)為大端模式宣蠕,則原值不變例隆。
// 把本地存儲(chǔ)模式的數(shù)字Number轉(zhuǎn)為大端模式
CFSwapInt32HostToBig(Number)
還有好多方法(詳見官方文檔),基本都是大同小異抢蚀,從字面就可以理解它的用法镀层。
通常能用到的也就那么兩三個(gè)。
一般需求是把大端轉(zhuǎn)成本地模式皿曲,也就是小端模式唱逢。
CFSwapInt16BigToHost
CFSwapInt32BigToHost
下面是封裝好了的兩個(gè)方法,在開發(fā)中可以直接用來解析數(shù)據(jù)屋休。
兩個(gè)方法分別返回Signed和Unsigned類型的數(shù)據(jù)坞古。
代碼中的location代表準(zhǔn)備解析的數(shù)據(jù)的位置,offset代表需要解析幾位博投。
- 需要注意的是绸贡,當(dāng)僅僅是解析1位數(shù)據(jù)的時(shí)候盯蝴,就不需要使用像CFSwapInt16BigToHost這樣的方法了毅哗,具體可以查閱代碼。
// 轉(zhuǎn)為本地大小端模式 返回Signed類型的數(shù)據(jù)
+(signed int)signedDataTointWithData:(NSData *)data Location:(NSInteger)location Offset:(NSInteger)offset {
signed int value=0;
NSData *intdata= [data subdataWithRange:NSMakeRange(location, offset)];
if (offset==2) {
value=CFSwapInt16BigToHost(*(int*)([intdata bytes]));
}
else if (offset==4) {
value = CFSwapInt32BigToHost(*(int*)([intdata bytes]));
}
else if (offset==1) {
signed char *bs = (signed char *)[[data subdataWithRange:NSMakeRange(location, 1) ] bytes];
value = *bs;
}
return value;
}
// 轉(zhuǎn)為本地大小端模式 返回Unsigned類型的數(shù)據(jù)
+(unsigned int)unsignedDataTointWithData:(NSData *)data Location:(NSInteger)location Offset:(NSInteger)offset {
unsigned int value=0;
NSData *intdata= [data subdataWithRange:NSMakeRange(location, offset)];
if (offset==2) {
value=CFSwapInt16BigToHost(*(int*)([intdata bytes]));
}
else if (offset==4) {
value = CFSwapInt32BigToHost(*(int*)([intdata bytes]));
}
else if (offset==1) {
unsigned char *bs = (unsigned char *)[[data subdataWithRange:NSMakeRange(location, 1) ] bytes];
value = *bs;
}
return value;
}
三捧挺、按位運(yùn)算虑绵,左移、右移運(yùn)算
在講解位運(yùn)算和左右移之前闽烙,先來回憶回憶基本的數(shù)據(jù)計(jì)量單位翅睛。
1字節(jié)是一個(gè)8位的數(shù)據(jù),可以代表從0-255共256個(gè)數(shù)字黑竞。
1B(byte捕发,字節(jié))= 8 bit(位)。
模擬一次解析數(shù)據(jù)的過程:
- 假如藍(lán)牙每次發(fā)過來的數(shù)據(jù)大小為32個(gè)字節(jié)很魂,這個(gè)數(shù)據(jù)在NSData類型下Log出來是這個(gè)樣子:<0aa60000 00000000 00000000 00000000 00000000 00000059 9db56800 00260b01>
- 每?jī)蓚€(gè)數(shù)字表示一個(gè)十六進(jìn)制的數(shù)據(jù)扎酷,例如最左邊的0a代表了一個(gè)字節(jié),也就是0x0A = 10遏匆。
- 現(xiàn)在我們要截取最左邊的0aa6這兩個(gè)字節(jié)(16位)法挨,這個(gè)數(shù)據(jù)是UInt16類型,那么首先要做的就是運(yùn)用上面封裝好了的大小端轉(zhuǎn)換方法來截取這兩個(gè)字節(jié)幅聘,下面代碼中的result就是所需要的數(shù)據(jù)凡纳。
// 從第0位開始,截取2個(gè)字節(jié)帝蒿,所以location是0荐糜,offset是2
UInt16 result = [self unsignedDataTointWithData:data Location:0 Offset:2];
可是拿到result之后工作還沒有結(jié)束。
- 需求:result的二進(jìn)制是0000 1010 1010 0110,一個(gè)16位的數(shù)字暴氏,假如與硬件工程師提前說好了丛版,低4位(0110)代表組數(shù),5-8位(1010)代表每組的人數(shù)偏序。
如何分別拿出所需的數(shù)據(jù)呢页畦?
這時(shí)候,位運(yùn)算就派上用場(chǎng)了研儒。
一起來看看位運(yùn)算和左右移的基本使用方法和情景豫缨,需求的答案也在其中。
1端朵、按位與 &
同為1為1好芭,否則為0
例如:3 & 5
0000 0011
0000 0101
0000 0001 = 1
所以 3 & 5=1
特點(diǎn):
(1)清零:任何數(shù)和0相與,結(jié)果為0.
(2)取出指定位的值冲呢。取哪一位舍败,就把對(duì)應(yīng)的位定為1。例如:
拿到了一個(gè)16位的數(shù)據(jù)result = 0000 1010 1010 0110敬拓,如何拿到這個(gè)數(shù)據(jù)的低4位呢邻薯?
就可以使用按位與,代碼如下
// 0x000f == 0000 0000 0000 1111
// 按位與上result之后乘凸,得到的number == 0000 0000 0000 0110 就是低4位的數(shù)據(jù)0110
int number = result & 0x000f;
2厕诡、按位或 |
只要有一個(gè)為1就為1
負(fù)數(shù)按補(bǔ)碼的形式參加按位或運(yùn)算
例如:3 | 5
0000 0011
0000 0101
0000 0111 = 7
所以 3 | 5=7
特點(diǎn):
(1)對(duì)數(shù)據(jù)的某些位置1。例如:
將X=1010 0000的后四位置1
1010 0000
0000 1111
1010 1111
這樣后4位就全為1了
3营勤、異或運(yùn)算 ^
如果對(duì)應(yīng)的位不同則為1灵嫌,相同為0
例如 3 ^ 5
0000 0011
0000 0101
0000 0110
所以 3 ^ 5= 6
特點(diǎn):
(1)特定位翻轉(zhuǎn),哪一位需要翻轉(zhuǎn)就把對(duì)應(yīng)的位設(shè)置為1
(2)任何數(shù)和0異或葛作,原值不變寿羞。
(3)異或運(yùn)算可以交換位置:3 ^ 5 ^ 6 == 3 ^ 6 ^ 5
(4)相同的數(shù)異或等于0:9 ^ 9 == 0
(5)a ^ b ^ a == b
4、取反 ~
0變1赂蠢,1變0
例如 ~3
0000 0011
1111 1100
特點(diǎn):
(1)配合按位與把一個(gè)數(shù)的最低位設(shè)置為0
例如:
把X=1011 0111按位與(~1)
X & (~1) = 1011 0110
這樣最后一位就為0了
5绪穆、左移運(yùn)算 <<
二進(jìn)制位全部左移若干位,左邊的丟棄客年,右邊補(bǔ)0
例如 3<<2
0000 0011 = 3
0000 1100 = 12 (左移后)
左移3<<2 == 12
特點(diǎn):
若左移時(shí)舍棄的最高位不包含1霞幅,則每左移一位,就乘以一次2.
所以a<
6量瓜、右移運(yùn)算 >>
二進(jìn)制右移若干位司恳,正數(shù)左邊補(bǔ)0,負(fù)數(shù)左邊補(bǔ)1绍傲,右邊丟棄扔傅。
例如 12>>2
0000 1100 = 12
0000 0011 = 2 (右移后)
右移12>>2 == 3
特點(diǎn):
每右移一位耍共,就除以一次2.
a>>n 就是 a除以2的n次方例如:
繼續(xù)用上面按位與的例子,
拿到了一個(gè)16位的數(shù)據(jù)result = 0000 1010 1010 0110猎塞,如何拿到這個(gè)數(shù)據(jù)的5-8位呢试读?
首先運(yùn)用按位與把5-8位之外的數(shù)據(jù)全部置0,然后用右移來拿到具體數(shù)值荠耽。
代碼如下
// 0x00f0 == 0000 0000 1111 0000钩骇,result按位與0xf0之后,結(jié)果為0000 0000 1010 0000
// 然后右移4位铝量,得到最終所需要的數(shù)據(jù)number == 0000 0000 0000 1010
int number = (result & 0x00f0) >> 4;