基础概念
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-00007F | 0xxxxxxx |
000080-0007FF | 110xxxxx 10xxxxxx |
000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 11110xxx10xxxxxx10xxxxxx10xxxxxx |
理解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;