mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-18 23:23:44 +08:00
fix img & index.html & .md.html
This commit is contained in:
@@ -12,9 +12,7 @@
|
||||
<!-- theme css & js -->
|
||||
<meta name="generator" content="Hexo 4.2.0">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="book-container">
|
||||
<div class="book-sidebar">
|
||||
<div class="book-brand">
|
||||
@@ -27,176 +25,98 @@
|
||||
<ul class="uncollapsible">
|
||||
<li><a href="/" class="current-tab">首页</a></li>
|
||||
</ul>
|
||||
|
||||
<ul class="uncollapsible">
|
||||
<li><a href="../">上一级</a></li>
|
||||
</ul>
|
||||
|
||||
<ul class="uncollapsible">
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html">00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/00 开篇词 Go 为开发者的需求设计,带你实现高效工作.md.html">00 开篇词 Go 为开发者的需求设计,带你实现高效工作</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/01 基础入门:编写你的第一个 Go 语言程序.md.html">01 基础入门:编写你的第一个 Go 语言程序.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/01 基础入门:编写你的第一个 Go 语言程序.md.html">01 基础入门:编写你的第一个 Go 语言程序</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/02 数据类型:你必须掌握的数据类型有哪些?.md.html">02 数据类型:你必须掌握的数据类型有哪些?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/02 数据类型:你必须掌握的数据类型有哪些?.md.html">02 数据类型:你必须掌握的数据类型有哪些?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/03 控制结构:if、for、switch 逻辑语句的那些事儿.md.html">03 控制结构:if、for、switch 逻辑语句的那些事儿.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/03 控制结构:if、for、switch 逻辑语句的那些事儿.md.html">03 控制结构:if、for、switch 逻辑语句的那些事儿</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/04 集合类型:如何正确使用 array、slice 和 map?.md.html">04 集合类型:如何正确使用 array、slice 和 map?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/04 集合类型:如何正确使用 array、slice 和 map?.md.html">04 集合类型:如何正确使用 array、slice 和 map?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/05 函数和方法:Go 语言中的函数和方法到底有什么不同?.md.html">05 函数和方法:Go 语言中的函数和方法到底有什么不同?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/05 函数和方法:Go 语言中的函数和方法到底有什么不同?.md.html">05 函数和方法:Go 语言中的函数和方法到底有什么不同?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/06 struct 和 interface:结构体与接口都实现了哪些功能?.md.html">06 struct 和 interface:结构体与接口都实现了哪些功能?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/06 struct 和 interface:结构体与接口都实现了哪些功能?.md.html">06 struct 和 interface:结构体与接口都实现了哪些功能?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html">07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/07 错误处理:如何通过 error、deferred、panic 等处理错误?.md.html">07 错误处理:如何通过 error、deferred、panic 等处理错误?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/08 并发基础:Goroutines 和 Channels 的声明与使用.md.html">08 并发基础:Goroutines 和 Channels 的声明与使用.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/08 并发基础:Goroutines 和 Channels 的声明与使用.md.html">08 并发基础:Goroutines 和 Channels 的声明与使用</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/09 同步原语:sync 包让你对并发控制得心应手.md.html">09 同步原语:sync 包让你对并发控制得心应手.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/09 同步原语:sync 包让你对并发控制得心应手.md.html">09 同步原语:sync 包让你对并发控制得心应手</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/10 Context:你必须掌握的多线程并发控制神器.md.html">10 Context:你必须掌握的多线程并发控制神器.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/10 Context:你必须掌握的多线程并发控制神器.md.html">10 Context:你必须掌握的多线程并发控制神器</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/11 并发模式:Go 语言中即学即用的高效并发模式.md.html">11 并发模式:Go 语言中即学即用的高效并发模式.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/11 并发模式:Go 语言中即学即用的高效并发模式.md.html">11 并发模式:Go 语言中即学即用的高效并发模式</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/12 指针详解:在什么情况下应该使用指针?.md.html">12 指针详解:在什么情况下应该使用指针?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/12 指针详解:在什么情况下应该使用指针?.md.html">12 指针详解:在什么情况下应该使用指针?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/13 参数传递:值、引用及指针之间的区别?.md.html">13 参数传递:值、引用及指针之间的区别?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/13 参数传递:值、引用及指针之间的区别?.md.html">13 参数传递:值、引用及指针之间的区别?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/14 内存分配:new 还是 make?什么情况下该用谁?.md.html">14 内存分配:new 还是 make?什么情况下该用谁?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/14 内存分配:new 还是 make?什么情况下该用谁?.md.html">14 内存分配:new 还是 make?什么情况下该用谁?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/15 运行时反射:字符串和结构体之间如何转换?.md.html">15 运行时反射:字符串和结构体之间如何转换?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/15 运行时反射:字符串和结构体之间如何转换?.md.html">15 运行时反射:字符串和结构体之间如何转换?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/16 非类型安全:让你既爱又恨的 unsafe.md.html">16 非类型安全:让你既爱又恨的 unsafe.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/16 非类型安全:让你既爱又恨的 unsafe.md.html">16 非类型安全:让你既爱又恨的 unsafe</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
<a class="current-tab" href="/专栏/22 讲通关 Go 语言-完/17 SliceHeader:slice 如何高效处理数据?.md.html">17 SliceHeader:slice 如何高效处理数据?.md.html</a>
|
||||
<a class="current-tab" href="/专栏/22 讲通关 Go 语言-完/17 SliceHeader:slice 如何高效处理数据?.md.html">17 SliceHeader:slice 如何高效处理数据?</a>
|
||||
|
||||
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/18 质量保证:Go 语言如何通过测试保证质量?.md.html">18 质量保证:Go 语言如何通过测试保证质量?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/18 质量保证:Go 语言如何通过测试保证质量?.md.html">18 质量保证:Go 语言如何通过测试保证质量?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/19 性能优化:Go 语言如何进行代码检查和优化?.md.html">19 性能优化:Go 语言如何进行代码检查和优化?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/19 性能优化:Go 语言如何进行代码检查和优化?.md.html">19 性能优化:Go 语言如何进行代码检查和优化?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/20 协作开发:模块化管理为什么能够提升研发效能?.md.html">20 协作开发:模块化管理为什么能够提升研发效能?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/20 协作开发:模块化管理为什么能够提升研发效能?.md.html">20 协作开发:模块化管理为什么能够提升研发效能?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/21 网络编程:Go 语言如何玩转 RESTful API 服务?.md.html">21 网络编程:Go 语言如何玩转 RESTful API 服务?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/21 网络编程:Go 语言如何玩转 RESTful API 服务?.md.html">21 网络编程:Go 语言如何玩转 RESTful API 服务?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?.md.html">22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?.md.html">22 网络编程:Go 语言如何通过 RPC 实现跨平台服务?</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/23 结束语 你的 Go 语言成长之路.md.html">23 结束语 你的 Go 语言成长之路.md.html</a>
|
||||
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/23 结束语 你的 Go 语言成长之路.md.html">23 结束语 你的 Go 语言成长之路</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||||
<div class="sidebar-toggle-inner"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function add_inner() {
|
||||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||||
inner.classList.add('show')
|
||||
}
|
||||
|
||||
function remove_inner() {
|
||||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||||
inner.classList.remove('show')
|
||||
}
|
||||
|
||||
function sidebar_toggle() {
|
||||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||||
let sidebar = document.querySelector('.book-sidebar')
|
||||
@@ -211,8 +131,6 @@
|
||||
content.classList.add('extend')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function open_sidebar() {
|
||||
let sidebar = document.querySelector('.book-sidebar')
|
||||
let overlay = document.querySelector('.off-canvas-overlay')
|
||||
@@ -225,9 +143,7 @@ function hide_canvas() {
|
||||
sidebar.classList.remove('show')
|
||||
overlay.classList.remove('show')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="off-canvas-content">
|
||||
<div class="columns">
|
||||
<div class="column col-12 col-lg-12">
|
||||
@@ -252,18 +168,13 @@ function hide_canvas() {
|
||||
<p>在讲 slice 的原理之前,我先来介绍一下数组。几乎所有的编程语言里都存在数组,Go 也不例外。那么为什么 Go 语言除了数组之外又设计了 slice 呢?要想解答这个问题,我们先来了解数组的局限性。</p>
|
||||
<p>在下面的示例中,a1、a2 是两个定义好的数组,但是它们的类型不一样。变量 a1 的类型是 [1]string,变量 a2 的类型是 [2]string,也就是说数组的大小属于数组类型的一部分,只有数组内部元素类型和大小一致时,这两个数组才是同一类型。</p>
|
||||
<pre><code>a1:=[1]string{"飞雪无情"}
|
||||
|
||||
a2:=[2]string{"飞雪无情"}
|
||||
</code></pre>
|
||||
<p>可以总结为,一个数组由两部分构成:数组的大小和数组内的元素类型。</p>
|
||||
<pre><code>//数组结构伪代码表示
|
||||
|
||||
array{
|
||||
|
||||
len
|
||||
|
||||
item type
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>比如变量 a1 的大小是 1,内部元素的类型是 string,也就是说 a1 最多只能存储 1 个类型为 string 的元素。而 a2 的大小是 2,内部元素的类型也是 string,所以 a2 最多可以存储 2 个类型为 string 的元素。<strong>一旦一个数组被声明,它的大小和内部元素的类型就不能改变</strong>,你不能随意地向数组添加任意多个元素。这是数组的第一个限制。</p>
|
||||
@@ -278,13 +189,9 @@ array{
|
||||
<p>通过内置的 append 方法,你可以向一个切片中追加任意多个元素,所以这就可以解决数组的第一个限制。</p>
|
||||
<p>在下面的示例中,我通过内置的 append 函数为切片 ss 添加了两个字符串,然后返回一个新的切片赋值给 ss。</p>
|
||||
<pre><code>func main() {
|
||||
|
||||
ss:=[]string{"飞雪无情","张三"}
|
||||
|
||||
ss=append(ss,"李四","王五")
|
||||
|
||||
fmt.Println(ss)
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>现在运行这段代码,会看到如下打印结果:</p>
|
||||
@@ -292,24 +199,16 @@ array{
|
||||
</code></pre>
|
||||
<p>当通过 append 追加元素时,如果切片的容量不够,append 函数会自动扩容。比如上面的例子,我打印出使用 append 前后的切片长度和容量,代码如下:</p>
|
||||
<pre><code>func main() {
|
||||
|
||||
ss:=[]string{"飞雪无情","张三"}
|
||||
|
||||
fmt.Println("切片ss长度为",len(ss),",容量为",cap(ss))
|
||||
|
||||
ss=append(ss,"李四","王五")
|
||||
|
||||
fmt.Println("切片ss长度为",len(ss),",容量为",cap(ss))
|
||||
|
||||
fmt.Println(ss)
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>其中,我通过内置的 len 函数获取切片的长度,通过 cap 函数获取切片的容量。运行这段代码,可以看到打印结果如下:</p>
|
||||
<pre><code>切片ss长度为 2 ,容量为 2
|
||||
|
||||
切片ss长度为 4 ,容量为 4
|
||||
|
||||
[飞雪无情 张三 李四 王五]
|
||||
</code></pre>
|
||||
<p>在调用 append 之前,容量是 2,调用之后容量是 4,说明自动扩容了。</p>
|
||||
@@ -319,13 +218,9 @@ array{
|
||||
<h4>数据结构</h4>
|
||||
<p>在 Go 语言中,切片其实是一个结构体,它的定义如下所示:</p>
|
||||
<pre><code>type SliceHeader struct {
|
||||
|
||||
Data uintptr
|
||||
|
||||
Len int
|
||||
|
||||
Cap int
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>SliceHeader 是切片在运行时的表现形式,它有三个字段 Data、Len 和 Cap。</p>
|
||||
@@ -336,24 +231,16 @@ array{
|
||||
</ol>
|
||||
<p>通过这三个字段,就可以把一个数组抽象成一个切片,便于更好的操作,所以不同切片对应的底层 Data 指向的可能是同一个数组。现在通过一个示例来证明,代码如下:</p>
|
||||
<pre><code>func main() {
|
||||
|
||||
a1:=[2]string{"飞雪无情","张三"}
|
||||
|
||||
s1:=a1[0:1]
|
||||
|
||||
s2:=a1[:]
|
||||
|
||||
//打印出s1和s2的Data值,是一样的
|
||||
|
||||
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&s1)).Data)
|
||||
|
||||
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&s2)).Data)
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>用上节课学习的 unsafe.Pointer 把它们转换为 *reflect.SliceHeader 指针,就可以打印出 Data 的值,打印结果如下所示:</p>
|
||||
<pre><code>824634150744
|
||||
|
||||
824634150744
|
||||
</code></pre>
|
||||
<p>你会发现它们是一样的,也就是这两个切片共用一个数组,所以我们在切片赋值、重新进行切片操作时,使用的还是同一个数组,没有复制原来的元素。这样可以减少内存的占用,提高效率。</p>
|
||||
@@ -367,17 +254,11 @@ array{
|
||||
<p>要获取切片数据结构的三个字段的值,也可以不使用 SliceHeader,而是完全自定义一个结构体,只要字段和 SliceHeader 一样就可以了。</p>
|
||||
<p>比如在下面的示例中,通过 unsfe.Pointer 转换成自定义的 *slice 指针,同样可以获取三个字段对应的值,你甚至可以把字段的名称改为 d、l 和 c,也可以达到目的。</p>
|
||||
<pre><code>sh1:=(*slice)(unsafe.Pointer(&s1))
|
||||
|
||||
fmt.Println(sh1.Data,sh1.Len,sh1.Cap)
|
||||
|
||||
type slice struct {
|
||||
|
||||
Data uintptr
|
||||
|
||||
Len int
|
||||
|
||||
Cap int
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
@@ -392,40 +273,24 @@ type slice struct {
|
||||
<p>进一步对比,在数组和切片中,切片又是高效的,因为它在赋值、函数传参的时候,并不会把所有的元素都复制一遍,而只是复制 SliceHeader 的三个字段就可以了,共用的还是同一个底层数组。</p>
|
||||
<p>在下面的示例中,我定义了两个函数 arrayF 和 sliceF,分别打印传入的数组和切片底层对应的数组指针。</p>
|
||||
<pre><code>func main() {
|
||||
|
||||
a1:=[2]string{"飞雪无情","张三"}
|
||||
|
||||
fmt.Printf("函数main数组指针:%p\n",&a1)
|
||||
|
||||
arrayF(a1)
|
||||
|
||||
s1:=a1[0:1]
|
||||
|
||||
fmt.Println((*reflect.SliceHeader)(unsafe.Pointer(&s1)).Data)
|
||||
|
||||
sliceF(s1)
|
||||
|
||||
}
|
||||
|
||||
func arrayF(a [2]string){
|
||||
|
||||
fmt.Printf("函数arrayF数组指针:%p\n",&a)
|
||||
|
||||
}
|
||||
|
||||
func sliceF(s []string){
|
||||
|
||||
fmt.Printf("函数sliceF Data:%d\n",(*reflect.SliceHeader)(unsafe.Pointer(&s)).Data)
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>然后我在 main 函数里调用它们,运行程序会打印如下结果:</p>
|
||||
<pre><code>函数main数组指针:0xc0000a6020
|
||||
|
||||
函数arrayF数组指针:0xc0000a6040
|
||||
|
||||
824634400800
|
||||
|
||||
函数sliceF Data:824634400800
|
||||
</code></pre>
|
||||
<p>你会发现,同一个数组在 main 函数中的指针和在 arrayF 函数中的指针是不一样的,这说明数组在传参的时候被复制了,又产生了一个新数组。而 slice 切片的底层 Data 是一样的,这说明不管是在 main 函数还是 sliceF 函数中,这两个切片共用的还是同一个底层数组,底层数组并没有被复制。</p>
|
||||
@@ -437,26 +302,18 @@ func sliceF(s []string){
|
||||
<p>下面我通过 string 和 []byte 相互强制转换的例子,进一步帮你理解 slice 高效的原因。</p>
|
||||
<p>比如我把一个 []byte 转为一个 string 字符串,然后再转换回来,示例代码如下:</p>
|
||||
<pre><code>s:="飞雪无情"
|
||||
|
||||
b:=[]byte(s)
|
||||
|
||||
s3:=string(b)
|
||||
|
||||
fmt.Println(s,string(b),s3)
|
||||
</code></pre>
|
||||
<p>在这个示例中,变量 s 是一个 string 字符串,它可以通过 []byte(s) 被强制转换为 []byte 类型的变量 b,又可以通过 string(b) 强制转换为 string 类型的变量 s3。打印它们三个变量的值,都是</p>
|
||||
<p>“飞雪无情”。</p>
|
||||
<p>Go 语言通过先分配一个内存再复制内容的方式,实现 string 和 []byte 之间的强制转换。现在我通过 string 和 []byte 指向的真实内容的内存地址,来验证强制转换是采用重新分配内存的方式。如下面的代码所示:</p>
|
||||
<pre><code>s:="飞雪无情"
|
||||
|
||||
fmt.Printf("s的内存地址:%d\n", (*reflect.StringHeader)(unsafe.Pointer(&s)).Data)
|
||||
|
||||
b:=[]byte(s)
|
||||
|
||||
fmt.Printf("b的内存地址:%d\n",(*reflect.SliceHeader)(unsafe.Pointer(&b)).Data)
|
||||
|
||||
s3:=string(b)
|
||||
|
||||
fmt.Printf("s3的内存地址:%d\n", (*reflect.StringHeader)(unsafe.Pointer(&s3)).Data)
|
||||
</code></pre>
|
||||
<p>运行它们,你会发现打印出的内存地址都不一样,这说明虽然内容相同,但已经不是同一个字符串了,因为内存地址不同。</p>
|
||||
@@ -465,13 +322,9 @@ fmt.Printf("s3的内存地址:%d\n", (*reflect.StringHeader)(unsafe.
|
||||
</blockquote>
|
||||
<p>通过以上的示例代码,你已经知道了 SliceHeader 是什么。其实 StringHeader 和 SliceHeader 一样,代表的是字符串在程序运行时的真实结构,StringHeader 的定义如下所示:</p>
|
||||
<pre><code>// StringHeader is the runtime representation of a string.
|
||||
|
||||
type StringHeader struct {
|
||||
|
||||
Data uintptr
|
||||
|
||||
Len int
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>也就是说,在程序运行的时候,字符串和切片本质上就是 StringHeader 和 SliceHeader。这两个结构体都有一个 Data 字段,用于存放指向真实内容的指针。所以我们打印出 Data 这个字段的值,就可以判断 string 和 []byte 强制转换后是不是重新分配了内存。</p>
|
||||
@@ -480,23 +333,16 @@ type StringHeader struct {
|
||||
<p>仔细观察 StringHeader 和 SliceHeader 这两个结构体,会发现它们的前两个字段一模一样,那么 []byte 转 string,就等于通过 unsafe.Pointer 把 *SliceHeader 转为 *StringHeader,也就是 *[]byte 转 *string,原理和我上面讲的把切片转换成一个自定义的 slice 结构体类似。</p>
|
||||
<p>在下面的示例中,s4 和 s3 的内容是一样的。不一样的是 s4 没有申请新内存(零拷贝),它和变量 b 使用的是同一块内存,因为它们的底层 Data 字段值相同,这样就节约了内存,也达到了 []byte 转 string 的目的。</p>
|
||||
<pre><code>s:="飞雪无情"
|
||||
|
||||
b:=[]byte(s)
|
||||
|
||||
//s3:=string(b)
|
||||
|
||||
s4:=*(*string)(unsafe.Pointer(&b))
|
||||
</code></pre>
|
||||
<p>SliceHeader 有 Data、Len、Cap 三个字段,StringHeader 有 Data、Len 两个字段,所以 *SliceHeader 通过 unsafe.Pointer 转为 *StringHeader 的时候没有问题,因为 *SliceHeader 可以提供 *StringHeader 所需的 Data 和 Len 字段的值。但是反过来却不行了,因为 *StringHeader 缺少 *SliceHeader 所需的 Cap 字段,需要我们自己补上一个默认值。</p>
|
||||
<p>在下面的示例中,b1 和 b 的内容是一样的,不一样的是 b1 没有申请新内存,而是和变量 s 使用同一块内存,因为它们底层的 Data 字段相同,所以也节约了内存。</p>
|
||||
<pre><code>s:="飞雪无情"
|
||||
|
||||
//b:=[]byte(s)
|
||||
|
||||
sh:=(*reflect.SliceHeader)(unsafe.Pointer(&s))
|
||||
|
||||
sh.Cap = sh.Len
|
||||
|
||||
b1:=*(*[]byte)(unsafe.Pointer(sh))
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
@@ -504,11 +350,8 @@ b1:=*(*[]byte)(unsafe.Pointer(sh))
|
||||
</blockquote>
|
||||
<p>通过 unsafe.Pointer 进行类型转换,避免内存拷贝提升性能的方法在 Go 语言标准库中也有使用,比如 strings.Builder 这个结构体,它内部有 buf 字段存储内容,在通过 String 方法把 []byte 类型的 buf 转为 string 的时候,就使用 unsafe.Pointer 提高了效率,代码如下:</p>
|
||||
<pre><code>// String returns the accumulated string.
|
||||
|
||||
func (b *Builder) String() string {
|
||||
|
||||
return *(*string)(unsafe.Pointer(&b.buf))
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
<p>string 和 []byte 的互转就是一个很好的利用 SliceHeader 结构体的示例,通过它可以实现零拷贝的类型转换,提升了效率,避免了内存浪费。</p>
|
||||
@@ -527,12 +370,10 @@ func (b *Builder) String() string {
|
||||
<a href="/专栏/22 讲通关 Go 语言-完/18 质量保证:Go 语言如何通过测试保证质量?.md.html">下一页</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||||
</div>
|
||||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996dcbdfe83d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||||
@@ -541,11 +382,9 @@ func (b *Builder) String() string {
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-NPSEEVD756');
|
||||
var path = window.location.pathname
|
||||
@@ -559,14 +398,12 @@ func (b *Builder) String() string {
|
||||
} else {
|
||||
setCookie("lastPath", path)
|
||||
}
|
||||
|
||||
function setCookie(cname, cvalue) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||||
var expires = "expires=" + d.toGMTString();
|
||||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||||
}
|
||||
|
||||
function getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
@@ -576,7 +413,5 @@ func (b *Builder) String() string {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user