go关于string与[]byte再学深一点

news2024/11/24 0:39:31

目标:充分理解string与[]bytes零拷贝转换的实现

先回顾下string与[]byte的基本知识

1. string与[]byte的数据结构

reflect包中关于字符串的数据结构

// StringHeader is the runtime representation of a string.

type StringHeader struct {

   Data uintptr

   Len int

}

Data指向的是某个数组的首地址

len代表数组的长度。

uintptr是一种特殊指针,下文会具体介绍

说明

  • string是一个8位的byte的集合,通常代表utf-8文本(但不一定都是)
  • string可以为empty但不能是nil
  • string的值是不能改变的(因为底层是数组)

reflect包中关于[]bytes的数据结构

type SliceHeader struct {

    Data uintptr

    Len int

    Cap int

}

Data指向的就是byte数组

[]byte是一个指向byte类型数组的slice

可以看到stringStruct与slice区别是cap,说明[]bytes的值是可变的,因为底层是切片。而string的值是不能改变的(因为底层是数组)

2. 基本数据结构的空间大小以及内存对齐

bl := true

fmt.Println("size of bool:", unsafe.Sizeof(bl))// 1

i := 10

fmt.Println("size of int:", unsafe.Sizeof(i)) // 8

i32 := int32(10)

fmt.Println("size of int32:", unsafe.Sizeof(i32)) // 4

i64 := int64(10)

fmt.Println("size of int64:", unsafe.Sizeof(i64)) // 8

str := "xxx"

fmt.Println("size of str:", unsafe.Sizeof(str)) // 16



type xstruct struct {

  a bool

  b int32

  c string

}

xx := xstruct{true, 10, "hello"}



fmt.Println("size of xx.a:", unsafe.Sizeof(xx.a)) // 1

fmt.Println("size of xx.b:", unsafe.Sizeof(xx.b)) // 4

fmt.Println("size of xx.c:", unsafe.Sizeof(xx.c)) // 16

fmt.Println("size of xx:", unsafe.Sizeof(xx)) // 不是1+4+16=21,而是24,为什么呢?由于字节对齐

// xx.a为一个字节,而实际存储时会占用一个"对齐系数"也就是8字节(对于64位机器,"对齐系数"是8字节);

// xx.b为四个字节,所以1+4=5,放一个对齐系数(8字节),其余剩余部分用0补充

// xx.c为16个字节,刚好放满2个对齐系数(16字节)。所以8+16=24字节

从代码中总结常见类型变量占用空间:

bool占1个字节

int32占4个字节

int与int64 占4字节(64位机器)

string占16个字节,其中包含2部分,第一部分unsafe.pointer占8字节,第二部分len int占8字节

struct结构体占用的空间,计算时需要考虑字节对齐,字节对齐的好处是减少cpu访问memory的此次,cpu读取memory最小单位是一个字长(8字节),从而提供访问内存性能。(具体细节请问google)

3. unsafe.pointer与uintptr

string与[]byte结构的定义出现了uintptr,并且unsafe.pointer通常用于类型转换,下面具体介绍2中指针类型。

  • unsafe.pointer与uintptr都是指针,但又不是普通指针,经查阅指针分为三种类型,分别有:

1. *类型: 这是最常用的指针,名叫普通指针类型,用于传递对象地址,不能进行指针运算。

2. unsafe.Pointer:通用指针类型,用于转换不同类型的指针,不能进行指针运算,不能读取内存存储的值(必须转换到某一类型的普通指针)。

3. uintptr:用于指针运算. 本质是存储 `指针地址` 的int类型

  • unsafe.Pointer类型有四个重要描述:

(1)任何类型的指针都可以被转化为Pointer

(2)Pointer可以被转化为任何类型的指针

(3)uintptr可以被转化为Pointer

(4)Pointer可以被转化为uintptr

简而言之,unsafe.Pointer可以实现指针类型的转换,uintptr用于指针计算

下面看看Pointer的内部结构:

type Pointer *ArbitraryType


// ArbitraryType is here for the purposes of documentation only and is not actually

// part of the unsafe package. It represents the type of an arbitrary Go expression.

type ArbitraryType int

ArbitraryType是int的一个别名,在Go中对ArbitraryType赋予特殊的意义。代表一个任意Go表达式类型。

  • unsafe.Pointer的使用示例:
value1 := int32(10)

value2 := int64(12)



p := &value1

fmt.Println(reflect.TypeOf(p)) // *int32

fmt.Println(*p) // 10



//p = &value2 // 错误。 无法将p指向&value2地址,因为p是*int32类型,&value2是*ini64类型

unsPtr := unsafe.Pointer(&value2) // 将*int64先转换为unsafe.Pointer类型指针,此时unsPtr指向&value2(也就是value2的地址)

p = (*int32)(unsPtr) //转换为*int32指针类型

fmt.Println(reflect.TypeOf(p)) // *int32

fmt.Println(*p) // 11
  • uintptr的使用示例
type student struct {

name string

age uint8

}

s := student{

name: "tom", // string类型,16字节

age: 18, // int8类型,1字节

}



uptr := (uintptr)(unsafe.Pointer(&s)) // uptr指向结构体的首地址

uptr = uptr + 16 // uptr移动16字节,指向age的地址

age := *(*int8)(unsafe.Pointer(uptr)) // 转换为*int8类型的指针

fmt.Printf("age=%d\n", age) // 18

  • 对uinitptr与unsafe.Pointer有简单了解后,再看结构体内存对齐示例
type student struct {

name string

age uint8

city string

}

s := student{

name: "tom", // string类型,16字节

age: 18, // int8类型,1字节; 需要做内存对齐,独占一个字长,本身占一个字节,其余7个字节填充

city: "shenzhen", // string类型,16字节

}

结构体的内存空间占用情况:

代码验证结构体占用空间的总大小,以及每个成员占用空间大小:

// 结构体占用空间

fmt.Println("size of s:", unsafe.Sizeof(s)) // 40


// 计算变量的地址

fmt.Printf("address of s.name: %p\n", &s.name) // 0xc00008c030--->转换为十进制824634294320

fmt.Printf("address of s.age: %p\n", &s.age) // 0xc00008c040--->转换为十进制824634294336

fmt.Printf("address of s.city: %p\n", &s.city) // 0xc00008c048--->转换为十进制824634294344


// 打印内部变量的相对字符串首地址的偏移量

fmt.Printf("offset of s.age:%d\n", unsafe.Offsetof(s.name)) // 0

fmt.Printf("offset of s.age:%d\n", unsafe.Offsetof(s.age)) // 16

fmt.Printf("offset of s.age:%d\n", unsafe.Offsetof(s.city)) // 24,计算方法是24=16+8

下面我们把s字符串再进一步“打开”,探索一下字符串内部

先将s字符串转换为*reflect.StringHeader, 并查看字符串内部Data,Len的值


x := (*reflect.StringHeader)(unsafe.Pointer(&s)) // 转换为*reflect.StringHeader

fmt.Printf("x.Data: %v\n", x.Data) // 17603737,这就是Data变量保存的具体值,其实是一个内存地址

fmt.Println("type of x.Data:", reflect.TypeOf(x.Data)) // uintptr

fmt.Printf("&x.Data: %p\n", &x.Data) // 0xc000100030

fmt.Printf("x.Len: %v\n", x.Len) // 3, 字符串"tom"的长度

fmt.Printf("&x.Len: %p\n", &x.Len) // 0xc000100038, 说明38-30=8,表示x.Data占8个字节

  • 使用uintptr,计算出x.city的地址,再获取改地址的值
uptr := (uintptr)(unsafe.Pointer(&s)) // uptr指向结构体的首地址

uptr = uptr + 16 + 8 // uptr移动16+8字节,指向address的地址

city := *(*string)(unsafe.Pointer(uptr)) // 转换为*string类型的指针,再用*获取改地址的值,也就是x.city的值

fmt.Printf("city=%s\n", city) // shenzhen

4.string与[]bytes零拷贝的实现

最后,有了基础知识后,我们再看看string与[]bytes零拷贝的实现

string转换为[]byte

// 内存零拷贝方式类型转换
func stringtobyte(s string) []byte {
	// &s转换为*reflect.StringHeader
	var sptr *reflect.StringHeader
	sptr = (*reflect.StringHeader)(unsafe.Pointer(&s))

	var b []byte
	// &b转换为*reflect.SliceHeader
	bptr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	// 填充*reflect.SliceHeader内的Data,Len,Cap
	bptr.Data = sptr.Data
	bptr.Len = sptr.Len
	bptr.Cap = sptr.Len
	return b
}

为了编译理解,将转换过去用图示表示

[]byte转换为string

// 内存零拷贝方式类型转换
func bytetostring(b []byte) string {
	var bptr *reflect.SliceHeader
	bptr = (*reflect.SliceHeader)(unsafe.Pointer(&b))

	var s string
	sptr := (*reflect.StringHeader)(unsafe.Pointer(&s))
	sptr.Data = bptr.Data
	sptr.Len = bptr.Len

	return s
}

// 转换方法二

// 转换方法二
func String2Bytes(s string) []byte {
	sh := (*[2]uintptr)(unsafe.Pointer(&s))
	bh := [3]uintptr{sh[0], sh[1], sh[1]}
	return *(*[]byte)(unsafe.Pointer(&bh))
}

func String2Bytes2(s string) []byte {
	// &s转换为unsafe.Pointer类型的指针,再转换为指向定长为2的uintptr数组的指针
	sh := (*[3]uintptr)(unsafe.Pointer(&s))
	// sh是一个指针,不能直接转换为*[]byte的指针,先转换为unsafe.Pointer,再转换为*[]byte指针;最后*取出指针指向的内容
	return *(*[]byte)(unsafe.Pointer(sh))
}

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

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

相关文章

浅谈断言之XML断言

浅谈断言之XML断言 XML断言是JMeter的一个组件,用于验证请求的响应数据是否符合XML结构。这对于测试返回XML格式数据的Web服务特别有用。 如何添加XML断言? 要在JMeter测试计划中添加XML断言,遵循以下步骤: 打开测试计划&…

JCR一区级 | Matlab实现CPO-Transformer-LSTM多变量回归预测【2024新算法】

JCR一区级 | Matlab实现CPO-Transformer-LSTM多变量回归预测【2024新算法】 目录 JCR一区级 | Matlab实现CPO-Transformer-LSTM多变量回归预测【2024新算法】效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.【JCR一区级】Matlab实现CPO-Transformer-LSTM多变量回归预测…

基于STM32F103的FreeRTOS系列(二)·多任务系统

基于STM32F103的FreeRTOS系列(一)单片机设计模式介绍裸机程序的设计模式-CSDN博客 目录 1. 多任务模式 2. 互斥操作 3. 同步操作 1. 多任务模式 对于裸机程序,无论使用哪种模式进行精心的设计,在最差的情况下都无法解决这个…

12. Hibernate 模板设计模式

1. 前言 本节课和大家一起使用模板设计模式重构 Hibernate 操作流程,通过本节课程内容,你将了解到: 如何运用模板设计模式重构 Hibernate 操作流程;持久化对象与序列化接口; 2. 模板设计模式 学习 Hibernate 的过程…

服务器上使用Docker部署sonarQube,并集成到Jenkins实现自动化。

目标是要在目标服务器上使用docker工具部署好sonar环境,然后再集成到Jenkins中实现自动化的代码审查工作。 Docker 首先Dokcer的源大部分现在都用不了,于是我上网查询,终于找到了一个可用的镜像。 编辑/etc/docker/daemon.json文件&#x…

71.PLC Settings for OPCSERVER(KEPWare)- SAP ME实施

目录 0.目的 1.三菱PLCMitsubishi Ethernet 1.1 型号FX-3U的配置 选择Operational settings 按下图设置通讯参数 选择Open settings 按下图设置通讯端口 选择Router ralay parameter 按下图设置网关 1.2型号Q Series 按下图设置IP、网关 按下图设置端口…

WebGoC题解(13) 狐猬编程:GoC L4 结业测试 第4题 找木柴

题目描述 小明今天找了n跟木柴,但是木柴太多了,小明只能拿走m根木柴,小明希望拿走的木柴都是剩下的木柴中最长的,小明还画出以下图形 例如 输入 5 3 10 20 30 40 50 小明要拿走30 40 50 这3根木柴 从大到小画出以下图形 矩形的宽…

AWS监控工具,监控性能指标

执行AWS监视是为了跟踪在AWS环境中主动运行的应用程序工作负载和资源,AWS监视器跟踪各种AWS云指标,以帮助提高在其上运行的应用程序的整体性能。 借助阈值突破警报系统,AWS应用程序监控在识别性能瓶颈来源方面起着至关重要的作用&#xff0c…

46 AP-AC实战图示

一 流程 一 无线上WEB页面 1 创建vlan 56 [AC-KongZhi]vlan 56 2 退出 [AC-KongZhi-vlan56]quit 3 进入vlan三层口 配置IP地址 [AC-KongZhi]interface Vlan-interface 56 [AC-KongZhi-Vlan-interface56]ip address 192.168.56.55 24 4 在AC控制器与Host主机的接口上能通关vl…

Adobe国际认证详解-动漫制作专业就业方向和前景

动漫制作专业的就业方向和前景随着创意产业的蓬勃发展而愈发广阔。这一专业涵盖了从角色设计、场景绘制到动画制作、特效合成等多个环节,是创意与技术相结合的典型代表。随着数字媒体和互联网的普及,动漫制作专业人才的需求正不断增长,为该专…

Chrome v8 pwn 前置

文章目录 参考用到啥再更新啥简介环境搭建depot_tools和ninjaturbolizer 调试turbolizer使用结构数组 ArrayArrayBufferDataViewWASMJSObject结构Hidden Class命名属性-快速属性Fast Properties命名属性-慢速属性Slow Properties 或 字典模式Dictionary Mode编号属性 (Elements…

redis的使用场景和持久化方式

redis的使用场景 热点数据的缓存。热点:频繁读取的数据。限时任务的操作:短信验证码。完成session共享的问题完成分布式锁。 redis的持久化方式 什么是持久化:把内存中的数据存储到磁盘的过程,同时也可以把磁盘中的数据加载到内存…

Ubuntu 24.04 LTS Noble安装 FileZilla Server

FileZilla Server 是一款使用图形用户界面快速创建 FTP 服务器的软件。它有助于测试需要 FTP 服务器功能的各种项目。虽然早期的 FileZilla FTP 服务器仅适用于 Windows 和 macOS,但现在我们也可以在 Linux(例如 Ubuntu 24.04)上安装 FileZil…

C++ | Leetcode C++题解之第274题H指数

题目&#xff1a; 题解&#xff1a; class Solution { public:int hIndex(vector<int>& citations) {int left0,rightcitations.size();int mid0,cnt0;while(left<right){// 1 防止死循环mid(leftright1)>>1;cnt0;for(int i0;i<citations.size();i){if(…

英伟达、Mistral AI 开源企业级大模型,120亿参数、可商用

全球AI领导者英伟达&#xff08;Nvidia&#xff09;和著名开源大模型平台Mistral.ai联合开源了&#xff0c;企业级大模型Mistral NeMo 12B。&#xff08;以下简称“MN 12B”&#xff09; 据悉&#xff0c;MN 12B一共有基础和指令微调两种模型&#xff0c;支持128K上下文长度&a…

vue3.0学习笔记(二)——生命周期与响应式数据(ref,reactive,toRef,toRefs函数)

1. 组合API-setup函数 使用细节&#xff1a; setup 是一个新的组件选项&#xff0c;作为组件中使用组合API的起点。从组件生命周期来看&#xff0c;它的执行在组件实例创建之前vue2.x的beforeCreate执行。这就意味着在setup函数中 this 还不是组件实例&#xff0c;this 此时是…

Linux、Windows和macOS上使用Telnet

文章目录 LinuxWindowsmacOS 在Linux、Windows和macOS上使用Telnet时&#xff0c;不同的系统有不同的工具和设置方法。以下是在这些系统上使用Telnet的简要说明&#xff1a; Linux 在Linux上&#xff0c;Telnet通常是通过telnet命令来使用的。首先&#xff0c;你需要确保你的系…

【等保测评】服务器——Windows server 2012 R2

文章目录 **身份鉴别****访问控制****安全审计****入侵防范****恶意代码防范****可信验证****测评常用命令** Windows服务器安全计算环境测评 测评对象&#xff1a;Windows server 2012 R2 身份鉴别 &#xff08;高风险&#xff09;应对登录的用户进行身份标识和鉴别&#x…

Mysql注意事项(二)

Mysql注意事项&#xff08;二&#xff09; 最近回顾了一下MySQL&#xff0c;发现了一些MySQL需要注意的事项&#xff0c;同时也作为学习笔记&#xff0c;记录下来。—2020年06月11日 接上一篇Mysql注意事项&#xff08;一&#xff09; 9、分组数据 GROUP BY 规定&#xff…

数据库对象中出现复杂的对象嵌套,如何使用Mybatis plus优雅的解决这个问题:

起因 类原型&#xff1a; 在User类&#xff1a; package com.itheima.mp.domain.po;import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import java.time…