notes
  • notes
  • codes
    • 安卓脚本
    • redis入门指南
    • js 原型链
    • 如何发布npm
    • go字符串
    • redis
    • this指向
    • go1.13
    • go by example
    • hook
    • go指南 - 官网
    • git基本操作
  • vim
  • training
    • 9月19日 ubc讲座
    • zby
      • DAY 3 :硬拉 | ZBY
      • DAY 2:卧推 | ZBY
      • DAY 1:深蹲 | ZBY
      • DAY 4 :计划 | ZBY
    • 汉唐
    • 周旋康复课
  • book notes
    • 思考致富
    • 邓普顿教你逆向投资
    • 阮琦
    • 魔鬼经济学
    • 网络是怎样连接的
    • 好奇心
    • 魔鬼约会学5.0
    • 股票作手回忆录
    • 漫步华尔街
    • 码农翻身
    • 十分钟速成课 - 哲学
    • 魔鬼答疑
    • 大话数据结构
    • 魔鬼约会学笔记
    • 算法图解
  • 狼人杀
  • 图书馆
  • typora
  • imovie
Powered by GitBook
On this page
  • go怎么存字符串的?
  • unicode和utf-8
  • 如何获取我们以为的长度

Was this helpful?

  1. codes

go字符串

Previous如何发布npmNextredis

Last updated 5 years ago

Was this helpful?

go操作字符串,会遇到一个很反直觉的事情:看起来是字母,但拿出来的却是数字。

比如下面这段代码:

str := "1a帅"
for i, elem := range str {
  fmt.Println("elem", elem, fmt.Sprintf("%T", elem))
  fmt.Println("i", str[i], fmt.Sprintf("%T", str[i]))
}
fmt.Println("len(str)", len(str))

elem 49 int32 i 49 uint8 elem 97 int32 i 97 uint8 elem 24069 int32 i 229 uint8 len(str) 5

你看,用for/range拿出来的字符串,每个elem都是一串数字,类型是int32。

但是你改用str[i]拿出来,类型就变成了uint8

数字和英文拿出来的结果都是一样的,1对应49,a对应97

但中文的结果却不一样,帅用elem拿对应的是24069,用str[i]拿对应的却是229

而且看起来这个str只有三位,打印出来的长度却是5。

很反直觉不是么?把我看得满脸问号🤪

go怎么存字符串的?

从中可以知道,gloang中的string,底层是通过byte数组实现的。换言之,string就是[]byte。

byte是啥呢?byte,就是这里的uint8。就是用str[i]取出来的数据类型

比如这里1对应49(00110001),a对应97(01100001),用的就是ASCII码的翻译规则。

那么问题来了,存字符串用byte,为什么用elem取出来的数据类型是int32呢?为什么取出来的时候还要特地转一下数据类型呢?

这个问题和中文的两个取值结果不同有关。

英语用128个符号编码就够了,但其他语言就不够。尤其汉字有10w多,只用一个字节最多表示256种符号,因此要用多个字节来表达一个中文字。

于是产生了unicode的编码方法,相当于一个大字典,全世界的符号都有独一无二的编码了。

比如帅的unicode是5e05,对应二进制(1011110 00000101),对应10进制就是我们输出的24069。

unicode和utf-8

那么问题来了,帅的unicode明显只有两个byte啊,5e和05。英文和数字各1个字节,那么总长度应该是4,为什么我们输出的长度是5呢?

unicode只是用来找二进制对应的字符,用于翻译的。但用这个来存储和传输,并不方便。

既然中文需要两个byte,那么怎么区分英文和中文呢?因此大家都要存两个字节。这样英文和数字就很不划算。

比如用unicode存,就需要6个byte

字节1

字节2

十进制

1

00000000

00110001

49

a

00000000

01100001

97

帅

01011110

00000101

94 05

因此就出现了一个方便unicode存储和传输的方法:utf-8

用utf-8存,只要5个字节

字节1

字节2

字节3

十进制

1

00110001

49

a

01100001

97

帅

11100101

10111000

10000101

229 184 133

这就是为什么我们用s[i]取出来的是229,因为一共有5位,我们只取了第3位,而第三位就是229.

这就是为什么我们字符串的长度是5,因为最底层是用utf-8存的。

go把存着的utf-8编码的一个个byte取出来,翻译成unicode,根据这个unicode找到对应的字符,最终显示出来给我们看。

换言之,我们看到的字母、数字、汉字都是幻觉,本质上我们操纵的都是这一位位的rune,或者说是int32

这也就解释了为什么elem取出来的时候,需要转一下数据类型。因为我们操作的是这个rune,这个unicode翻译出来的字符。不转一个elem只能取出1/3的“帅”了😂😂(其实会输出乱码)

s[i]和len()是一起的,操作的是最底层的一个个byte。

如何获取我们以为的长度

答案是把字符串(本质为[]byte类型),转成[]rune类型

str := "1a帅"

rune := []rune(str)
byte := []byte(str)

fmt.Println("rune", rune, len(rune))
fmt.Println("byte", byte, len(byte))

rune [49 97 24069] 3 byte [49 97 229 184 133] 5

当然也可以用utf8这个包:

// 通过utf8这个包
fmt.Println("RuneCountInString:", utf8.RuneCountInString(str))

从中可以知道,计算机最底层,是用1和0来存东西的。但0和1就两个二进制位(bit),也就只能代表两个东西,肯定不够。

于是就用8个0或者1,组成一个字节(byte),用一个byte来代表字母或数字。这个组成的规则叫。

utf-8里面,汉字是三个字节,因为它,让计算机能知道,这个汉字从哪开始到哪结束。

image-20190907144741901

那么问题来了,我们该?

这篇文章
这篇
ASCII码
使用了一个方法
如何获取字符串“我们以为的长度”