字符串

定义

在golang中字符串是一种不可变的字节序列,它可以包含任意的数据,包括0值字节,但主要是人类可以阅读的文本。golang中默认字符串被解读为utf-8编码的Unicode码点(文字符号)序列。

特性

golang中字符串具有不可变性。例如

str := "hello 世界!"

str[0] = 'L'

这种写法会引起编译错误:str[0] 不可赋值

字符串支持类似数组中分片的引用写法:


fmt.Println(str[:5]) // 输出 hello fmt.Println(str[7:]) // 输出 世界 fmt.Println(str[len(s)+1:) // 宕机

str[i:j] , 当i、j 越界 (j 、i < 0 或 j、i > len(str) )或 j < i 时会发生宕机。

str := "Hello"

t := str

str += "world"

这种写法可以通过,虽然str指向了一个新的字符串“Hello world”,但t指向的旧字符串仍然存在。

不可变意味着两个字符串能够安全的共享同一段底层内存,是的复制任何长度字符串的开销都低廉,类似的字符串s及其子串(s[n:])字符串的复制也开销低廉。

常见问题

  1. 顺序输出字符串中的每一个字符。

这个问题乍一看十分的简单,直接遍历就好了:


str := "Hello 世界!" for i := 0; i < len(str) ; i++{
fmt.Printf(“%c \t”,str[i])
}

然而事实没那么简单,其输出结果如下:

H	e	l	l	o	 	ä	¸	­	å	›	½	ï	¼	

中文字符部分全部为乱码。这与utf-8的编码规则有关, utf-8是以字节为单位对unicode码点进行变长编码。每一个文字符号用14个字节表示,ASCII字符仅仅占1字节的内存,其他常用的文书符号会占到23个字节。一个字符编码的首字节高位指明后面还有多少个字节:

规则 表示范围 说明
0xxxxxxx 文字符号0~127 Ascii 字符
110xxxxx 10xxxxxx 128~2047 少于128个未使用的值
1110xxxxx 10xxxxxx 10xxxxxx 2048~65535 少于2048个未使用的值
1110xxxxx 10xxxxxx 10xxxxxx 10xxxxxx 65536~0x10ffffff 其他未使用值

在上面提到的例子里面Hello子串中的字符为Ascii字符,占用一个字节, 而 世界这两个符号占用的字符为 3 个,所以遍历的时候会出现乱码的情况。

我们这里换一种写法:

for i , v := range str {
fmt.Printf("%d\t%c\n", i , v)
}

其显示结果如下:

0	H
1 e
2 l
3 l
4 o
5
6 中
9 国
12 !

以上代码正常的输出了每一个字符包括中文字符。为什么使用range会成功? 因为range在循环的同时进行了隐式的解码。其中 i 表示该字符在字符串中起始的下角标,v要重点说一下,它表示的是字符对应的unicode 点码,在golang中它有一个专门的变量类型 rune (文字符号) ,它是int32类型的别名。在golang中int占用内存大小取决于操作系统底层和计算机硬件,但int32一定是占用 4 bytes ,rune类型在打印输出的时候使用“%c”。

有了能遍历输出的函数自然很容易的就可以写出取反函数:

func Reverse(str string)(res string)  {
for _, v := range str {
res = string(v) + res
} return
}

分析和优化

我们测试一下这个函数的性能:

func BenchmarkReverse1(b *testing.B) {
tStr := "Hello 中国!"
for i := 0; i < b.N; i++{
Reverse(tStr)
}
}

作者在winx10/arm64 的操作系统中进行测试,cpu 为core i3,内存为 4g(硬件设施比较老旧了),最后得出的分析结果如下:

goos: windows
goarch: amd64
pkg: project/learn/chapeter2
BenchmarkReverse1-4 2000000 788 ns/op
PASS ROUTINE ======================== project/learn/chapeter2.Reverse1 in D:\gopath\src\project\learn\chapeter2\str.go 250ms 2.25s (flat, cum) 94.54% of Total
. . 14:
. . 15: res = string(rnStr)
. . 16: return
. . 17:}
. . 18:
20ms 20ms 19:func Reverse1(str string)(res string) {
. . 20:
80ms 110ms 21: for _, v := range str {
150ms 2.12s 22: res = string(v) + res
. . 23: //res = fmt.Sprintf("%c%s", v ,res)
. . 24: }
. . 25:
. . 26: return
. . 27:}

可以看见最耗时的操作就是res 重新赋值的部分,此时有两种情况:1、res字符串执行 + 操作很费时; 2、进行字符转化的时候费时,我们把代码调整一下:

func Reverse(str string)(res string)  {
for _, v := range str {
temp := string(v)
res = temp + res
} return
}

性能测试结果如下

ROUTINE ======================== project/learn/chapeter2.Reverse1 in D:\gopath\src\project\learn\chapeter2\str.go

 230ms      2.31s (flat, cum) 93.90% of Total
. . 14:
. . 15: res = string(rnStr)
. . 16: return
. . 17:}
. . 18:
10ms 10ms 19:func Reverse1(str string)(res string) {
90ms 210ms 20: for _, v := range str {
30ms 200ms 21: temp := string(v)
90ms 1.88s 22: res = temp + res
. . 23:
. . 24: }
. . 25:
10ms 10ms 26: return
. . 27:}
. . 28:

可见res 执行 + 操作要更费时一些,在执行+操作的过程中,要经历 字符串拷贝、底层字节数组内存重新分配(可能被触发)。

优化的思路很简单,创建一片‘缓存’,用来存储字符串对应的字节数据,最后再统一转化为字符串。

func Reverse(str string)(res string)  {
i:=0
cache := make([]byte, len(str)) for _, v := range str {
i += utf8.RuneLen(v)
utf8.EncodeRune(cache[len(str) - i:], v)
} res = string(cache)
return
}

执行结果如下:

goos: windows
goarch: amd64
pkg: project/learn/chapeter2
BenchmarkReverse2-4 5000000 253 ns/op
PASS
ok project/learn/chapeter2 1.831s ROUTINE ======================== project/learn/chapeter2.Reverse2 in D:\gopath\src\project\learn\chapeter2\str.go 510ms 1.45s (flat, cum) 90.62% of Total
. . 29:
20ms 20ms 30:func Reverse2(str string)(res string) {
. . 31: i:=0
20ms 210ms 32: cache := make([]byte, len(str))
. . 33:
280ms 460ms 34: for _, v := range str {
50ms 90ms 35: i += utf8.RuneLen(v)
100ms 250ms 36: utf8.EncodeRune(cache[len(str) - i:], v)
. . 37: }
. . 38:
20ms 400ms 39: res = string(cache)
20ms 20ms 40: return
. . 41:}

优化率接近68%。从以上过程我们可以对golang的字符串类型的变量有一个直观的认识。

golang -- 字符串就地取反的更多相关文章

  1. PHP中关于位运算符 与 或 异或 取反

    <?php /** * author:LMS * createTime:2015/07/22 * desctiption:位运算[ & | ^ ~ ] * 与&:如果a.b两个值 ...

  2. iOS 十六进制的相加取反

    ios中将NSstring字符串转换成char类型 NSString *string = [NSString stringWithFormat:@"5D"]; const char ...

  3. GO语言的进阶之路-Golang字符串处理以及文件操作

    GO语言的进阶之路-Golang字符串处理以及文件操作 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们都知道Golang是一门强类型的语言,相比Python在处理一些并发问题也 ...

  4. [转+自]关于PHP7的新特性(涉及取反和disabled_functions绕过)

    PHP7和PHP5上的安全区别 preg_replace()不再支持/e修饰符 利用\e修饰符执行代码的后门大家也用了不少了,具体看官方的这段描述: 如果设置了这个被弃用的修饰符, preg_repl ...

  5. 第11.20节 Python 中正则表达式的扩展功能:后视断言、后视取反

    一. 引言 在<第11.19节 Python 中正则表达式的扩展功能:前视断言和前视取反>中老猿介绍了前视断言和前视取反,与二者对应的还有后视断言和后视取反. 二. (?<=-)后视 ...

  6. 第11.19节 Python 中正则表达式的扩展功能:前视断言和前视取反

    一. 引言 在<第11.16节 Python正则元字符"()"(小括号)与组(group)匹配模式>中老猿介绍了组匹配模式的命名组功能及引用组功能,这两者都是组模式的扩 ...

  7. Golang字符串是否存在于切片或数组中的小工具(基本等同于python in语法)

    // golang中是没有判断字符串是否存在数组或切片里的内置方法, 所以我们可以自己搞一个快速小组件 func Find(slice []string, val string) (int, bool ...

  8. C的数值取反操作

    今儿在代码中发现一句"return x? ~0 : 0;"对~0这个取反操作相关的知识又还给老师了.一查,查到下面一道题,弄过来贴上. //-------------------- ...

  9. HDU 3911 线段树区间合并、异或取反操作

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=3911 线段树区间合并的题目,解释一下代码中声明数组的作用: m1是区间内连续1的最长长度,m0是区间内连续 ...

随机推荐

  1. Anaconda 安装和使用

    最近看了些关于数据分析的书,想系统的整理下相关知识,算是学习笔记吧,也希望能帮到初学者. 1.Anaconda介绍 安装python的方法有很多种,数据分析方面比较常用Anaconda.Anacond ...

  2. SDWC补题计划

    2018的寒假去了SD的冬令营,因为一班二班难度悬殊,对我很不友好,几乎什么也没学会,但是我把两个班的课件都存了下来,现在慢慢把两个班的例题以及课后题都补一补(毕竟冬令营的钱不能白花). 这些题目横跨 ...

  3. 理解JavaScript继承(三)

    理解JavaScript继承(三) 通过把父对象的属性,全部拷贝给子对象,也能实现继承. 7.浅拷贝 function extendCopy(p) { var o = {}; for (var pro ...

  4. Python读文件报错:SyntaxError: Non-ASCII character in file

    打开city.py文件时报错 问题原因: 程序中的编码错误,python默认是acii模式,没有支持utf8.如果代码中有汉字 ,就会报错 解决方案: 源代码文件(city.py)第一行添加:#cod ...

  5. HDU2544(dijkstra_邻接矩阵最水的题没有之一)

    https://cn.vjudge.net/problem/HDU-2544 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场 ...

  6. ios 开发UI篇—UITextView

    概述 UITextView可滚动的多行文本区域 UITextView支持使用自定义样式信息显示文本,并支持文本编辑.您通常使用文本视图来显示多行文本,例如在显示大型文本文档的正文时. UITextVi ...

  7. linux学习第十七天(NFS、AUTOFS文件共享配置,DNS配置)

    一.NFS(网络文件系统,实现linux系统上文件共享) 服务器配置 yum install nfs-utils  (安装NFS软件包) iptables -F  (清空防火墙) service ip ...

  8. 【NoSql】之Hbase

    Hbase概述 ·  Hbase是构建在hdfs上的分布式列式存储系统 ·  Hbase内部管理的文件全部存储在HDFS上面, ·  Hbase是基于google bigtable 模型开发的,典型的 ...

  9. 利用Dropbox中转自己的C2

    利用Dropbox中转自己的C2 0x00 前言 本来的目的呢,是利用dropbox中转自己的c2达到免杀的效果,然后可能是看了文章太久没抓住机会复现,到最后发现已经失效了,会被360拦截,所以好像也 ...

  10. Matlab中的“prod”函数

    B = prod(A)将A矩阵不同维的元素的乘积返回到矩阵B. 如果A是向量,prod(A)返回A向量的乘积.如果A是矩阵,prod(A)将A看作列向量,返回每一列元素的乘积并组成一个行向量B.如果A ...