天天看點

大小端位元組序與序列化

大端位元組序

這是一種更适合人類讀取資料的方式

舉個例子,有如下資料:

0x12345678

高—————>低 位

按8位為一個位元組邏輯(byte進行網絡傳輸時不需要進行網絡位元組序轉換)把上面資料拆分成4部分:

0x12   0x34   0x56   0x78

位址: 0x100 0x101 0x102 0x103

|———|—12—|—34—|—56—|—78—|——|

從左到右:低位址到高位址

從左到右:高位到低位

記憶方法:低高高低,這是一種便于人類讀取的方式。像120,百位十位個位,也是從高位到低位進行讀的:一百二十。

小端位元組序

這是一種更适合機器讀取的方式,反人類的方式。

還是如上的資料再這邊的位址資料顯示是這樣的:

位址: 0x100 0x101 0x102 0x103

|———|—78—|—56—|—34—|—12—|——|

從左到右:低位址到高位址

從左到右:低位到高位

記憶方法:低低高高(對于大小端的記憶其實了解最為重要,了解後就可以很深刻的記住了)

計算機都是從低位開始計算的,是以計算機電路是先處理低位位元組的,效率也會高些。

計算機位元組序,通常都會使用小端位元組序,當然有些系統例外。

網絡位元組序的轉換

通常網絡傳輸慣于使用大端位元組序,也就是人類習慣的一種位元組序。當然進行網絡位元組序的轉換還有個目的是為了保證傳輸格式的統一。

因為雖然主機位元組序大多數使用小端位元組序,但是還是有一小部分的使用大端位元組序的,為了保證不同位元組序的兩台機子能夠進行通信,則可以都先轉換為統一的網絡位元組序進行資料的接收和傳輸。

如C++中的: htonl、ntohl等,都是進行位元組序的轉換。

前者主機位元組序到網絡位元組序,後者網絡位元組序到主機位元組序

如何用代碼判斷大小端

#include <iostream>

// 判斷大端位元組序 方法一
bool IsBigEndian()
{
    int data = 0x12345678;
    char *tar = (char *)&data;
    return (tar[0] == 0x12);
}

// 判斷小端位元組序 方法二:利用聯合體
union UEndian
{
    char a[4];
    int val;
};

bool IsLittleEndianByUnion()
{
    UEndian data;
    data.val = 1;
    return (data.a[0] == data.val);
}

int main(void)
{

    std::cout << "fun1 is big endian =======" << (int)IsBigEndian() << std::endl;
    std::cout << "fun2 is little endian =======" << (int)IsLittleEndianByUnion() << std::endl;

    return 0;
}


           

輸出結果如下:

fun1 is big endian =======0
fun2 is little endian =======1
           

從如上的輸出可以看出判斷大端位元組序傳回的是false,而判斷小端位元組序傳回的是true,由此可以證明我本地(macOS)的位元組序為小端位元組序。

那麼位元組序轉換跟序列化有什麼關系呢

對于這兩者的概念,其實他們倆沒有直接的關系,挺多人可能會搞混,先搞清楚什麼是序列化?

序列化 (Serialization)是将對象的狀态資訊轉換為可以存儲或傳輸的形式的過程

從我個人了解來看

如果學過計算機的童鞋都知道,其實計算機隻能識别0跟1(一個位元組就是八個0或1,對于C而言一個位元組=一個字元,多個字元就形成了字元串;相比起數值啥的,他們隻是多了個規定多少個位元組為一個數值(語言層面上的東西,對于計算機來說它是不懂的),但他們本質是一樣的,隻需要把數值轉換成多個位元組那麼計算機就可以識别了。)

目前的序列化協定其實挺多的,二進制流、protobuf、xml、json等等,這些傳輸方式存在是為了讓變量啥的能夠通過某種邏輯轉換能傳輸到其他終端,再通過相反的邏輯進行解析,并在其他端可以使用。

總結:我們看到的形形色色的變量呀對象啥的,其實計算機是不認識的,是以這中間需要翻譯,那麼翻譯其實就是序列化/反序列化。

還不明白也不要緊,以下是使用二進制的形式進行序列化,先來看看下面一段代碼就明白了。

void Push(uint32 val)
{
    append<uint32>(htonl(val));
}

// 這裡省略了一些調用
void append(const uint8 *src, size_t cnt)
{
	if (!cnt) return;
	
	if(_storage.size() < _wpos + cnt)
		_storage.resize(_wpos + cnt);
	if (_storage.size() > _wpos)
	{
		memcpy(&_storage[_wpos], src, cnt);
		_wpos += cnt;
	}
}

           

從上面的代碼中我們可以看出,對于Push方法,我們在插入 val 的時候,其實是先将 val 通過htonl進行了本地位元組序到網絡位元組序的轉換(這個過程主要是防止不同端因為本地位元組序的不同導緻接收後資料錯誤),後通過append方法,将位元組序轉換後的值插入到storage這個數組裡面(當然我這裡用的std::vector),其實這個插入過程就是一個序列化的過程。

如有說的不對的地方,歡迎批評指錯~

繼續閱讀