欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang字符編碼的實(shí)現(xiàn)

 更新時(shí)間:2024年08月22日 11:46:57   作者:molaifeng  
本文主要介紹了golang字符編碼的實(shí)現(xiàn),,有三種編碼形式:UTF-8,UTF-16,UTF-32,下面就來介紹一下如何使用,感興趣的可以了解一下

1、 Golang 字符編碼

Golang 的代碼是由 Unicode 字符組成的,并由 Unicode 編碼規(guī)范中的 UTF-8 編碼格式進(jìn)行編碼并存儲。

Unicode 是編碼字符集,囊括了當(dāng)今世界使用的全部語言和符號的字符。有三種編碼形式:UTF-8,UTF-16,UTF-32。(UTF: Unicode Transformation Format,統(tǒng)一碼轉(zhuǎn)換格式)

在這幾種編碼格式的名稱中,- 右邊的整數(shù)的含義是,以多少個(gè)比特作為一個(gè)編碼單元。以 UTF-8 為例,它會以 8 個(gè)比特也就是一個(gè)字節(jié),作為一個(gè)編碼單元。并且,它與標(biāo)準(zhǔn)的 ASCII 編碼是完全兼容的。也就是說,在 [0x00, 0x7F]的范圍內(nèi),這兩種編碼表示的字符都是相同的,這也是 UTF-8 編碼格式的一個(gè)巨大優(yōu)勢(這里不探討 UTF-16 及 UTF-32)。

UTF-8 是一種可變長的編碼方案。換句話說,它會用一個(gè)或多個(gè)字節(jié)來表示某個(gè)字符,最多使用四個(gè)字節(jié)。比如,對于一個(gè)英文字符,它僅用一個(gè)字節(jié)就可以表示,而對于一個(gè)中文字符,它需要使用三個(gè)字節(jié)才能夠表示。不論怎樣,一個(gè)受支持的字符總是可以由 UTF-8 編碼為一個(gè)字節(jié)序列。以下會簡稱后者為 UTF-8 編碼值。

在這里插入圖片描述

從上圖可知 UTF-8 的編碼方式:

  • 什么時(shí)候讀1個(gè)字節(jié)的字符?

字節(jié)的第一位為0,后面7位為符號的unicode碼。所以這樣看,英語字母的utf-8ascii一致。

什么時(shí)候讀多個(gè)字節(jié)的字符?

對于有n個(gè)字節(jié)的字符,(n>1)…. 其中第一個(gè)字節(jié)的高n位就為1,換句話說:

  • 第一個(gè)字節(jié)讀到0,那就是讀1個(gè)字節(jié)
  • 第一個(gè)字節(jié)讀到n個(gè)1,就要讀n個(gè)字節(jié)
0xxxxxxx # 讀1個(gè)字節(jié)
110xxxxx 10xxxxxx # 讀2個(gè)字節(jié)
1110xxxx 10xxxxxx 10xxxxxx #讀3個(gè)字節(jié)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx #讀4個(gè)字節(jié)

Unicode符號范圍      |        UTF-8編碼方式
(十六進(jìn)制)           |        (二進(jìn)制)
------------------ -+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

那 Unicode 是如何填充UTF-8各個(gè)字節(jié)的呢?

比如  這個(gè)漢字,對應(yīng)的 unicode編碼為 U+7801

  • 對應(yīng)的十六進(jìn)制處于 0000 0800-0000 FFFF 中,也就是 3 個(gè)字節(jié),相應(yīng)的二進(jìn)制為 1110xxxx 10xxxxxx 10xxxxxx
  •  的 unicode編碼為 U+7801 對應(yīng)的二進(jìn)制為 111100000000001,為了和接下來填充字節(jié)方便,這里做個(gè)格式優(yōu)化 111 100000 000001
  • 從后向前填充,高位不夠的補(bǔ)0
  • 000001 填充第三個(gè)字節(jié)(從左往右數(shù))10000001
  • 100000 填充第二個(gè)字節(jié) 10100000
  • 111 填充第一個(gè)字節(jié),高位不夠的就補(bǔ)0,為 11100111
  • 最終結(jié)果為 11100111 10100000 10000001(對應(yīng)的十六進(jìn)制分別對應(yīng) e7 a0 81
func TestInt(t *testing.T) {
	s1 := "碼"
	for i := 0; i < len(s1); i++ {
		fmt.Printf("%x ", s1[i])
	}
}

打印的結(jié)果為 e7 a0 81,和上面演算的一致。

2、string 數(shù)據(jù)結(jié)構(gòu)

先來看看 Golang 的 string 的數(shù)據(jù)結(jié)構(gòu)

type StringHeader struct {
	Data uintptr
	Len  int
}

其中包含指向字節(jié)數(shù)組的指針 Data 和數(shù)組的大小 Len,后者 Len 方便在 len() 時(shí)可以 O(1) 時(shí)間給出大小,就是常見的以空間換時(shí)間。字符串由字符組成,字符的底層由字節(jié)組成,而一個(gè)字符串在底層的表示是一個(gè)字節(jié)序列,這個(gè)字節(jié)序列就存儲在 Data 里,不過是只讀的。

import (
	"fmt"
	"testing"
)

func TestStr(t *testing.T) {
	str := "Hello World"
	fmt.Println(str)
}

把上面代碼 go tool compile -S str_test.go > str_test.S 生成匯編代碼,然后找到

go.string."Hello World" SRODATA dupok size=11
	0x0000 48 65 6c 6c 6f 20 57 6f 72 6c 64                 Hello World

能夠看到 Hello World 旁有一個(gè) SRODATA 的標(biāo)記,在 Golang 中編譯器會將只讀數(shù)據(jù)標(biāo)記成 SRODATA。

再來看看 slice 的數(shù)據(jù)結(jié)構(gòu)

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

相比 string 多了個(gè) Cap,因此在 Golang 中,字符串實(shí)際上是只讀的字節(jié)切片。

那么對于只讀的 string,若是想要改值應(yīng)該怎么弄呢?

func TestModifyString(t *testing.T) {

	str := "golang編程"

	l := []byte(str)
	l[0] = 'G'

	fmt.Println(string(l)) // Golang編程
}

轉(zhuǎn)成相應(yīng)的字節(jié)數(shù)組,然后以索引的形式更新值。

3、string 編碼方式

前面說過,字符串由字符組成,字符的底層由字節(jié)組成,而一個(gè)字符串在底層的表示是一個(gè)字節(jié)序列。在 Golang 中,字符可以被分成兩種類型處理:對占 1 個(gè)字節(jié)的英文類字符,可以使用 byte(或者 unit8);對占 1 ~ 4 個(gè)字節(jié)的其他字符,可以使用 rune(或者int32),如中文、特殊符號等。

// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is
// used, by convention, to distinguish byte values from 8-bit unsigned
// integer values.
type byte = uint8

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

可以看到 byte 、 rune 其實(shí)分別就是 uint8、int32 的別名,byte 占 1 個(gè)字節(jié), rune 占 4個(gè)字節(jié)。

func TestStrLen(t *testing.T) {

	str1 := "go"
	str2 := "go編程"

	fmt.Printf("%v len is %d\n", str1, len(str1))
	fmt.Printf("%v len is %d\n", str2, len(str2))

}

運(yùn)行后,發(fā)現(xiàn) str1 長度為 2 這個(gè)沒問題,但 str2 的長度不是 4 而是 8,這是什么原因呢?

先不著急找答案,看看下面的代碼

func printBytes(s string) {
	fmt.Printf("Bytes: ")
	for i := 0; i < len(s); i++ {
		fmt.Printf("%x ", s[i]) // 按十六進(jìn)制輸出
	}
	fmt.Printf("\n")
}

func printChars(s string) {
	fmt.Printf("Charaters: ")
	for i := 0; i < len(s); i++ {
		fmt.Printf("%c ", s[i]) // 將數(shù)字轉(zhuǎn)換成它對應(yīng)的 Unicode 字符
	}
	fmt.Printf("\n")
}

func TestInt(t *testing.T) {
	s1 := "go編程"
	fmt.Printf("s1: %s, bytes len(s1)=%d\n", s1, len(s1))
	fmt.Printf("s1: %s, rune  len(s1)=%d\n", s1, len([]rune(s1)))
	printBytes(s1)
	printChars(s1)
}

運(yùn)行后打印如下

s1: go編程, bytes len(s1)=8
s1: go編程, rune  len(s1)=4
Bytes: 67 6f e7 bc 96 e7 a8 8b 
Charaters: g o ç ¼ – ç ¨

仔細(xì)看,發(fā)現(xiàn) rune 類型的輸出了 4,另外 printChars 輸出亂碼了。

先來看看 rune 類型,是 int32 的別名,也就是說,一個(gè) rune 類型的值會由 4 個(gè)字節(jié)寬度的空間來存儲。它的存儲空間總是能夠存下一個(gè) UTF-8 編碼值。一個(gè) rune 類型的值在底層其實(shí)就是一個(gè) UTF-8 編碼值。前者是(便于我們?nèi)祟惱斫獾模┩獠空宫F(xiàn),后者是(便于計(jì)算機(jī)系統(tǒng)理解的)內(nèi)在表達(dá)。

Golang 中常用 rune 類型來處理中文。printChars 之所以輸出亂碼,是因?yàn)樵诘谝还?jié)中提到的在 UTF-8 中漢字是以三個(gè)字節(jié)存儲的,len() 是按單字節(jié)來計(jì)算長度,因此對于三個(gè)字節(jié)的中文來說輸出三分之鐵定亂碼。那么如何輸出才不亂碼呢?

func TestRune(t *testing.T) {
	str := "golang編程"
	l := []rune(str)
	for i := 0; i < len(l); i++ {
		fmt.Printf("%c ", l[i])
	}
}

打印輸出 g o l a n g 編 程。

當(dāng)然了,還可以使用 for range 來打印字符串里的中文。

func TestRange(t *testing.T) {
	str := "golang編程"
	for i, s := range str {
		fmt.Printf("%d: %c\n", i, s)
	}
}

打印輸出

0: g
1: o
2: l
3: a
4: n
5: g
6: 編
9: 程

那為什么會這樣呢?原因就在 Golang 中,會把 for range 結(jié)構(gòu)轉(zhuǎn)換成如下所示的形式

	// Transform string range statements like "for v1, v2 = range a" into
	ha := a
	for hv1 := 0; hv1 < len(ha); {
	  hv1t := hv1
	  hv2 := rune(ha[hv1])
	  if hv2 < utf8.RuneSelf {
	     hv1++
	  } else {
	     hv2, hv1 = decoderune(ha, hv1)
	  }
	  v1, v2 = hv1t, hv2
	  // original body
	}

for range 循環(huán)在迭代字符串時(shí)會逐個(gè)處理字符串中的 Unicode 碼點(diǎn)(rune),而不是字節(jié)。由于 Golang 的原生字符串類型是以 UTF-8 編碼的,UTF-8 是一種能夠表示 Unicode 碼點(diǎn)的變長編碼方式,for range 循環(huán)能夠正確處理這種編碼。

通俗點(diǎn)就是 for range 會先把被遍歷的字符串值拆成一個(gè)字節(jié)序列,然后再試圖找出這個(gè)字節(jié)序列中包含的每一個(gè) UTF-8 編碼值,或者說每一個(gè) Unicode字符。

func TestRange(t *testing.T) {
	str := "golang編程"
	for i, s := range str {
		fmt.Printf("%d: %c [% x]\n", i, s, []byte(string(s)))
	}
}

打印輸出

0: g [67]
1: o [6f]
2: l [6c]
3: a [61]
4: n [6e]
5: g [67]
6: 編 [e7 bc 96]
9: 程 [e7 a8 8b]

由此可以看出,字符串中相鄰 Unicode 字符的索引值不一定是連續(xù)的。 這取決于前一個(gè) Unicode 字符是否為單字節(jié)字符(byte)。Golang 中的一個(gè) string 類型值會由若干個(gè) Unicode 字符組成,每個(gè) Unicode 字符都可以由一個(gè) rune 類型的值來承載。這些字符在底層都會被轉(zhuǎn)換為 UTF-8 編碼值,而這些 UTF-8 編碼值又會以字節(jié)序列的形式表達(dá)和存儲。因此,一個(gè)string 類型的值在底層就是一個(gè)能夠表達(dá)若干個(gè) UTF-8 編碼值的字節(jié)序列。

ok,到這里了,發(fā)現(xiàn)兩種不同的 for 循環(huán)在輸出字符串的字符時(shí)會有所不同,這里做個(gè)歸類

  • for-standalone 會遍歷字符串的每一個(gè)字節(jié)(Byte類型),在遇到字符串中有漢字時(shí)會亂碼
  • for-range 會遍歷字符串的每一個(gè) Unicode 字符(Rune 類型) ,在遇到字符串中有漢字時(shí)不會亂碼

最后說說 string、byte 和 rune 三者之間的關(guān)系。

  • string 在底層的表示是由單個(gè)字節(jié)組成的只讀的字節(jié)序列,Golang 的字符串是以 UTF-8 編碼存儲的,這意味著它們可以包含任意的 Unicode 字符。Golang 把字符分 byte 和 rune 兩種類型處理。
  • byte 是類型 unit8 的別名,用于存放占 1 個(gè)字節(jié)的 ASCII 字符,如英文字符,返回的是字符原始字節(jié)。由于 Golang 的字符串是以 UTF-8 編碼的,一個(gè) byte 可能表示一個(gè)字符的一部分(對于多字節(jié)字符如中文字符),也可能表示一個(gè)完整的字符(對于 ASCII 字符)。
  • rune 是類型 int32 的別名,用于存放多字節(jié)字符,如占 3 字節(jié)的中文字符,返回的是字符 Unicode 碼點(diǎn)值(或者說它代表一個(gè) Unicode 碼點(diǎn))。在處理字符串時(shí),rune 用于表示字符串中的一個(gè)完整的 Unicode 字符,無論這個(gè)字符是由多少個(gè)字節(jié)組成的。rune 類型的變量可以存儲任何 Unicode 字符,包括那些由多個(gè)字節(jié)表示的字符。

等等,等等,到這里,不妨再多看看。那么如果計(jì)算一個(gè)字符串的長度呢,用自帶的 len() 函數(shù)對于單字節(jié)的字符串來說是準(zhǔn)確的,若是帶有中文字符這種多字節(jié)的字符串就不準(zhǔn)確了,這時(shí)除了自己造輪子外,其實(shí)可以用 Golang 內(nèi)置的 utf8.RuneCountInString 來統(tǒng)計(jì)。

func TestCountStr(t *testing.T) {
	str := "golang編程"
	fmt.Println(utf8.RuneCountInString(str)) // 8
}

有興趣的讀者可以看看其內(nèi)部實(shí)現(xiàn)。

// RuneCountInString is like RuneCount but its input is a string.
func RuneCountInString(s string) (n int) {
	ns := len(s)
	for i := 0; i < ns; n++ {
		c := s[i]
		if c < RuneSelf {
			// ASCII fast path
			i++
			continue
		}
		x := first[c]
		if x == xx {
			i++ // invalid.
			continue
		}
		size := int(x & 7)
		if i+size > ns {
			i++ // Short or invalid.
			continue
		}
		accept := acceptRanges[x>>4]
		if c := s[i+1]; c < accept.lo || accept.hi < c {
			size = 1
		} else if size == 2 {
		} else if c := s[i+2]; c < locb || hicb < c {
			size = 1
		} else if size == 3 {
		} else if c := s[i+3]; c < locb || hicb < c {
			size = 1
		}
		i += size
	}
	return n
}

到此這篇關(guān)于golang字符編碼的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)golang字符編碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go?Excelize?API源碼閱讀SetSheetViewOptions示例解析

    Go?Excelize?API源碼閱讀SetSheetViewOptions示例解析

    這篇文章主要為大家介紹了Go-Excelize?API源碼閱讀SetSheetViewOptions示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • golang-gin-mgo高并發(fā)服務(wù)器搭建教程

    golang-gin-mgo高并發(fā)服務(wù)器搭建教程

    這篇文章主要介紹了golang-gin-mgo高并發(fā)服務(wù)器搭建教程,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang API開發(fā)過程的中的自動重啟方式(基于gin框架)

    golang API開發(fā)過程的中的自動重啟方式(基于gin框架)

    這篇文章主要介紹了golang API開發(fā)過程的中的自動重啟方式(基于gin框架),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • Golang新提案:panic?能不能加個(gè)?PanicError?

    Golang新提案:panic?能不能加個(gè)?PanicError?

    這篇文章主要為大家介紹了Golang的新提案關(guān)于panic能不能加個(gè)PanicError的問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • go語言的初始化順序,包,變量,init詳解

    go語言的初始化順序,包,變量,init詳解

    這篇文章主要介紹了go語言的初始化順序,包,變量,init詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang接口型函數(shù)使用小結(jié)

    Golang接口型函數(shù)使用小結(jié)

    接口函數(shù)指的是用函數(shù)實(shí)現(xiàn)接口,這樣在調(diào)用的時(shí)候就會非常簡便,這種方式適用于只有一個(gè)函數(shù)的接口,這里以迭代一個(gè)map為例,演示這一實(shí)現(xiàn)的技巧,對Golang接口型函數(shù)使用知識感興趣的朋友一起看看吧
    2022-06-06
  • golang通過反射手動實(shí)現(xiàn)json序列化的方法

    golang通過反射手動實(shí)現(xiàn)json序列化的方法

    在 Go 語言中,JSON 序列化和反序列化通常通過標(biāo)準(zhǔn)庫 encoding/json 來實(shí)現(xiàn),本文給大家介紹golang  通過反射手動實(shí)現(xiàn)json序列化的方法,感興趣的朋友一起看看吧
    2024-12-12
  • GoLang抽獎系統(tǒng)簡易實(shí)現(xiàn)流程

    GoLang抽獎系統(tǒng)簡易實(shí)現(xiàn)流程

    這篇文章主要介紹了GoLang抽獎系統(tǒng)實(shí)現(xiàn)流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2022-12-12
  • Golang中map的三種聲明定義方式實(shí)現(xiàn)

    Golang中map的三種聲明定義方式實(shí)現(xiàn)

    本文主要介紹了Golang中map的三種聲明定義方式實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Golang 語言控制并發(fā) Goroutine的方法

    Golang 語言控制并發(fā) Goroutine的方法

    本文我們介紹了不同場景中分別適合哪種控制并發(fā) goroutine 的方式,其中,channel 適合控制少量 并發(fā) goroutine,WaitGroup 適合控制一組并發(fā) goroutine,而 context 適合控制多級并發(fā) goroutine,感興趣的朋友跟隨小編一起看看吧
    2021-06-06

最新評論