Go源码--Strings库

1. 简介

strings库 存储了 一些针对 字符串的具体操作 其 代码短小精悍 可以学习到很多编程的思路 尤其是 涉及到字符串使用性能的方面,其源码库有好多的优秀案例可以学习。向强者对齐不一定成为强者,但向弱者对齐一定变为弱者。
介绍思路是先介绍 strings 库的一些基础 结构体和函数,它们被其它函数调用,然后挑选几个比较有代表性的函数介绍,下面开始吧

2. Strings.Builder 结构体

Builder结构体主要是用来处理字符串的拼接的,它底层采用了 append的 形式,可以大幅度减少内存的开销。至少比 + 要快很多,尤其在for循环里进行字符串拼接, + 每次都要 进行内存分配,效率低下,但是append 时 内存分配时倍数增长的,内存copy次数要少很多。
下面我们来看下 Builder源码

type Builder struct {
	addr *Builder // 检测是否有 Builder 值传递
	buf  []byte  // 字节数组 用来 拼接字符串 采用 append 形式
}

我们来挑选几个代表性的 函数来说明下

2.1 WriteString(s string) (int, error) 函数
func (b *Builder) WriteString(s string) (int, error) {
	b.copyCheck()  // 检查 Builder 是否是值传递 例如 是否 将 c:=b 然后 调用 c.WriteString ,之所以不允许这样 主要还是考虑 避免内存复制 保证 Builder 的执行效率
	b.buf = append(b.buf, s...) // 将 s 拆分成 byte 类型 添加到 buf中
	return len(s), nil  // 返回 长度
}

其中 b.copyCheck() 代码说明 如下

func (b *Builder) copyCheck() {
	if b.addr == nil {
		// This hack works around a failing of Go's escape analysis
		// that was causing b to escape and be heap allocated.
		// See issue 23382.
		// TODO: once issue 7921 is fixed, this should be reverted to
		// just "b.addr = b"
		b.addr = (*Builder)(noescape(unsafe.Pointer(b)))    // 赋值操作,且 保证 b空间 一直在 栈上 ,避免逃逸分析;调用 unsafe.Pointer 进行类型强转 保证效率 不内存复制,但不保证安全。
	} else if b.addr != b {// 避免 Builder 值赋值 ,例如不允许 执行  c:=b c.WriteString(...)这种操作 ,否则 panic
		panic("strings: illegal use of non-zero Builder copied by value")
	}
}

2.2 Grow(n int) 函数

todo

2.3 String() 函数

用来 将 b.buf 中的字节数组 转换成字符串

func (b *Builder) String() string {
	return unsafe.String(unsafe.SliceData(b.buf), len(b.buf))  // 采用 无安全包 将 内存缓冲区的 字符数组直接转换为string 无需 内存复制 ps: string([]byte) 会产生一次内存复制
}

3. Strings.Index(s, substr string)int 函数

Index 函数是其他函数的基础函数 需首先介绍,其代码如下

func Index(s, substr string) int {
	n := len(substr)
	switch {
	case n == 0:
		return 0
	case n == 1:
		return IndexByte(s, substr[0])
	case n == len(s):
		if substr == s {
			return 0
		}
		return -1
	case n > len(s):
		return -1
	case n <= bytealg.MaxLen:   // 当子串长度小于等于阈值63时 执行如下操作
		// Use brute force when s and substr both are small
		if len(s) <= bytealg.MaxBruteForce {  // 当 len(s) 小于阈值时 直接调用 bytealg 的IndexString()方法来 计算 也就是 当 s 和 substr 都比较小的时候可直接调用 底层函数
			                                    // bytealg库不对用户开放,它比直接调原生的c库和go语言编写更加的效率高。                 
			                                    //bytealg.IndexString 是采用汇编直接跟底层编译器通信 来编写的 查找索引的函数 速度更快。
			return bytealg.IndexString(s, substr)
		}
		c0 := substr[0]  // c0: 子串第一个字符
		c1 := substr[1]  // c1: 子串第二个字符 用于 进行多一层比较后 在进行字符串比较时 可以提升查询效率
		i := 0 // 字符串s参与比较的 起始索引,初始化为0
		t := len(s) - n + 1 // 最大比较次数
		fails := 0 // 失败次数 也就是 for循环执行的次数
		for i < t {
			if s[i] != c0 { // 如果某次比较的  s 起始索引对应字符 s[i]跟 子串第一个字符不相等,走这里
				// IndexByte is faster than bytealg.IndexString, so use it as long as
				// we're not getting lots of false positives.
				o := IndexByte(s[i+1:t], c0)  // 调用底层汇编函数 查找 字符 c0 在 s[i+1:t] 中第一次出现的 位置 起始位置从 i+1 开始查找,大家思考下为什么
				if o < 0 { // 没找到 直接返回 -1 循环退出
					return -1
				}
				i += o + 1  // 找到后更新 i的 位置为 当前找到的 c0 索引 起始位置,这是 是s[i]==c0
			}
			if s[i+1] == c1 && s[i:i+n] == substr {  // 走到这里证明 s[i]==c0 这时 先不急与 比较 s[i:i+n] == substr 而是 再对 需要比较的两个子串的 第二个字符进行比较 这样 如果不相等 就不用后续 字符串比较了
				                                     // 因为字符串比较 比字符比较 速度上低一个数量级 所以 如果不相等 则可以节省大量时间 如果相等 则 多出的比较时间 
				                                     // 可以忽略不计 或者说 整体代价也是值得接受的 之所以不进行 第三个字符比较 可能也是为了找一个效率平衡点吧
				return i
			}
			fails++   // 走到这里 证明 上面没匹配到 这次比较 失败 失败次数+1 
			i++       // 能走到这里 证明 s[i]==c0 只是 没命中子串而已 所以 需要后移一位接着 开始比较
			// Switch to bytealg.IndexString when IndexByte produces too many false positives.
			if fails > bytealg.Cutover(i) {   // 如果失败次数达到了阈值 再次以 s[i:] 为 基础 调用 bytealg.IndexString(...)函数 开始寻找 
				r := bytealg.IndexString(s[i:], substr) 
				if r >= 0 {
					return r + i
				}
				return -1
			}
		}
		return -1
	}
	

	c0 := substr[0]   // 下方代码跟 上面循环一样 只是 如果 子串 长度 >63时 执行如下操作 这时 也是先按上面循环执行逻辑 查找
	                 
	c1 := substr[1]
	i := 0
	t := len(s) - n + 1
	fails := 0
	for i < t {
		if s[i] != c0 {
			o := IndexByte(s[i+1:t], c0)
			if o < 0 {
				return -1
			}
			i += o + 1
		}
		if s[i+1] == c1 && s[i:i+n] == substr {
			return i
		}
		i++
		fails++
		if fails >= 4+i>>4 && i < t {  // 翻译一下  fails >= 4+i/16 && i < t
			// See comment in ../bytes/bytes.go.
			j := bytealg.IndexRabinKarp(s[i:], substr)  // 开始执行 Rabin-Karp 算法 简单来说 就是 子串太长了 需要 将 子串 取 hash 使它 缩短 然后进行匹配,感兴趣的可以了解下这个算法
			if j < 0 {
				return -1
			}
			return i + j
		}
	}
	return -1
}

可以看到 Index函数 会 根据 匹配字符串长度 子串长度 和 匹配次数 来综合考虑 使用那种算法 来进行 匹配,一般场景 使用 bytealg.IndexString(s, substr) 就可以了,二班三班的比较特殊。

4. Strings.Split(s, sep string) []string 函数

Split函数 如下

func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }

可以看到其 是 genSplit()函数的特殊情况,我们来介绍下 genSplit函数

// split 的 核心函数,s 需要分裂的字符串, sep分裂种子,sepSave 是需要在原始结果a[i]上额外多保存此子串后sepSave个字符,  n是需要分裂成几个函数 注意 文中会进行修正
func genSplit(s, sep string, sepSave, n int) []string {
	if n == 0 {     
		return nil
	}
	if sep == "" {   // 子串为0时 计算字符串中 元数据的个数 
		return explode(s, n)  
	}
	if n < 0 {
		n = Count(s, sep) + 1   // n是 s  最多被 sep  分为 几个子串 ,计算逻辑是 计算s中 sep的个数 +1,大家思考下为啥
	}

	if n > len(s)+1 {   // 修正 n ,如果传递的 n过大 会创建 多余空间,造成浪费
		n = len(s) + 1
	}
	a := make([]string, n)  // a 为结果集 ,初始化 空间大小是 n
	n--  // n-- 是 对结果的前 n-1个 可以使用 for 循环处理 最后一个 就直接是 截断后剩余的 s了 直接赋值,例如 s=="akjkl" sep="k" 则 2次循环后 已经 break  了 这时 s=="l" 直接赋值给a[i]就行
	i := 0  // a数组索引,计算a[i]值
	for i < n {   // 寻找 a 的 最多前 n-1个值  sepSave 一般为0 ,当不为0时,表示需要额外获取m后sepSave个 字节  放入 a中,不确定这种使用场景,不做分析。 
		m := Index(s, sep) // 计算 m 索引
		if m < 0 { // 查找不到 break
			break
		}
		a[i] = s[:m+sepSave] //为 a的第 i个索引 赋值 一般是是s[:m]
		s = s[m+len(sep):] // 截取s 现在可以知道 s[i:j] 为啥是 i是可取 j是不可取了吧 这种左闭右开的思路 正是为这种迭代情况设计的
		i++  
	}
	a[i] = s // 将最后截断后的 s赋值给 a[i] 注意 这是 i<=n
	return a[:i+1]  // 返回 值 ,这是对a的截断,因为不一定全部存储满
}

5. Strings.Join(elems []string, sep string) string 函数

Join 函数底层 也是调用的 Builder 结构体相关函数 大体思路是 先计算 Join后形成的新字符串需要的空间 然后分配空间 再 调用 WriteString(…)函数 append进 数组 然后用 不安全 指针 强转成 string
我们来简单梳理下

func Join(elems []string, sep string) string {
	switch len(elems) {  // 特殊情况处理
	case 0:
		return ""
	case 1:
		return elems[0]
	}

	var n int
	if len(sep) > 0 {  // 计算 引入的 sep需要的空间
		if len(sep) >= maxInt/(len(elems)-1) {
			panic("strings: Join output length overflow")
		}
		n += len(sep) * (len(elems) - 1)
	}
	for _, elem := range elems {   // 计算 总的空间
		if len(elem) > maxInt-n {
			panic("strings: Join output length overflow")
		}
		n += len(elem)
	}

	var b Builder
	b.Grow(n)   // 初始化空间
	b.WriteString(elems[0]) // 将第一个先写入 则 后续可以用for 循环一次搞定;for循环也可以从0 开始 ,最后在把 elems[len(elems)-1]写入缓存区
	for _, s := range elems[1:] {
		b.WriteString(sep)
		b.WriteString(s)
	}
	return b.String() // 一次性强转 byte[]-->string 没有使用多余空间,底层强转
}

6. Strings.TrimRight(s, sep string) []string 函数

这个函数 利用了 位图的思想来计算 s 中是否包含字符,位图的思想其实 就是将 一些 字符等 通过 一定的映射算法 把它 映射到 字节码数组的行 行列上 其位置置为1 且是独有的位置。举个简单地例子如下:

在这里插入图片描述

如上图 字符 ‘a’,‘b’ 通过 映射函数 获得坐标(0,3),(1,4)这个数组中的 相应为置为 1,规则是 行坐标确定索引,列坐标确定左移个数。则可以获得一个set集, 这样,要查找某字符在不在是,只要计算出坐标 查看其时是否是1就行了 (&运算)。举个例子:

字符’c’ 经过映射 发现其 行坐标是 1 则取出 set 中的 值 二进制表示是 00001000 ,列坐标假设是 2,则 我们执行 1<<2 得到 00000010,则
00001000&00000010=0 则证明,'c’不在set中。
介绍完了 位图思想 我们来看下 在源码中的应用

TrimRight源码 如下

// TrimRight 删除字符串 s 右侧 在 cutset集合中的字符
func TrimRight(s, cutset string) string {
	if s == "" || cutset == "" {
		return s
	}
	if len(cutset) == 1 && cutset[0] < utf8.RuneSelf {  // 如果 cutset 长度为1 则从右边开始 挨个匹配 直到 不是 cutset为止,然后返回。这里直接直接字符比较就行
		return trimRightByte(s, cutset[0])
	}
	if as, ok := makeASCIISet(cutset); ok { // 如果所有字符都是 ascii码<127的 字符, cutset映射成位图set集 
		return trimRightASCII(s, &as) // 从最后边开始 挨个字符查看是否正在set集中,直到不在为止,然后返回。
	}
	return trimRightUnicode(s, cutset)  // 如果cutset中存在非ascii字符 例如 中文 走这里。不是本文重点,且使用概率较小,感兴趣的可以看看。
}

其中 TrimRight 中的 makeASCIISet 生成位图集的源码如下


// makeASCIISet 采用位图的思想 将 chars 对应set坐标 置为 1
func makeASCIISet(chars string) (as asciiSet, ok bool) { // as 长度为8且一个字符占 4字节(uint32)可映射 32字符, 但是 ascii 只有 128位字符 所以 只会 使用 索引 0-3,索引 4-7是保留空间,应该是留着后续扩展的。
	for i := 0; i < len(chars); i++ {
		c := chars[i]
		if c >= utf8.RuneSelf {   // 非ASCII字符返回错误
			return as, false
		}
		as[c/32] |= 1 << (c % 32)   // 位图 行坐标 c/32,列坐标是 c%32, 然后 1左移 列坐标各个数 1<<(c%32)  然后 |= 或运算,这样相位坐标位置置为1
	}
	return as, true
}

其中 TrimRight 中的 trimRightASCII 从右边挨个字符匹配位图集函数如下:

// trimRightASCII 从最后边开始 挨个字符查看是否在set集中,直到不在为止,然后返回。
func trimRightASCII(s string, as *asciiSet) string {
	for len(s) > 0 {
		if !as.contains(s[len(s)-1]) { // 从右边开始 挨个查看 字符的映射坐标 在as中对应的 值是否是1
			break
		}
		s = s[:len(s)-1]
	}
		return s // 返回 截取后的结果
	
}
	

其中 trimRightASCII 的 contains 是 查看位图集中是否有 字符映射 源码 如下:

// contains reports whether c is inside the set.
func (as *asciiSet) contains(c byte) bool {
	return (as[c/32] & (1 << (c % 32))) != 0  // 位运算 通过 列坐标获取 1左移后的 结果 与 通过行结果拿到set中的 结果,然后 与 运算 ,可查看c是否包含在 set 中。
}

之所以 用 位图这种方法 不用 字符串循环查找 子串 主要是考虑到效率。位图集方式速度比字符串查找速度快很多,原因包括位图查找算法时间复杂度是o(1) 和 与或运算在硬件层面执行等,感兴趣的可以研究下。

7. Strings.Replace(s, old, new string, n int) string 函数

我们来看下 replace函数具体内容

```go
func Replace(s, old, new string, n int) string {
	if old == new || n == 0 {
		return s // avoid allocation
	}

	// Compute number of replacements.
	if m := Count(s, old); m == 0 { // 计算字符串s 子串 old 的数量
		return s // avoid allocation
	} else if n < 0 || m < n {
		n = m
	}

	// Apply replacements to buffer.
	var b Builder                          // 这里用到了 Builder 比 +  速度快很多
	b.Grow(len(s) + n*(len(new)-len(old))) // 预先分配 空间 防止不断扩容
	start := 0                             // 生成新字符串时  每次需要从s串截取的 子串 在s中的起始位置
	for i := 0; i < n; i++ {
		j := start // j 生成新字符串时  每次需要从s串截取的 子串 在s中的截止位置,初始化为 start位置
		if len(old) == 0 {
			if i > 0 {// 小细节 当i==0 时 下面 值wid 肯定为0,原因见下行解释 
				_, wid := utf8.DecodeRuneInString(s[start:]) // 如果 old 为空 其实就是在 每个单词旁边 添加 new,这时每次需要跳过 一个 utf-8 rune 元素,因为s中可能有中文(一个中文通常占3个字符) 如果一个字符一个字符跳 可能出现乱码
				j += wid
			}
		} else {
			j += Index(s[start:], old) // 通过计算 old 字符串在子串 s[start:] 中第一次出现的索引,再累加上次截止 索引,可找到 第 i+1 次 子串的 截止索引
		}
		b.WriteString(s[start:j]) //在s中 提取 第 i+1 个 子串
		b.WriteString(new)        //  拼接需要替换的子串
		start = j + len(old)      // 计算下次 起始位置
	}
	b.WriteString(s[start:]) // 将 s 字符串中后续没有 new 字符串的子串 拼接到 新字符串当中
	return b.String()        // 将 内存中的byte[] 转换成字符串
}

可以看到 上述代码写的 逻辑清楚 层次分明 同时又短小精悍 简直简直了

总结

一直在用stings包,现在梳理了以便其源码,更加佩服大师们的能力了,考虑的很细节,而且优化大部分都是在最底层优化,考虑的层深至少是字节码和底层交互时的深度。所以越深入源码就是越深入优化的最核心部分,通到计算机最核心底层的优化才是真正的优化。

参考文章:
https://darjun.github.io/2021/05/18/youdontknowgo/string/
https://blog.csdn.net/ya_feng/article/details/105011464

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

9.列表渲染

列表渲染 我们可以使用 v-for 指令基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法&#xff0c;其中 items 是源数据的数组&#xff0c;而 item 是迭代项的别名 <template><div><p v-for"item in names">{{ it…

基于 RT-Thread 的 PPP Device 软件包的详细使用以及AT通用配网过程

一、AT通用上网过程 网络初始化流程 一般情况如下 1、先上电复位模块&#xff1b; 2、间隔一直发送 AT\r 等待模组响应,表示模组启动&#xff0c;并且调试好了波特率&#xff1b; 3、发送ATCPIN?\r 测试卡是否插好&#xff1b; 4、发送 ATCSQ\r 查询信号质量&#xff0c;只有…

【QT进阶】Qt http编程之后端API测试工具postman使用介绍

往期回顾 【QT进阶】Qt Web混合编程之使用ECharts显示各类折线图等-CSDN博客 【QT进阶】Qt Web混合编程之实现ECharts数据交互动态修改-CSDN博客 【QT进阶】Qt http编程之http与https简单介绍-CSDN博客 【QT进阶】Qt http编程之后端API测试工具postman使用介绍 其实这个工具的…

【简单介绍下Faiss原理和使用】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Docker - 入门基础

原文地址&#xff0c;使用效果更佳&#xff01; Docker - 入门基础 | CoderMast编程桅杆https://www.codermast.com/dev-tools/docker/docker-basic.html Docker架构 Docker 使用的是客户端-服务端&#xff08;C/S&#xff09;架构模式&#xff0c;使用远程 API 来管理和创建…

【Leetcode每日一题】 穷举vs暴搜vs深搜vs回溯vs剪枝_全排列 - 全排列(难度⭐⭐)(62)

1. 题目解析 题目链接&#xff1a;46. 全排列 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 回溯算法是一种通过探索所有可能的候选解来找出所有解的算法。当候选解被确认不是一个解&#xff08;或者至少不是最后一…

MyBatis 框架学习(II)

MyBatis 框架学习(II) 文章目录 MyBatis 框架学习(II)1. 介绍2. 准备&测试2.1 配置数据库连接字符串和MyBatis2.2 编写持久层代码 3. MyBatis XML基础操作3.1 Insert 操作3.2 Delete 操作3.3 Update 操作3.4 Select 操作 4. #{} 与 ${}的使用5. 动态SQL操作5.1 < if >…

4.2冰达机器人:视觉实例-机器人视觉循线、视觉实例-调整循线颜色

4.2.10a视觉实例-机器人视觉循线 本节内容演示一个机器人视觉的视觉循线实例 准备工作&#xff1a;布置一块区域作为循线场所&#xff0c;如下图所示。用蓝色胶带在地面贴一条路线&#xff08;机器人极限转弯半径0.5m&#xff0c;不要贴得过于曲折&#xff09;&#xff0c;将…

leetcode:438. 找到字符串中所有字母异位词

给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#xff08;包括相同的字符串&#xff09;。 示例 1: 输入: s "cbaebabacd", p "…

形态学初步

其实在考虑集合的位移z的时候我发现了个问题&#xff0c;以书上的图9.4为例子。现实中&#xff0c;位移z不需要坐标系&#xff0c;但是实际上&#xff0c;不给出坐标系描述位移量是不方便的。其次&#xff0c;位移的前提是需要一个起始位置&#xff0c;但是起始位置如何建立呢&…

Docker - HelloWorld

原文地址&#xff0c;使用效果更佳&#xff01; Docker - HelloWorld | CoderMast编程桅杆https://www.codermast.com/dev-tools/docker/docker-helloworld.html 开始之前 在学习本小节之前&#xff0c;你必须确保你正确安装了 Docker&#xff0c;正确安装 Docker 是后续学习的…

.net8系列-02图文并茂手把手教你编写增删改查接口

前情提要 接上篇文章&#xff0c;我们的应用已经创建完毕了&#xff0c;接下来我们编写几个自己的接口 快速开始 新增Controller 复制一份WeatherForecastController.cs,改名为CommonInfoController 设置Class名 将CommonInfoController中的复制过来的class名改成新名 …

.NET .exe .dll 反编译 程序反编译 程序逆向

反编译是对程序进行逆向分析、研究&#xff0c;以推导出软件产品所使用的思路、原理、结构、算法、处理过程、运行方法等设计要素。 反编译.NET程序需要使用专门的反编译工具 &#x1f9ff;使用dotPeek进行反编译 1.下载dotPeek dotPeek&#xff1a;JetBrains 出品的免费 .N…

[阅读笔记25][WebArena]A Realistic Web Environment for Building Autonomous Agents

这篇论文提出了WebArena这个环境与测试基准&#xff0c;在24年1月发表。 之前的agent都是在一些简化过的合成环境中测试的&#xff0c;这会导致与现实场景脱节。这篇论文构建了一个高度逼真、可复现的环境。该环境涉及四个领域&#xff1a;电子商务、论坛讨论、软件开发和内容管…

【linux运维】系统常见管理命令

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了学习基本的shell编程和linux命令&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于b站大学——linux运维课程进行的&#xff0c;…

数据结构 -- 二叉树二叉搜索树

二叉树 二叉树是这么一种树状结构&#xff1a;每个节点最多有两个孩子&#xff0c;左孩子和右孩子 重要的二叉树结构 完全二叉树&#xff08;complete binary tree&#xff09;是一种二叉树结构&#xff0c;除最后一层以外&#xff0c;每一层都必须填满&#xff0c;填充时要遵…

自然语言处理基础面试

文章目录 TF-IDFbag-of-wordsBert 讲道理肯定还得有Transformer&#xff0c;我这边先放着&#xff0c;以后再加吧。 TF-IDF TF&#xff08;全称TermFrequency&#xff09;&#xff0c;中文含义词频&#xff0c;简单理解就是关键词出现在网页当中的频次。 IDF&#xff08;全称…

【C++初识继承】

博主首页&#xff1a; 有趣的中国人 专栏首页&#xff1a; C进阶 本篇文章主要讲解 继承 的相关内容 目录 1. 继承的概念和定义 1.1 继承的概念 1.2 继承的定义 1.2.1 继承定义格式 1.2.2 继承方式与访问修饰限定符 2. 基类和派生类对象赋值转换 3. 继承中的作用域 …

读天才与算法:人脑与AI的数学思维笔记05_算法的幻觉

1. 自下而上 1.1. 代码在未来可以自主学习、适应并进行自我改进 1.2. 程序员通过编程教会计算机玩游戏&#xff0c;而计算机却会比教它的人玩得更好&#xff0c;这种输入寡而输出众的事情不大可能实现 1.3. 早在20世纪50年代&#xff0c;计算机科学家们就模拟该过程创造了感…

内存概念理解:RANK,BANK,BURST,INTERLEAVING

背景&#xff1a;死磕内存的bank和rank概念的一天。网上的资料都差不多&#xff0c;还是有些地方没理通顺&#xff0c;有什么内存基础知识的书籍可以推荐吗&#xff1f; 物理RANK的概念 当我们给计算机购买内存条时候&#xff0c;上面显示的1RX8, 2RX8&#xff0c;其中R就是r…