golang深度学习-基础篇

news2025/4/17 5:09:00

基础数据结构及类型

字符型-string

string 是Go标准库 buildin 内置的一个基础数据类型。string是由8比特字节的集合,通常不一定是UTF-8编码的文本。string可以为空(长度为0),但不会是nil。

string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.

在Go的实现中,string不包含内存空间,只有一个内存的指针,这样做的好处是string变得非常轻量,可以很方便的进行传递而不用担心内存拷贝。 因为string通常指向字符串字面量,而字符串字面量存储位置是只读段(静态存储区),而不是堆或栈上,所以才有了string不可修改的约定,但是可以通过切片间接修改内容。

string的本质

字符串的终止有两种方式,一种是C语言中的隐式申明,以字符“\0”作为终止符。一种是Go语言中的显式声明。Go语言运行时字符串string的表示结构如下。

type StringHeader struct {
    // 运行时代表
    Data unitptr // 指向底层的字符数组
	Len  int     // 字符串长度
}
type stringStruct struct {
   
    str unsafe.Pointer  // 字符串的首地址
    len int             // 字符串的长度
}

字符串在本质上是一串字符数组,每个字符在存储时都对应了一个或多个整数,这涉及字符集的编码方式。Go语言中所有的文件都采用UTF-8的编码方式,同时字符常量使用UTF-8的字符编码集。UFT-8是一种长度可变的编码方式,可包含世界上大部分的字符。 在Go语言中使用符文(rune)类型来表示和区分字符串中的“字符”,rune其实是int32的别称。当用range轮询字符串时,轮询的不再是单字节,而是具体的rune。

在标准库strings包中包含字符查找、分割、大小写转换、trim修剪等数十个函数。在标准库strconv包中,还包含很多字符串与其他类型进行转换的函数。

字符串拼接

当拼接后的s字符串小于32字节时,会有一个临时的缓存供其使用。当拼接后的字符串大于32字节时,会请求在堆区分配内存。
大于32字节时的具体过程:一个拼接语句的字符串编译时都会被存放到一个切片中,拼接过程需要遍历两次切片,第一次遍历获取总的字符串长度,据此申请内存,第二次遍历会把字符串逐个拷贝过去。即便有非常多的字符串需要拼接,性能上也有比较好的保证,因为新字符串的内存空间是一次分配完成的,所以性能消耗主要在拷贝数据上。

字符串与字节数组的转换

字节数组与字符串可以相互转换。b := []byte(a)c := string(b)
字节数组转换为字符串在运行时调用了slicebytetostring函数。字节数组与字符串的相互转换并不是简单的指针引用,而是涉及了复制。当字符串大于32字节时,还需要申请堆内存,因此在涉及一些密集的转换场景时,需要评估这种转换带来的性能损耗。

func slicebytetostring(buf *tmpBuf, ptr *byte, n int) (str string) {
   
    // ... 省略多余代码
	if n == 1 {
   
		p := unsafe.Pointer(&staticuint64s[*ptr])
		if goarch.BigEndian {
   
			p = add(p, 7)
		}
		stringStructOf(&str).str = p
		stringStructOf(&str).len = 1
		return
	}

	var p unsafe.Pointer
	if buf != nil && n <= len(buf) {
   
		p = unsafe.Pointer(buf)
	} else {
   
		p = mallocgc(uintptr(n), nil, false) // 分配内存
	}
	stringStructOf(&str).str = p
	stringStructOf(&str).len = n
	memmove(p, unsafe.Pointer(ptr), uintptr(n))
	return
}

当字符串转换为字节数组时,在运行时需要调用stringtoslicebyte函数,其和slicebytetostring函数非常类似,需要新的足够大小的内存空间。当字符串小于32字节时,可以直接使用缓存buf。当字符串大于32字节时,rawbyteslice函数需要向堆区申请足够的内存空间。最后使用copy函数完成内存复制。

func stringtoslicebyte(buf *tmpBuf, s string) []byte {
   
	var b []byte
	if buf != nil && len(s) <= len(buf) {
   
		*buf = tmpBuf{
   }
		b = buf[:len(s)]
	} else {
   
		b = rawbyteslice(len(s))    // 重新申请内存
	}
	copy(b, s)
	return b
}
// rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
func rawbyteslice(size int) (b []byte) {
   
    cap := roundupsize(uintptr(size))
    p := mallocgc(cap, nil, false)
    if cap != uintptr(size) {
   
        memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size))
    }
    *(*slice)(unsafe.Pointer(&b)) = slice{
   p, size, int(cap)}
    return
}

如果不希望发生内存拷贝,可以直接通过指针的方式完成转换。

// String2Bytes 高性能的转换 string 为 []byte
func String2Bytes(s string) []byte {
   
	x := (*[2]uintptr)(unsafe.Pointer(&s))
	h := [3]uintptr{
   x[0], x[1], x[1]}
	return *(*[]byte)(unsafe.Pointer(&h))
}

// Bytes2String 高性能的转换 []byte 为 string
// 比直接使用string(b)的性能强悍4-5倍
func Bytes2String(b []byte) string {
   
	return *(*string)(unsafe.Pointer(&b))
}

byte切片转换成string的场景很多,为了性能上的考虑,有时候只是临时需要字符串的场景下,byte切片转换成string时并不会拷贝内存,而是直接返回一个string,这个string的指针(string.str)指向切片的内存。如:

  1. 使用m[string(b)]来查找map(map是string为key,临时把切片b转成string);
  2. 字符串拼接,如”<” + “string(b)” + “>”;
  3. 字符串比较:string(b) == “foo”

数组

数组是一片连续的内存区域,其不能进行扩容、在复制和传递时为值复制。数组形如[n]T,在编译时就需要确定其长度和类型。当数组的长度小于4时,在运行时数组会被放置在栈中,当数组的长度大于4时,数组会被放置到内存的静态只读区。

使用未命名常量索引访问数组时,数组的一些简单越界错误能够在编译期间被发现。但是如果使用变量去访问数组或者字符串,编译器无法发现对应的错误,因为变量的值随时可能变化。数组访问索引异常(非整数、越界、负数)时会发生panic。

切片-slice

Slice又称动态数组,依托数组实现的长度可变的序列,可以方便的进行扩容、传递等,实际使用中比数组更灵活。

数据结构

一个切片在运行时由指针(date)、长度(len)和容量(cap)3部分构成。指针指向切片元素对应的底层数组元素的地址。长度对应切片中元素的数目,长度不能超过容量。容量一般是从切片的开始位置到底层数据的结尾位置的长度。
在这里插入图片描述

type slice struct {
   
    array unsafe.Pointer // 元素指针
    len   int // 长度 
    cap   int // 容量
}

在Go语言中,切片的复制其实也是值复制,但这里的值复制指对于运行时SliceHeader结构的复制。
底层指针仍然指向相同的底层数据的数组地址,因此可以理解为数据进行了引用传递。切片的这一特性使得即便切片中有大量数据,在复制时的成本也比较小,这与数组有显著的不同。
但实际上,切片可能分配在堆内存上,而数组会被分配到栈上。如果数组容量在相对较小的情况下可能要比切片的分配效率要高。

slice 和数组的区别

  • slice 的底层数据是数组,slice 是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。
  • 数组是定长的,长度定义好之后,不能再更改。在 Go 中,数组是不常见的,因为其长度是类型的一部分,限制了它的表达能力,比如 [3]int[4]int 就是不同的类型。
  • 当 slice 作为函数参数时,就是一个普通的结构体,会发生复制。尽管函数参数传递只有值传递没有引用传递,但如果改变了 slice 底层数组的数据,会反应到实参 slice 的底层数据。这是因为底层数据在 slice 结构体里是一个指针,仅管 slice 结构体自身不会被改变,但是通过指向底层数据的指针,可以改变切片的底层数据。

slice 的创建

  1. 直接声明:var slice []int,声明一个nil slice,长度和容量都为0,和nil比较的结果为true。
  2. new:slice := *new([]int),创建一个nil slice,长度和容量都为0,和nil比较的结果为true。
  3. 字面量:slice := []int{},声明一个empty slice,长度和容量都为0,和nil比较的结果为false(array指针有值)。
  4. 字面量:slice := []int{1,2,3,4,5},直接用初始化表达式创建切片。
  5. make:slice := make([]int, 0),初始化一个empty slice,长度和容量都为0,和nil比较的结果为false(array指针有值)。
  6. make:slice := make([]int, 5, 10),初始化一个slice,长度为5、容量为10。
  7. 从切片或数组“截取”:slice := array[1:5] 或 slice := sourceSlice[1:5]

nil 切片和空切片很相似,长度和容量都是0,官方建议尽量使用 nil 切片。
基于已有 slice 创建新 slice 对象,被称为 reslice。新 slice 和老 slice 共用底层数组,新老 slice 对底层数组的更改都会影响到彼此。基于数组创建的新 slice 对象也是同样的效果:对数组或 slice 元素作的更改都会影响到彼此。 但如果因为执行 append 操作使得新 slice 底层数组扩容,移动到了新的位置,两者就不会相互影响了。

扩容机制

使用 append 函数可以向切片中追加元素。append 函数的参数长度可变,因此可以追加多个值到 slice 中,还可以用 ... 传入 slice,直接追加一个切片。append函数返回值是一个新的slice,Go编译器不允许调用了 append 函数后不使用返回值。

// append 函数
func append(slice []Type, elems ...Type) []Type
// 追加元素
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

使用 append 可以向 slice 追加元素,实际上是往底层数组添加元素。但是底层数组的长度是固定的,如果索引len-1所指向的元素已经是底层数组的最后一个元素,就会触发扩容机制。扩容扩大的容量都是针对原来的容量而言的,而不是针对原来数组的长度而言的。

扩容的具体思路是:当原 slice 容量小于 阈值(256,不同版本阈值不同,旧本版是1024) 的时候,新 slice 容量变成原来的 2 倍;原 slice 容量超过 阈值,新 slice 容量按照1.25倍的速度依次递增,直到最终容量大于等于新申请的容量。之后会根据内存分配策略进行内存对齐roundupsize函数,因此最终扩容的容量总是要 大于等于 老 slice 容量的 2倍 或者1.25倍。

func growslice(et *_type, old slice, cap int) slice {
   
	// ... 省略无关代码
	// 发生了溢出
	if cap < old.cap {
    panic(errorString("growslice: cap out of range")) }
    // 容量为0的切片要扩容,直接生成一个新的切片返回
	if et.size == 0 {
    return slice{
   unsafe.Pointer(&zerobase), old.len, cap} }

	// 扩容机制
	newcap := old.cap
	doublecap := newcap + newcap // 2倍
	if cap > doublecap {
   
		newcap = cap
	} else {
   
		const threshold = 256 // 不同版本下,阈值不同,此处Go1.18是256,旧版本是1024。
		if old.cap < threshold {
   
			newcap = doublecap
		} else {
   
			// 防止溢出和死循环
			for 0 < newcap && newcap < cap {
   
				// 按0.25倍的逐渐向上加,知道大于目标容量为止
				newcap += (newcap + 3*threshold) / 4
			}
			// 再次防止溢出
			if newcap <= 0 {
    newcap = cap }
		}
	}

	// 计算新的切片的容量,长度。会对 newcap 作一个内存对齐操作( roundupsize 函数),计算方式和内存分配策略相关。
	// 因为需要保证申请内存后不浪费空间,因此,进行内存对齐之后,新 slice 的容量是要 大于等于 老 slice 容量的 2倍或者1.25倍。
	
	// ... 省略大部分代码,仅保留核心代码
	capmem = roundupsize(uintptr(newcap))
    newcap = int(capmem<

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

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

相关文章

win11安装wsl报错:无法解析服务器的名称或地址(启用wsl2)

1. 启用wsl报错如下 # 查看可安装的 wsl --install wsl --list --online此原因是因为没有开启DNS的原因&#xff0c;所以需要我们手动开启DNS。 2. 按照如下配置即可 Google的DNS&#xff08;8.8.8.8和8.8.4.4) 全国通用DNS地址 (114.114.114.114) 3. 运行以下命令来重启 WSL…

技术总结 | MySQL面试知识点

存储引擎 Mysql 中的存储引擎 查询存储引擎的命令 show engines; Archive 只支持 insert 与select操作, 不支持索引 不支持事务 适用于存储需要长期保存,但是很少访问的数据,例如 历史日志 BlackHole 不存储数据,但是会记录写入操作 适用于性能测试 语言验证等情况 MyISAM…

Linux下Ollama下载安装速度过慢的解决方法

问题描述&#xff1a;在Linux下使用默认安装指令安装Ollama&#xff0c;下载安装速度过慢&#xff0c;进度条进度缓慢&#xff0c;一直处于Downloading Linux amd64 bundle中&#xff0c;具体如下图所示&#xff1a; 其中&#xff0c;默认的Ollama Linux端安装指令如下&#xf…

关于redis的主从复制(下)

目录 全量复制 关于replid和runid 部分复制 补充问题 实时复制 psync可以从主节点获取全量数据&#xff0c;也可以获取一部分数据。主要就是看offset的进度&#xff0c;如果offset写作-1&#xff0c;就是获取全量数据。offset写具体的正整数&#xff0c;则是从当前偏移量位…

uniapp uni.request重复请求处理

类似这种切换tab时&#xff0c;如果操作很快并且网络不太好&#xff0c;就出现数据错乱&#xff0c;在网上查了一圈&#xff0c;有一个使用uview拦截处理的&#xff0c;但是原生uni.requse没有找到详细的解决办法&#xff0c;就查到使用 abort 方法&#xff0c;我自己封装了一个…

【大模型】DeepSeek:AI浪潮中的破局者

【大模型】DeepSeek&#xff1a;AI浪潮中的破局者 引言&#xff1a;AI 新时代的弄潮儿DeepSeek&#xff1a;横空出世展锋芒&#xff08;一&#xff09;诞生背景与发展历程&#xff08;二&#xff09;全球影响力初显 探秘 DeepSeek 的技术内核&#xff08;一&#xff09;独特的模…

SOME/IP--协议英文原文讲解8

前言 SOME/IP协议越来越多的用于汽车电子行业中&#xff0c;关于协议详细完全的中文资料却没有&#xff0c;所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块&#xff1a; 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 4.2 Speci…

用PyInstaller构建动态脚本执行器:嵌入式Python解释器与模块打包 - 简明教程

技术场景&#xff1a; 需分发的Python工具要求终端用户可动态修改执行逻辑将Python环境与指定库&#xff08;如NumPy/Pandas&#xff09;嵌入可执行文件实现"一次打包&#xff0c;动态扩展"的轻量化解决方案。 ▌ 架构设计原理 1. 双模运行时识别 # 核心判断逻辑…

在做题中学习(89):螺旋矩阵

解法&#xff1a;模拟 思路&#xff1a;创建ret数组&#xff0c;用变量标记原矩阵的行数和列数&#xff0c;遍历一个元素就push_back进ret数组&#xff0c;每次遍历完一行或一列&#xff0c;相应行/列数--&#xff0c;进行顺时针螺旋遍历到为0即可。 细节&#xff1a;要有边界…

从零搭建微服务项目Base(第5章——SpringBoot项目LogBack日志配置+Feign使用)

前言&#xff1a; 本章主要在原有项目上添加了日志配置&#xff0c;对SpringBoot默认的logback的配置进行了自定义修改&#xff0c;并详细阐述了xml文件配置要点&#xff08;只对日志配置感兴趣的小伙伴可选择直接跳到第三节&#xff09;&#xff0c;并使用Feign代替原有RestT…

【数据分析】通过个体和遗址层面的遗传相关性网络分析

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍原理应用场景加载R包数据下载函数个体层面的遗传相关性网络分析导入数据数据预处理构建遗传相关性的个体网络对个体网络Nij进行可视化评估和选择最佳模型评估和选择最佳模型最佳模型…

在 macOS 的 ARM 架构上按住 Command (⌘) + Shift + .(点)。这将暂时显示隐藏文件和文件夹。

在 macOS 的 ARM 架构&#xff08;如 M1/M2 系列的 Mac&#xff09;上&#xff0c;设置 Finder&#xff08;访达&#xff09;来显示隐藏文件夹的步骤如下&#xff1a; 使用快捷键临时显示隐藏文件&#xff1a; 在Finder中按住 Command (⌘) Shift .&#xff08;点&#xff…

【产品经理】需求分析方法论+实践

阐述了需求分析的基本认知&#xff0c;包括需求分析的定义、原则和内容。接着&#xff0c;文章详细介绍了需求分析的十个步骤&#xff0c;从收集需求到结果评审&#xff0c;为产品经理提供了清晰的操作指南。 作为产品经理&#xff0c;需求分析是一个最基本的工作&#xff0c;但…

Windows平台的小工具,功能实用!

今天给大家分享一款超实用的Windows平台监控工具&#xff0c;堪称“桌面小管家”&#xff0c;能帮你轻松掌握电脑的各种运行状态&#xff0c;比如网速、下载速度、内存和CPU占用率等常用参数&#xff0c;让你的电脑运行情况一目了然。 TrafficMonitor 网速监控悬浮窗软件 这款…

SAP-工单技术性关闭操作手册

文章目录 单个工单批量处理TECO和CLSD标识的区别 单个工单 事务代码CO02&#xff0c;输入工单号后回车 功能-》限制处理-》技术性完成 工单状态更改 撤销TECO操作 CO02输入工单号&#xff0c;功能-》限制处理-》撤销技术性完成 批量处理 事务代码COHV&#xff0c;点击生…

Aseprite绘画流程案例(1)——画相机图标

原图&#xff1a; 步骤一&#xff1a;打开需要参照的图标 步骤二&#xff1a;将参照的图片拖放到右边&#xff0c;作为参考 步骤三&#xff1a;新建24x24的画布&#xff0c;背景为白色的画布 步骤四&#xff1a;点击菜单栏——视图——显示——像素网格&#xff08;如果画布已经…

安装海康威视相机SDK后,catkin_make其他项目时,出现“libusb_set_option”错误的解决方法

硬件&#xff1a;雷神MIX G139H047LD 工控机 系统&#xff1a;ubuntu20.04 之前运行某项目时&#xff0c;处于正常状态。后来由于要使用海康威视工业相机&#xff08;型号&#xff1a;MV-CA013-21UC&#xff09;&#xff0c;便下载了并安装了该相机的SDK&#xff0c;之后运行…

云计算架构学习之Ansible-playbook实战、Ansible-流程控制、Ansible-字典循环-roles角色

一、Ansible-playbook实战 1.Ansible-playbook安装软件 bash #编写yml [rootansible ansible]# cat wget.yml - hosts: backup tasks: - name: Install wget yum: name: wget state: present #检查playbook的语法 [rootansible ansible]…

网络工程师 (47)QOS

一、概念与原理 QOS即服务质量&#xff08;Quality of Service&#xff09;是一种网络技术&#xff0c;用于管理和保证网络中不同类型的质量和性能。它通过设置优先级和带宽限制等策略&#xff0c;确保关键应用&#xff08;如视频会议、语音通信&#xff09;的数据包能够在网络…

风铃摇晃的弧度与不安等长

晴&#xff0c;2025年2月19日 的确是&#xff0c;有依靠又有谁会去自己打伞。是啊&#xff0c;有时候生活推着我们走的样子确实挺无力的。不过谁都愿意携手走的&#xff0c;希望有一天再也不用“抛头露面”了吧。 又下载回了 X &#xff0c;马上 Gork 3 可以使用&#xff0c…