字符编码以及go中的处理

本文通过实例演示了如何使用PHP中的mb_strlen(), mb_detect_encoding(), iconv()等函数来检测和转换不同编码(如UTF-8和GB2312)的字符串长度,并解释了在不同编码下字符串长度的变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础概念

Unicode统一码

参照统一码_百度百科

它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

在表示一个Unicode的字符时,通常会用“U+”然后紧接着一组十六进制的数字来表示这一个字符。在基本多文种平面(英文为 Basic Multilingual Plane,简写 BMP。它又简称为“零号平面”, plane 0)里的所有字符,要用四位十六进制数(例如U+4AE0,共支持六万多个字符);在零号平面以外的字符则需要使用五位或六位十六进制数了。旧版的Unicode标准使用相近的标记方法,但却有些微的差异:在Unicode 3.0里使用“U-”然后紧接着八位数,而“U+”则必须随后紧接着四位数。

Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。目前的Unicode字符分为17组编排,0x0000 至 0x10FFFF,每组称为平面(Plane),而每平面拥有65536个码位,共1114112个。然而目前只用了少数平面。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。

unicode就是一个编码方案,让世界上任何一个字符有唯一一个数字对应,而utf-8是将这个数字转换到程序数据的编码方案。
 

UTF-8

UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:

Unicode编码(十六进制) UTF-8 字节流(二进制)
000000-00007F0xxxxxxx
000080-0007FF110xxxxx 10xxxxxx
000800-00FFFF1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF11110xxx10xxxxxx10xxxxxx10xxxxxx

理解go如何处理字符

打印go中的字符

比如字符 “ ⌘”,它的unicode对应是“U+2318”,参考网站 在线 Unicode 编码转换

go的源文件都是UTF-8编码的,参考UTF-8编码表,U+2318在范围(000800 - 00FFFF),所以采用三个字节编码,(1110xxxx 10xxxxxx 10xxxxxx)。

2318是四个16进制数字,转为二进制是 0010 0011 0001 1000。 把它赋值给16个x,得到11100010 10001100 10011000。也就是最终这个程序文件中“⌘”对应的就是前面这个三个字节。对应十进制分别是226 、 140、152。参考在线进制转换

/*
"⌘"的UTF-8编码为11100010 10001100 10011000占了三个字节,len(string)得到字节数, 所以是3
"⌘"转为字节数组,每个字节对应的十进制数字是[226 140 152]
"⌘"转为int32的数组,首先基于UTF-8编码,知道它对应的是U+2318,16进制的2318对应的十进制是8984
*/
func main() {
    var simple = "⌘"
	fmt.Println(len(simple)) //3

	simple2 := []byte(simple)
	fmt.Println(simple2)           //[226 140 152]
	fmt.Printf("%08b \n", simple2) //[11100010 10001100 10011000]
	fmt.Println(string(simple2))   //⌘
	c := []byte{226, 140, 152}     // 或者 []byte{0xe2, 0x8c, 0x98}
	fmt.Println("c的值", string(c))  //c的值 ⌘

	simple3 := []rune(simple)
	fmt.Println(simple3)            //[8984]
	fmt.Printf("%08b \n", simple3)  //[10001100011000]
	fmt.Printf("%U \n", simple3)    //[U+2318]
	fmt.Println(string(simple3))    //⌘
	c2 := []rune{0x2318}            // 或者 []byte{0xe2, 0x8c, 0x98}
	fmt.Println("c2的值", string(c2)) //c的值 ⌘
}

创建一个文件叫1.txt,里面就写一个字符⌘。 按字节读取,就能读到三个字节,用string()强制转换,就又能得到⌘。

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    b, err := ioutil.ReadFile("1.txt")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(b)         //[226 140 152]
    fmt.Println(string(b)) //⌘
}

[]byte和string转换,byte和string转换

func main() {
	//字符a的uft8编码后是一个字节,01100001。
	//直接获取一个字符串的第0个索引的字符,实际就是把它转为[]byte,然后获取第一个字节,就是01100001。
	//golang中byte类型是uint8的别名。
	a := "a"
	fmt.Println(a[0])          // 打印 97
	fmt.Printf("%T\n", a[0])   // 打印 uint8
	fmt.Printf("%08b\n", a[0]) // 打印 01100001
	fmt.Printf("%x\n", a)      // 打印 61

	//可以对二进制进行位操作
	b := a[0] << 1          //左移1位
	fmt.Println(b)          // 打印 194
	fmt.Printf("%T\n", b)   // 打印 uint8
	fmt.Printf("%08b\n", b) // 打印 11000010
	fmt.Printf("%x\n", b)   // 打印 c2

	//把一个[]byte转为string,用string()函数即可,会作为utf8编码的字节数组处理
	c := []byte{0x61}               // 或者 []byte{97}
	fmt.Println("c的值: ", string(c)) // 打印 a
	// 下面的[]byte并不是一个合法的utf8,所以输出乱码
	c2 := []byte{0xc2}
	fmt.Println("c2的值: " + string(c2)) // 打印 �

	// 如果把一个byte转为sting,使用string()函数时,会把这个单个字节的值作为unicode,然后转为uft8编码的字符
	// 这个是01100001,转为uft8编码仍然是 01100001
	d := 0x61
	fmt.Println("d的值: ", string(d))
	// 这个是11000010,转为uft8,根据模板110xxxxx 10xxxxxx, 得到 11000011 10000010,
	d2 := 0xc2
	fmt.Println("d2的值: " + string(d2))     // 打印 Â
	fmt.Printf("%b\n", []byte(string(d2))) // 打印 [11000011 10000010]
}

go如何处理字符

字符串在底层的表示是由单个字符组成的一个不可修改的字节序列。字节的本质是使用UTF-8编码的一个个Unicode字符的字节形式。UTF-8 是 Unicode 的一种实现方式,是一种针对 Unicode 可变长度的字符编码,它定义了字符串具体以何种方式存储在内存中。UFT-8 使用 1 ~ 4 为每个字符编码。

举例子,上面的‘⌘’,它的Unicode就是 U+2318,用utf8编码就是 11100010 10001100 10011000,占三个字节。

Go语言把字符分 byte rune 两种类型处理。

  • byte是类型uint8的别名,用于存放占1字节的ASCII字符,如英文字符,返回的是字符原始字节。
  • rune是类型int32的别名,用于存放多字节字符,如占3字节的中文字符,返回的是字符Unicode码点值。

比如:

s := "Go语言编程"
// byte
fmt.Println([]byte(s)) // 输出:[71 111 232 175 173 232 168 128 231 188 150 231 168 139]
// rune
fmt.Println([]rune(s)) // 输出:[71 111 35821 35328 32534 31243]

它们的对应关系如下:

rune类型怎么用?

统计中文字符串长度

	// 使用内置函数 len() 统计字符串长度
	fmt.Println(len("Go语言编程")) // 输出:14
	// 转换成 rune 数组后统计字符串长度
	fmt.Println(len([]rune("Go语言编程"))) // 输出:6

截取带中文字符串

s := "Go语言编程"
// 8=2*1+2*3
fmt.Println(s[0:8])  // 输出:Go语言

s := "Go语言编程"
fmt.Println(s[0:7]) // 输出:Go语�

s := "Go语言编程"
// 转成 rune 数组,需要几个字符,取几个字符
fmt.Println(string([]rune(s)[:4])) // 输出:Go语言    

uft8.RuneCountInString()源码解析

看看在源码中,RuneCountInString ()函数中,是如何处理一个字符串,计算字符数量的。

// RuneCountInString is like RuneCount but its input is a string.
func RuneCountInString(s string) (n int) {
    // 调用len()函数得到字节数
	ns := len(s)
	for i := 0; i < ns; n++ {
		c := s[i]
        // 如码点值小于 128,则为占 1 字节的 ASCII 字符(或者说英文字符),长度 + 1
		if c < RuneSelf {
			// ASCII fast path
			i++
			continue
		}
        // 查询首字节信息表,能知道当前字符会占几个字节,比如中文占 3 字节,x = 3
        // 遇到无效的字节数据,当作1继续看下一个
		x := first[c]
		if x == xx {
			i++ // invalid.
			continue
		}
        // 提取有效的 UTF-8 字节长度编码信息,比如size = 3
        // 如果后续剩余字节加一起都不够,就当前当作无效的,算作1
		size := int(x & 7)
		if i+size > ns {
			i++ // Short or invalid.
			continue
		}
        // 检验有效字节范围,符合范围检测的按照字符个数计算即+1
        // 如果字节范围不对,就把当前字节算作+1
		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
}

曾经PHP语言用的到编码函数

字符编码函数
mb_strlen()
mb_detect_encoding()
iconv()
strlen()


$str = “我是超人";
$encoding = mb_detect_encoding($str,array('ASCII','UTF-8','GB2312','GBK','BIG5'));
var_dump($encoding);//输出string(5) "UTF-8"
var_dump(strlen($str));//输出int(12),对于UTF-8编码的中文字,一个字占3个字节,所以四个字是12字节.
var_dump(mb_strlen($str,'UTF-8'));//输出int(4),因为本身字符是UTF-8编码,按照UTF-8来处理其长度,实际就是4个中文字.
var_dump(mb_strlen($str,'8bit'));//输出int(12),字节长度12
print("<br>");
$str = iconv('UTF-8','GB2312',$str);//转换为GB2312编码
$encoding = mb_detect_encoding($str,array('ASCII','UTF-8','GB2312','GBK','BIG5'));
var_dump($encoding);//输出string(6) "EUC-CN"
var_dump(strlen($str));//输出int(8),对于GB2312编码的中文字,一个字占2个字节,所以四个字是8字节.
var_dump(mb_strlen($str,'GB2312'));//输出int(4),因为本身字符是GB2312编码,按照GB2312来处理其长度,实际就是4个中文字.
var_dump(mb_strlen($str,'8bit'));//输出int(8),字节长度8
die;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值