zoukankan      html  css  js  c++  java
  • golang -- 字符串就地取反

    字符串

    定义

    在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 	”,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	%c
    ", 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:gopathsrcprojectlearnchapeter2str.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:gopathsrcprojectlearnchapeter2str.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:gopathsrcprojectlearnchapeter2str.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的字符串类型的变量有一个直观的认识。

  • 相关阅读:
    剑指offer——最小的K个数和数组中第K大的元素
    Leetcode刷题指南链接整理
    160. Intersection of Two Linked Lists
    100. Same Tree
    92. Reverse Linked List II
    94. Binary Tree Inorder Traversal
    79. Word Search
    78,90,Subsets,46,47,Permutations,39,40 DFS 大合集
    0x16 Tire之最大的异或对
    0x16 Tire
  • 原文地址:https://www.cnblogs.com/cnblogs-wangzhipeng/p/10577212.html
Copyright © 2011-2022 走看看