浅谈golang字符编码

news2024/11/25 0:36:50
1、 Golang 字符编码

Golang 的代码是由 Unicode 字符组成的,并由 Unicode 编码规范中的 UTF-8 编码格式进行编码并存储。

Unicode 是编码字符集,囊括了当今世界使用的全部语言和符号的字符。有三种编码形式:UTF-8UTF-16UTF-32。(UTF: Unicode Transformation Format,统一码转换格式)

在这几种编码格式的名称中,- 右边的整数的含义是,以多少个比特作为一个编码单元。以 UTF-8 为例,它会以 8 个比特也就是一个字节,作为一个编码单元。并且,它与标准的 ASCII 编码是完全兼容的。也就是说,在 [0x00, 0x7F]的范围内,这两种编码表示的字符都是相同的,这也是 UTF-8 编码格式的一个巨大优势(这里不探讨 UTF-16UTF-32)。

UTF-8 是一种可变长的编码方案。换句话说,它会用一个或多个字节来表示某个字符,最多使用四个字节。比如,对于一个英文字符,它仅用一个字节就可以表示,而对于一个中文字符,它需要使用三个字节才能够表示。不论怎样,一个受支持的字符总是可以由 UTF-8 编码为一个字节序列。以下会简称后者为 UTF-8 编码值。

在这里插入图片描述
从上图可知 UTF-8 的编码方式:

  • 什么时候读1个字节的字符?
    • 字节的第一位为0,后面7位为符号的unicode码。所以这样看,英语字母的utf-8ascii一致。
  • 什么时候读多个字节的字符?
    • 对于有n个字节的字符,(n>1)…. 其中第一个字节的高n位就为1,换句话说:
      • 第一个字节读到0,那就是读1个字节
      • 第一个字节读到n1,就要读n个字节
0xxxxxxx # 读1个字节
110xxxxx 10xxxxxx # 读2个字节
1110xxxx 10xxxxxx 10xxxxxx #读3个字节
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx #读4个字节

Unicode符号范围      |        UTF-8编码方式
(十六进制)           |        (二进制)
------------------ -+---------------------------------------------
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各个字节的呢?

比如 这个汉字,对应的 unicode编码为 U+7801

  • 对应的十六进制处于 0000 0800-0000 FFFF 中,也就是 3 个字节,相应的二进制为 1110xxxx 10xxxxxx 10xxxxxx
  • unicode编码为 U+7801 对应的二进制为 111100000000001,为了和接下来填充字节方便,这里做个格式优化 111 100000 000001
  • 从后向前填充,高位不够的补0
  • 000001 填充第三个字节(从左往右数)10000001
  • 100000 填充第二个字节 10100000
  • 111 填充第一个字节,高位不够的就补0,为 11100111
  • 最终结果为 11100111 10100000 10000001(对应的十六进制分别对应 e7 a0 81
func TestInt(t *testing.T) {
	s1 := "码"
	for i := 0; i < len(s1); i++ {
		fmt.Printf("%x ", s1[i])
	}
}

打印的结果为 e7 a0 81,和上面演算的一致。

2、string 数据结构

先来看看 Golangstring 的数据结构

type StringHeader struct {
	Data uintptr
	Len  int
}

其中包含指向字节数组的指针 Data 和数组的大小 Len,后者 Len 方便在 len() 时可以 O(1) 时间给出大小,就是常见的以空间换时间。字符串由字符组成,字符的底层由字节组成,而一个字符串在底层的表示是一个字节序列,这个字节序列就存储在 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 旁有一个 SRODATA 的标记,在 Golang 中编译器会将只读数据标记成 SRODATA

再来看看 slice 的数据结构

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

相比 string 多了个 Cap,因此在 Golang 中,字符串实际上是只读的字节切片。

那么对于只读的 string,若是想要改值应该怎么弄呢?

func TestModifyString(t *testing.T) {

	str := "golang编程"

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

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

转成相应的字节数组,然后以索引的形式更新值。

3、string 编码方式

前面说过,字符串由字符组成,字符的底层由字节组成,而一个字符串在底层的表示是一个字节序列。在 Golang 中,字符可以被分成两种类型处理:对占 1 个字节的英文类字符,可以使用 byte(或者 unit8);对占 1 ~ 4 个字节的其他字符,可以使用 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

可以看到 byterune 其实分别就是 uint8int32 的别名,byte1 个字节, rune4个字节。

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))

}

运行后,发现 str1 长度为 2 这个没问题,但 str2 的长度不是 4 而是 8,这是什么原因呢?

先不着急找答案,看看下面的代码

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

func printChars(s string) {
	fmt.Printf("Charaters: ")
	for i := 0; i < len(s); i++ {
		fmt.Printf("%c ", s[i]) // 将数字转换成它对应的 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)
}

运行后打印如下

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

仔细看,发现 rune 类型的输出了 4,另外 printChars 输出乱码了。

先来看看 rune 类型,是 int32 的别名,也就是说,一个 rune 类型的值会由 4 个字节宽度的空间来存储。它的存储空间总是能够存下一个 UTF-8 编码值。一个 rune 类型的值在底层其实就是一个 UTF-8 编码值。前者是(便于我们人类理解的)外部展现,后者是(便于计算机系统理解的)内在表达。

Golang 中常用 rune 类型来处理中文。printChars 之所以输出乱码,是因为在第一节中提到的在 UTF-8 中汉字是以三个字节存储的,len() 是按单字节来计算长度,因此对于三个字节的中文来说输出三分之铁定乱码。那么如何输出才不乱码呢?

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 编 程

当然了,还可以使用 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 结构转换成如下所示的形式

	// 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 循环在迭代字符串时会逐个处理字符串中的 Unicode 码点(rune),而不是字节。由于 Golang 的原生字符串类型是以 UTF-8 编码的,UTF-8 是一种能够表示 Unicode 码点的变长编码方式,for range 循环能够正确处理这种编码。

通俗点就是 for range 会先把被遍历的字符串值拆成一个字节序列,然后再试图找出这个字节序列中包含的每一个 UTF-8 编码值,或者说每一个 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 字符的索引值不一定是连续的。 这取决于前一个 Unicode 字符是否为单字节字符(byte)。Golang 中的一个 string 类型值会由若干个 Unicode 字符组成,每个 Unicode 字符都可以由一个 rune 类型的值来承载。这些字符在底层都会被转换为 UTF-8 编码值,而这些 UTF-8 编码值又会以字节序列的形式表达和存储。因此,一个string 类型的值在底层就是一个能够表达若干个 UTF-8 编码值的字节序列。

ok,到这里了,发现两种不同的 for 循环在输出字符串的字符时会有所不同,这里做个归类

  • for-standalone 会遍历字符串的每一个字节(Byte类型),在遇到字符串中有汉字时会乱码
  • for-range 会遍历字符串的每一个 Unicode 字符(Rune 类型) ,在遇到字符串中有汉字时不会乱码

最后说说 stringbyterune 三者之间的关系。

  • string 在底层的表示是由单个字节组成的只读的字节序列,Golang 的字符串是以 UTF-8 编码存储的,这意味着它们可以包含任意的 Unicode 字符。Golang 把字符分 byterune 两种类型处理。
  • byte 是类型 unit8 的别名,用于存放占 1 个字节的 ASCII 字符,如英文字符,返回的是字符原始字节。由于 Golang 的字符串是以 UTF-8 编码的,一个 byte 可能表示一个字符的一部分(对于多字节字符如中文字符),也可能表示一个完整的字符(对于 ASCII 字符)。
  • rune 是类型 int32 的别名,用于存放多字节字符,如占 3 字节的中文字符,返回的是字符 Unicode 码点值(或者说它代表一个 Unicode 码点)。在处理字符串时,rune 用于表示字符串中的一个完整的 Unicode 字符,无论这个字符是由多少个字节组成的。rune 类型的变量可以存储任何 Unicode 字符,包括那些由多个字节表示的字符。

等等,等等,到这里,不妨再多看看。那么如果计算一个字符串的长度呢,用自带的 len() 函数对于单字节的字符串来说是准确的,若是带有中文字符这种多字节的字符串就不准确了,这时除了自己造轮子外,其实可以用 Golang 内置的 utf8.RuneCountInString 来统计。

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

有兴趣的读者可以看看其内部实现。

// 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
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1827838.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode215】数组中的第K个最大元素

题目地址 1. 基本思路 用一个基准数e将集合S分解为不包含e在内的两个小集合 S 1 S_{1} S1​和 S 2 S_{2} S2​&#xff0c;其中 S 1 S_{1} S1​的任何元素均大于等于e&#xff0c; S 2 S_{2} S2​的任何元素均小于e&#xff0c;记 ∣ S ∣ |S| ∣S∣代表集合S元素的个数&…

C++ string字符串的使用和简单模拟实现

目录 前言 1. string简介 2. string的使用和简单模拟实现 2.1 string类的定义 2.2 string(),~string()和c_str() 2.2 size&#xff0c;重载符号[ ]&#xff0c;begin和end函数 2.3 push_back&#xff0c;reserve&#xff0c;append&#xff0c;运算符重载 2.4 insert和…

记录AE快捷键(持续补充中。。。)

记录AE快捷键 快捷键常用快捷键图层快捷键工具栏图层与属性常用指令视图菜单时间轴常规快捷键项目首选项功能摄像机操作 常用操作导入AI/PS工程文件加选一个关键参数快速回到上下一帧隐藏/显示图层关键帧拉长缩短关键帧按着鼠标左键不松手&#xff0c;在秒表那一列往下移动会都…

用CloudCompare软件拟合点云中的圆柱体

用CloudCompare软件拟合点云中的圆柱体 软件下载 点击下面的链接&#xff0c;进入下载页面&#xff1a; 下载页面 然后根据需要选择下载合适的软件版本。 一般选择windows installer版&#xff0c;如图所示&#xff1a; 下载完成后&#xff0c;安装并打开软件。软件的默认语…

重生奇迹MU圣导师简介

出生地&#xff1a;勇者大陆 性 别&#xff1a;男 擅 长&#xff1a;统率&宠物使用 转 职&#xff1a;祭师&#xff08;3转&#xff09; 介 绍&#xff1a;当玩家账号中有一个Lv250以上角色时&#xff0c;便可以创建职业为圣导师的新角色&#xff0c;圣导师每升一级获得…

经典电源电路基础(变压-整流-滤波-稳压)

1.电源电路的功能和组成 电子电路中的电源一般是低压直流电&#xff0c;先把220v交流电变换成低压直流电&#xff0c;再用整流电路变成脉动的直流电&#xff0c;最后用滤波电路滤除掉脉动直流中的交流成分后才能得到直流电。有的电子设备对电源的质量要求很高&#xff0c;所以…

MQTTfx连接阿里云(详细版)

1、介绍 作为物联网开放平台&#xff0c;阿里云可谓是吸引大多数嵌入式爱好者的平台。物联网MQTT协议火热的今天&#xff0c;你使用过阿里云吗&#xff1f;本篇文章带你接触阿里云&#xff0c;实现MQTT通信。 我们在测试MQTT之前先了解下什么是MQTT协议。大家都知道它是一种发…

【2024最新精简版】SpringBoot面试篇

文章目录 Spring和SpringBoot的区别 ?讲一讲SpringBoot自动装配的原理&#x1f44d;讲一讲SpringBoot启动流程你们常用的Starter有哪些&#x1f44d;如何定义一个SpringBoot的starter &#x1f44d;SpringBoot支持的配置文件有哪些&#x1f44d;讲一讲SpringBoot项目配置文件的…

用Python比较对象==与is,你还在用==?out啦,来看这个!

目录 1、基础比较:== 和 is 📐 1.1 ==:值的比较 1.2 id()函数揭秘对象身份 1.3 is:身份的辨识 1.4 实战演练:列表、字典的比较陷阱 列表比较陷阱 2、深入理解比较操作符 🌀 2.1 不等号的妙用 2.2 成员资格in操作 3、自定义比较:__eq__等魔法方法 🎩 3.1 重…

蓝队-溯源技巧

溯源技巧 大致思想 通常情况下&#xff0c;接到溯源任务时&#xff0c;获得的信息如下 攻击时间 攻击 IP 预警平台 攻击类型 恶意文件 受攻击域名/IP其中攻击 IP、攻击类型、恶意文件、攻击详情是溯源入手的点。 通过攻击类型分析攻击详情的请求包&#xff0c;看有没有攻击者…

1586. 扫地机器人

问题描述 Mike同学在为扫地机器人设计一个在矩形区域中行走的算法,Mike是这样设计的:先把机器人放在出发点 (1,1)(1,1) 点上,机器人在每个点上都会沿用如下的规则来判断下一个该去的点是哪里。规则:优先向右,如果向右不能走(比如:右侧出了矩形或者右侧扫过了)则尝试向…

分布式数据库核心问题和解决方法

当下&#xff0c;由于成本压力以及数据保护的要求&#xff0c;采用国产数据库的呼声越来越高&#xff0c;但是国产数据库数量众多&#xff0c;良莠不齐&#xff0c;没有选择数据库比较靠谱的标准&#xff0c;业内真正懂得数据库的人很少&#xff0c;且为了这块大的蛋糕&#xf…

《C语言深度解剖》(19):从头开始全面理解C语言指针和数组

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《C语言深度解剖》《精通C指针》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多C语言深度解剖点击专栏…

实例详解C/C++中static与extern关键字的使用

目录 1、概述 2、编译C++代码时遇到的变量及函数重复定义的问题 3、用 extern 声明外部变量 4、extern与全局函数 5、为何在变量和函数前添加一个static关键字编译就没问题了呢? 6、静态局部变量 7、函数的声明与定义都放置到一个头文件中,不使用static,通过宏控制去…

广东启动“粤企质量提升工作会议” 着力提升产品和服务质量

6月5日,由广东质量峰会组委会牵头,联合相关质量、信用、打假和检验检测等部门共同举办的“粤企质量提升工作会议”在广州正式启动。本次工作会议旨在贯彻落实《质量强国建设纲要》及《广东省质量强省建设纲要》精神,深入开展全民质量行动,弘扬企业家和工匠精神,营造政府重视质量…

实战17:GCN+LSTM图卷积神经网络预警预测 完整代码数据集

直接看视频演示: GCN+LSTM图卷积神经网络预警预测时间序列预测_哔哩哔哩_bilibili 模型图原理: 完整代码: import torch import torch.nn as nn import torch.optim as optim from torch_geometric.nn import GCNConv from torch.utils.data import DataLoader, TensorDat…

十二星座女、具有哪些情感特质。

白羊座&#xff08;奋不顾身&#xff09;。金牛座&#xff08;爱财如命&#xff09;。双子座&#xff08;灵活多变&#xff09;。 巨蟹座&#xff08;似水柔情&#xff09;。狮子座&#xff08;光明磊落&#xff09;。处女座&#xff08;尽善尽美&#xff09;。 天秤座&#xf…

【Unity学习笔记】第十八 基于物理引擎的日月地系统简单实现

转载请注明出处: https://blog.csdn.net/weixin_44013533/article/details/139701843 作者&#xff1a;CSDN|Ringleader| 目录 目标数学理论资源准备数据准备代码实现Unity准备效果展示注意事项后记 目标 目标&#xff1a;利用Unity的物理引擎实现 “日地月三体系统” 。 效果…

CubeMX Keil Configure

// 使用外部高速晶振 外部高速晶振为8M&#xff08;根据开发板上的晶振频率设置&#xff09;&#xff0c;使用 PLLCLK&#xff0c;HSE 选项 USART1 使用 Asynchronous&#xff0c;PA9&#xff0c;PA10引脚&#xff08;USART1 引脚根据开发板上引脚设置&#xff09; 设置MDK AR…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] URL拼接(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 URL拼接(100分) 🌍 评测功能需要订阅专栏后私信联系清隆解…