教你用golang判斷大小端字節序

前言

哈嘍,你們好,我是 asong 。今天想與你們聊一聊計算機硬件中的兩種儲存數據的方式:大端字節序(big endian)、小端字節序(little endian)。老實說,我第一次知道這個概念仍是在學習單片機的時候,不過當時學完就忘了,真正長記性是在面試的時候,面試官問我:你能用 C語言寫段代碼判斷機器的字節序嗎?你必定好奇爲何要用 C語言寫,傻瓜,這是我大學的時候面試嵌入式崗位呀。扯遠啦,其實當時的我是懵逼的,早就忘了什麼大端、小端了,因此遺憾的錯過嵌入式行業,進入了互聯網行業(手動狗頭)。

本文的全部代碼已經上傳githubhttps://github.com/asong2020/...;歡迎stargit

因爲微信公衆號改版,文章推送會亂序,爲了第一時間收到asong的消息,請讀者朋友動動小手,在公衆號主頁右上角設置裏加個星標。感謝你們~。github

爲何有大小端之分

我一直都不理解,爲何要有大小端區分,尤爲是小端,老是會忘記,由於他不符合人類的思惟習慣,但存在即爲合理,存在就有他存在的價值。這裏有一個比較合理的解釋:計算機中電路優先處理低位字節,效率比較高,由於計算機都是從低位開始的,因此計算機內部處理都是小端字節序。可是咱們日常讀寫數值的方法,習慣用大端字節序,因此除了計算機的內部,其餘場景大都是大端字節序,好比:網絡傳輸和文件儲存時都是用的大端字節序。面試

因此大小端問題極可能與硬件或者軟件的創造者們有關,實際在計算機工業應用上,不一樣的操做系統和不一樣的芯片類型都有所不一樣。不一樣的系統設計不一樣,因此咱們也不必深究爲何要有這個區分,只須要知道他們的原理就行了。算法

什麼是大端、小端

大端模式:高位字節排放在內存的低地址端,低位字節排放在內存的高地址端;編程

小端模式:低位字節排放在內存的低地址端,高位字節排放在內存的高地址端;設計模式

這麼說也有點模糊,仍是配個圖來看更清晰:緩存

咱們來看一看數值0x1A2B3C4D在大端與小端的表現形式,這裏咱們假設地址是從0x4000開始:微信

上圖所示:大端和小端的字節序最小單位是1字節(8bit),大端字節序就和咱們平時的寫法順序同樣,從低地址到高地址寫入0x1A2B3C4D,而小端字節序就是咱們平時的寫法反過來,由於字節序最小單位爲1字節,因此從低地址到高地址寫入0x4D3C2B1A網絡

由於大端、小端很容易混記,因此分享一個我本身記憶的小技巧:架構

大端:高低高低,也就是高位字節排放在內存低地址端,高地址端存在低位字節;

小端:高高低低;也就是高位字節排放在內存的高地址端,低位字節排放在內存的低地址端;

如何使用Go區分大小端

計算機處理字節序的時候,不知道什麼是高位字節,什麼是低位字節。它只知道按順序讀取字節,先讀取第一個字節,再讀取第二個字節,因此說我就能夠根據這個特性來讀判斷大小端。

在使用Go語言實現以前,仍是想再用C語言實現一遍,由於這是我一輩子的痛,畢竟在面試的時候沒寫出來。

能夠利用C語言中union各字段共享內存的特性,union型數據所佔的空間等於其最大的成員所佔的空間,對 union 型的成員的存取都是相對於該聯合體基地址的偏移量爲 0 處開始,也就是聯合體的訪問不論對哪一個變量的存取都是從 union 的首地址位置開始聯合是一個在同一個存儲空間裏存儲不一樣類型數據的數據類型。這些存儲區的地址都是同樣的,聯合裏不一樣存儲區的內存是重疊的,修改了任何一個其餘的會受影響。因此咱們可寫出代碼以下:

#include "stdio.h"


// big_endian: 1 
// little_endian: 2
int IsLittleEndian() {
    union {
        short value;
        char array[2];
    } u;
    u.value = 0x0102;
    if (u.array[0] == 1 && u.array[1] == 2){
        return 1;
    }else if (u.array[0] == 2 && u.array[1] == 1){
        return 2;
    }
    return -1;
}

int main() {
    
    int res;
    res = IsLittleEndian();
    printf("result is %d\n",res);
    if (res == 1) {
        printf("it is big endian");
    }
    if (res == 2){
        printf("it is little endian");
    }
    return 0;
}

// 運行結果(不一樣系統運行結果會有不一樣)
result is 2
it is little endian%

如今咱們來思考一下,怎麼用Go語言驗證大小端,Go中是沒有union這個關鍵字,那就要另闢蹊徑,換一個方法來實現啦,咱們能夠經過將int32類型(4字節)強制轉換成byte類型(單字節),判斷起始存儲位置內容來實現,由於Go不支持強制類型轉換,咱們能夠藉助unsafe包達到咱們的要求,寫出代碼以下:

package main

import (
    "fmt"
    "unsafe"
)

func IsLittleEndian()  bool{
    var value int32 = 1 // 佔4byte 轉換成16進制 0x00 00 00 01 
  // 大端(16進制):00 00 00 01
  // 小端(16進制):01 00 00 00
    pointer := unsafe.Pointer(&value)
    pb := (*byte)(pointer)
    if *pb != 1{
        return false
    }
    return true
}

func main()  {
    fmt.Println(IsLittleEndian())
}
// 運行結果:ture

大小端字節序轉化

這裏你們可能會有疑惑,爲何要有大小端轉化,這是由於在涉及到網絡傳輸、文件存儲時,由於不一樣系統的大小端字節序不一樣,這是就須要大小端轉化,才能保證讀取到的數據是正確的。我在大學時作armdsp通訊的時候,就遇到個大小端轉換的問題,由於arm是小端,dsp是大端,因此在不瞭解這個知識點的時候,通訊的數據就是亂的,致使我調試了很久。

大小端的轉換其實還算比較簡單,經過位操做就能夠實現,這裏咱們用uint32類型做爲例子:

func SwapEndianUin32(val uint32)  uint32{
    return (val & 0xff000000) >> 24 | (val & 0x00ff0000) >> 8 |
        (val & 0x0000ff00) << 8 | (val & 0x000000ff) <<24
}

是的,你沒看錯,就是這麼簡單,這裏也很簡單,就不細講了。

其實go官方庫encoding/binary中已經提供了大小端使用的庫,咱們要想進行大小端轉換,徹底可使用官方庫,不必本身造輪子。咱們看一下這個庫怎麼使用:

// use encoding/binary
// bigEndian littleEndian
func BigEndianAndLittleEndianByLibrary()  {
    var value uint32 = 10
    by := make([]byte,4)
    binary.BigEndian.PutUint32(by,value)
    fmt.Println("轉換成大端後 ",by)
    fmt.Println("使用大端字節序輸出結果:",binary.BigEndian.Uint32(by))
    little := binary.LittleEndian.Uint32(by)
    fmt.Println("大端字節序使用小端輸出結果:",little)
}
// 結果:
轉換成大端後  [0 0 0 10]
使用大端字節序輸出結果: 10
大端字節序使用小端輸出結果: 167772160

grpc中對大端的應用

你們對gRPC必定很熟悉,最近在看gRPC源碼時,看到gRPC封裝message時,在封裝header時,特地指定了使用大端字節序,源碼以下:

// msgHeader returns a 5-byte header for the message being transmitted and the
// payload, which is compData if non-nil or data otherwise.
func msgHeader(data, compData []byte) (hdr []byte, payload []byte) {
    hdr = make([]byte, headerLen)
    if compData != nil {
        hdr[0] = byte(compressionMade)
        data = compData
    } else {
        hdr[0] = byte(compressionNone)
    }

    // Write length of payload into buf
    binary.BigEndian.PutUint32(hdr[payloadLen:], uint32(len(data)))
    return hdr, data
}

結尾

在本文的最後咱們再來作一下總結:

  • 大端小端是不一樣的字節順序存儲方式,統稱爲字節序
  • 大端:是指數據的高字節位 保存在 內存的低地址中,而數據的低字節位 保存在 內存的高地址中。這樣的存儲模式有點兒相似於把數據看成字符串順序處理:地址由小向大增長,而數據從高位往低位放。和咱們」從左到右「閱讀習慣一致。
  • 小端:是指數據的高字節位 保存在 內存的高地址中,而數據的低字節位 保存在 內存的低地址中。這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低,和咱們的邏輯方法一致
  • 區分:計算機處理字節序的時候,不知道什麼是高位字節,什麼是低位字節。它只知道按順序讀區字節,先讀取第一個字節,再讀取第二個字節,因此說我就能夠根據這個特性來讀判斷大小端。
  • 轉換:經過位操做就能夠實現,具體可使用標準庫encoding/binary

本文的全部代碼已經上傳githubhttps://github.com/asong2020/...;歡迎star

好啦,這篇文章就到這裏啦,素質三連(分享、點贊、在看)都是筆者持續創做更多優質內容的動力!

建立了一個Golang學習交流羣,歡迎各位大佬們踊躍入羣,咱們一塊兒學習交流。入羣方式:加我vx拉你入羣,或者公衆號獲取入羣二維碼

結尾給你們發一個小福利吧,最近我在看[微服務架構設計模式]這一本書,講的很好,本身也收集了一本PDF,有須要的小夥能夠到自行下載。獲取方式:關注公衆號:[Golang夢工廠],後臺回覆:[微服務],便可獲取。

我翻譯了一份GIN中文文檔,會按期進行維護,有須要的小夥伴後臺回覆[gin]便可下載。

翻譯了一份Machinery中文文檔,會按期進行維護,有須要的小夥伴們後臺回覆[machinery]便可獲取。

我是asong,一名普普統統的程序猿,讓咱們一塊兒慢慢變強吧。歡迎各位的關注,咱們下期見~~~

推薦往期文章:

相關文章
相關標籤/搜索