[已解决] Golang中如何获取20个字符的字符串长度
[已解决] Golang中如何获取20个字符的字符串长度 我想创建一个结构体,其中包含两个字段,字符串长度为20个字符。
例如
type Name struct { firstName, lastName string }
我一直在寻找任何可以将 firstName 和 lastName 的字符串值修剪为20个字符长度的方法,任何想法都将非常有帮助!
太棒了,伙计们,感谢你们的回复,特别是关于基准测试的部分!😊
更多关于[已解决] Golang中如何获取20个字符的字符串长度的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我也学到了新东西,之前不知道有多字节字符,我想这解释了rune的用途。
还有一点需要注意。你不能使用 len 函数,因为它统计的是字节数。你必须使用:
utf8.RuneCountInString(firstName)
我从这里获得了新的灵感,并完成了 exstrings.Reverse 的性能改进,这让我感觉非常好。
它确实能工作,但对于码值大于127的任何字符则无效。它还可能破坏多字节字符,导致字符串末尾出现无效字符。
请查看此链接:https://play.golang.org/p/uVu1egklEuL
func main() {
fmt.Println("hello world")
}
是否引用原始字符串取决于您的目的,使用不当可能导致内存泄漏。
@thinkeridea,您是否有支持这一观点的案例研究/细节或示例?我对Go中的字节操作感到有些紧张。看起来我们将来可能无法使用字节,而只能使用符文。
字节操作指的是操作二进制/十六进制数据流。据我理解,符文是多字节字符(因此按字节切片切割不等于按字符切割)。
thinkeridea:
我尝试比较了以下两种方法的性能:
哇,这个发现很棒。 👍 以下是来自 Linux amd64 的基准测试结果:
goos: linux
goarch: amd64
pkg: gosandbox
BenchmarkSubStrA-8 857563 1297 ns/op
BenchmarkSubStrB-8 10494722 114 ns/op
BenchmarkSubStrC-8 8502182 142 ns/op
PASS
ok gosandbox 3.786s
thinkeridea:
我们仍需关注此问题并在必要时处理,但不宜过于频繁,极端情况很少发生。
thinkeridea:
程序中是使用
string还是[]byte需要根据数据来源和处理数据的函数来决定,一定要减少类型转换Translation: whether the program is using
stringor[]byte, it must be decided based on on the source codes and the data processing, with the aim to reduce as many data type conversion as possible.
我明白了。内存泄漏就是这样出现的。
既然如此,只要我不涉及实际的 rune(例如处理 utf8 字符),我仍然可以安全地使用 []byte 操作。否则,我需要将其升级为 []rune。
我使用 []byte 处理的一些情况是 I/O 二进制流数据处理(例如 SPI/I2C)。
谢谢!
一种简单的方法是将这些字段作为私有变量,并提供 getter 和 setter 接口函数。示例:
const (
maxLength = 20
)
type Name struct {
first string
last string
}
func (n *Name) Set(firstName string, lastName string) {
n.first = firstName
n.last = lastName
if len(firstName) > maxLength {
n.first = firstName[:maxLength]
}
if len(lastName) > maxLength {
n.last = lastName[:maxLength]
}
}
func (n *Name) GetWithFirst(withFirstName bool) string {
if withFirstName {
return n.last + ", " + n.first
}
return n.last
}
然后,你可以这样调用该结构体:
func main() {
l := &Name{}
l.Set("Dumitru Margareta Corneliu Leopold Blanca Karol Aeon Ignatius Raphael Maria Niketas A. Shilage", "Mihaly")
s := l.GetWithFirst(true)
fmt.Printf("My name is: %s\n", s)
}
// 输出:
// My name is: Mihaly, Dumitru Margareta Co
是的。这对我来说也是一次大开眼界的经历!以下是应用齐寅解决方案的最新代码:
// 贡献者:Chew, Kean Ho (Holloway), Johan Dahl, Qi Yin
//
// 主程序用于将名字修剪至20个字符
package main
import (
"fmt"
"unicode/utf8"
)
const (
maxLength = 20
)
type Name struct {
first string
last string
}
func (n *Name) trim(s string, length int) string {
var size, x int
for i := 0; i < length && x < len(s); i++ {
_, size = utf8.DecodeRuneInString(s[x:])
x += size
}
return s[:x]
}
func (n *Name) Set(firstName string, lastName string) {
n.first = n.trim(firstName, maxLength)
n.last = n.trim(lastName, maxLength)
}
func (n *Name) GetWithFirst(withFirstName bool) string {
if withFirstName {
return n.last + ", " + n.first
}
return n.last
}
func main() {
l := &Name{}
l.Set("Dumitru Margareta Ἄγγελος Leopold Blanca Karol Aeon Ignatius Raphael Maria Niketas A. Shilage", "Mihaly")
s := l.GetWithFirst(true)
fmt.Printf("My name is: %s\n", s)
}
// 输出:
// My name is: Mihaly, Dumitru Margareta Ἄγ
它也可能破坏多字节字符,导致字符串末尾出现无效字符。
感谢指出!我忽略了符文层面的处理,这是我的疏忽。学到了新东西。😅
以下是新改进的代码:Go Playground - The Go Programming Language
// Cotributed by: Chew, Kean Ho (Holloway), Johan Dahl
//
// main program is about trimming names to 20 characters
package main
import (
"fmt"
)
const (
maxLength = 20
)
type Name struct {
first string
last string
}
func (n *Name) Set(firstName string, lastName string) {
var rs []rune
n.first = firstName
if len(firstName) > maxLength {
rs = []rune(firstName)
n.first = string(rs[:maxLength])
}
n.last = lastName
if len(lastName) > maxLength {
rs = []rune(lastName)
n.last = string(rs[:maxLength])
}
}
func (n *Name) GetWithFirst(withFirstName bool) string {
if withFirstName {
return n.last + ", " + n.first
}
return n.last
}
func main() {
l := &Name{}
l.Set("Dumitru Margareta Ἄγγελος Leopold Blanca Karol Aeon Ignatius Raphael Maria Niketas A. Shilage", "Mihaly")
s := l.GetWithFirst(true)
fmt.Printf("My name is: %s\n", s)
}
// Output:
// My name is: Mihaly, Dumitru Margareta Ἄγ
这是一个以切片方式截取字符串的简单示例。实际上,并没有发生内存拷贝,只是创建了一个引用。标准库中的函数也经常以这种方式截断字符串。
这种方法简单高效,因为不需要分配新的内存,通常非常有效。
然而,如果原始字符串的生命周期非常短但字符串本身非常大,而获取的子字符串生命周期足够长但字符串本身非常短,那么GC将无法在子字符串的生命周期内释放原始字符串中不再使用的内存,这会导致内存泄漏。
大多数实际程序中存在这样的问题,但通常并不那么难以忍受,只是轻微且短期的问题,在不追求极致性能的情况下可以忽略。
我们仍然应该注意这个问题,并在必要时处理它,但不必过于频繁,因为极端情况很少发生。
这里有一篇关于字符串的中文介绍,可能会有所帮助:string 优化误区及建议
package main
import (
"fmt"
"reflect"
"strings"
"unsafe"
)
func main() {
s := strings.Repeat("A", 2000)
s1 := s[5:10]
fmt.Println((*reflect.StringHeader)(unsafe.Pointer(&s)).Data) // 824634433536
fmt.Println((*reflect.StringHeader)(unsafe.Pointer(&s1)).Data) // 824634433541
// 将 s 字符串类型更改为 []byte 不会产生重复,只是以字节的方式解析存储在 s 变量内存中的字符串数据
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bs := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}))
// 修改一段数据,这段数据被 s1 引用。
copy(bs[5:10], strings.Repeat("B", 5))
// 发现 s1 的数据已更改。
fmt.Println(s1) // BBBBB
}
你不能使用
len,它计算的是字节数。你必须使用…
明白了。len(..) 和 rune 计数的区别非常大。运行示例:Go Playground - The Go Programming Language
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Ἄγγελος"
x := len(s)
y := utf8.RuneCountInString(s)
fmt.Printf("length: x=%v y=%v \n" , x, y)
}
// Output:
// length: x=15 y=7
我也学到了新东西,之前不知道有多字节字符,这解释了 rune 的用途。
我也是。很高兴我也学到了新东西! 😄
更新后的代码如下:
// Cotributed by: Chew, Kean Ho (Holloway), Johan Dahl
//
// main program is about trimming names to 20 characters
package main
import (
"fmt"
"unicode/utf8"
)
const (
maxLength = 20
)
type Name struct {
first string
last string
}
func (n *Name) Set(firstName string, lastName string) {
var rs []rune
n.first = firstName
if utf8.RuneCountInString(firstName) > maxLength {
rs = []rune(firstName)
n.first = string(rs[:maxLength])
}
n.last = lastName
if utf8.RuneCountInString(lastName) > maxLength {
rs = []rune(lastName)
n.last = string(rs[:maxLength])
}
}
func (n *Name) GetWithFirst(withFirstName bool) string {
if withFirstName {
return n.last + ", " + n.first
}
return n.last
}
func main() {
l := &Name{}
l.Set("Dumitru Margareta Ἄγγελος Leopold Blanca Karol Aeon Ignatius Raphael Maria Niketas A. Shilage", "Mihaly")
s := l.GetWithFirst(true)
fmt.Printf("My name is: %s\n", s)
}
// Output:
// My name is: Mihaly, Dumitru Margareta Ἄγ
我认为我们应该提供一种更好的字符串截断方法。 我尝试比较了以下两种方法的性能:
import (
"testing"
"unicode/utf8"
"unsafe"
)
func SubStrA(s string, length int) string {
if utf8.RuneCountInString(s) > length {
rs := []rune(s)
return string(rs[:length])
}
return s
}
func SubStrB(s string, length int) string {
var size, n int
for i := 0; i < length && n < len(s); i++ {
_, size = utf8.DecodeRuneInString(s[n:])
n += size
}
return s[:n]
}
func SubStrC(s string, length int) string {
var size, n int
for i := 0; i < length && n < len(s); i++ {
_, size = utf8.DecodeRuneInString(s[n:])
n += size
}
b := make([]byte, n)
copy(b, s[:n])
return *(*string)(unsafe.Pointer(&b))
}
var s = "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。"
func BenchmarkSubStrA(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrA(s, 20)
}
}
func BenchmarkSubStrB(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrB(s, 20)
}
}
func BenchmarkSubStrC(b *testing.B) {
for i := 0; i < b.N; i++ {
SubStrC(s, 20)
}
}
goos: darwin
goarch: amd64
BenchmarkSubStrA-8 745708 1624 ns/op 336 B/op 2 allocs/op
BenchmarkSubStrB-8 9568920 122 ns/op 0 B/op 0 allocs/op
BenchmarkSubStrC-8 7274718 157 ns/op 48 B/op 1 allocs/op
PASS
ok command-line-arguments 4.782s
它们的性能差距巨大。是否引用原始字符串取决于你的目的,使用不当可能导致内存泄漏。
SubStrC 尽管分配了子字符串,仍然会有近十倍的性能差距。
在Go中,你可以使用字符串切片或utf8.RuneCountInString结合切片来确保字符串不超过20个字符。以下是两种实现方式:
1. 使用字节切片(适用于ASCII字符)
如果字符串只包含ASCII字符,可以直接使用切片操作:
type Name struct {
firstName, lastName string
}
func (n *Name) SetFirstName(s string) {
if len(s) > 20 {
n.firstName = s[:20]
} else {
n.firstName = s
}
}
func (n *Name) SetLastName(s string) {
if len(s) > 20 {
n.lastName = s[:20]
} else {
n.lastName = s
}
}
2. 使用rune切片(支持Unicode字符)
对于包含多字节字符的字符串,应该使用rune切片:
import "unicode/utf8"
type Name struct {
firstName, lastName string
}
func truncateTo20Chars(s string) string {
if utf8.RuneCountInString(s) > 20 {
runes := []rune(s)
return string(runes[:20])
}
return s
}
func (n *Name) SetFirstName(s string) {
n.firstName = truncateTo20Chars(s)
}
func (n *Name) SetLastName(s string) {
n.lastName = truncateTo20Chars(s)
}
3. 在结构体初始化时处理
你也可以在创建结构体时直接处理字符串:
func NewName(firstName, lastName string) Name {
return Name{
firstName: truncateTo20Chars(firstName),
lastName: truncateTo20Chars(lastName),
}
}
4. 使用自定义类型和验证
创建一个自定义字符串类型并添加验证方法:
type LimitedString string
func NewLimitedString(s string) LimitedString {
runes := []rune(s)
if len(runes) > 20 {
return LimitedString(string(runes[:20]))
}
return LimitedString(s)
}
type Name struct {
firstName, lastName LimitedString
}
// 使用示例
name := Name{
firstName: NewLimitedString("这是一个很长的名字,需要被截断"),
lastName: NewLimitedString("短名"),
}
选择哪种方法取决于你的具体需求。如果只需要处理ASCII字符,第一种方法最简单高效;如果需要处理Unicode字符,第二种方法更安全。



我将在24小时后标记为已解决,也许其他人会有其他的解决方案。