diff --git a/archive/go/big-file-sign/main.go b/archive/go/big-file-sign/main.go deleted file mode 100644 index f08de51..0000000 --- a/archive/go/big-file-sign/main.go +++ /dev/null @@ -1,129 +0,0 @@ -package main - -import ( - "bytes" - "crypto/md5" - "fmt" - "io" - "net/http" - "runtime" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - /** - 1G 文件测试结果 - { - "md5": "23023b24fc0ab2d03d5cd62a18bb4aa8", - "mem": "96M", - "startMem": "1M" - } - 100M 文件测试结果 - { - "md5": "6ba2b5a1b62f356fc86efad690613916", - "mem": "96M", - "startMem": "0M" - } - */ - r.GET("/hash1", func(c *gin.Context) { - fmt.Printf("%v", c.ContentType()) - m := runtime.MemStats{} - runtime.ReadMemStats(&m) - startM := m.Alloc / 1024 / 1024 - oldBody := c.Request.Body - defer oldBody.Close() - pr, pw := io.Pipe() - defer pw.Close() - defer pr.Close() - c.Request.Body = pr - hash := md5.New() - go func() { - _, err := io.Copy(io.MultiWriter(hash, pw), oldBody) - if err != nil { - fmt.Printf("io copy: %v", err) - } - }() - _, err := c.MultipartForm() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - runtime.ReadMemStats(&m) - c.JSON(http.StatusOK, gin.H{ - "md5": fmt.Sprintf("%x", hash.Sum(nil)), - "startMem": fmt.Sprintf("%dM", startM), - "mem": fmt.Sprintf("%dM", m.Alloc/1024/1024), - }) - }) - - /** - 1G 文件测试结果 - { - "md5": "62da2c499cdb3fad927f881c134684b0", - "mem": "2922M", - "startMem": "1M" - } - 100M 文件测试结果 - { - "md5": "55a6849293d0847a48f856254aa910e2", - "mem": "341M", - "startMem": "1M" - } - */ - r.GET("/hash2", func(c *gin.Context) { - m := runtime.MemStats{} - runtime.ReadMemStats(&m) - startM := m.Alloc / 1024 / 1024 - oldBody := c.Request.Body - defer oldBody.Close() - buffer := bytes.NewBuffer(nil) - hash := md5.New() - _, err := io.Copy(io.MultiWriter(buffer, hash), oldBody) - if err != nil { - fmt.Printf("io copy2: %v", err) - } - c.Request.Body = io.NopCloser(buffer) - _, err = c.MultipartForm() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - runtime.ReadMemStats(&m) - c.JSON(http.StatusOK, gin.H{ - "md5": fmt.Sprintf("%x", hash.Sum(nil)), - "startMem": fmt.Sprintf("%dM", startM), - "mem": fmt.Sprintf("%dM", m.Alloc/1024/1024), - }) - }) - - /** - 1G 文件测试结果 - { - "mem": "96M", - "startMem": "1M" - } - 100M 文件测试结果 - { - "mem": "96M", - "startMem": "1M" - } - */ - r.GET("/hash3", func(c *gin.Context) { - m := runtime.MemStats{} - runtime.ReadMemStats(&m) - startM := m.Alloc / 1024 / 1024 - _, err := c.MultipartForm() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - runtime.ReadMemStats(&m) - c.JSON(http.StatusOK, gin.H{ - "startMem": fmt.Sprintf("%dM", startM), - "mem": fmt.Sprintf("%dM", m.Alloc/1024/1024), - }) - }) - r.Run(":8088") -} diff --git a/docs/dev/language/golang/code/io_test.go b/docs/dev/language/golang/code/io_test.go new file mode 100644 index 0000000..8ed3b2d --- /dev/null +++ b/docs/dev/language/golang/code/io_test.go @@ -0,0 +1,75 @@ +package code + +import ( + "bufio" + "bytes" + "crypto/md5" + "crypto/sha1" + "io" + "testing" +) + +func TestIOHash(t *testing.T) { + file := bytes.NewBuffer([]byte("hello world")) + md5 := md5.New() + sha1 := sha1.New() + r := io.TeeReader(file, io.MultiWriter(md5, sha1)) + _, _ = io.Copy(io.Discard, r) + t.Logf("md5: %x\n", md5.Sum(nil)) + t.Logf("sha1: %x\n", sha1.Sum(nil)) +} + +func TestLimitReader(t *testing.T) { + file := bytes.NewBuffer([]byte("hello world")) + lr := io.LimitReader(file, 5) + data, _ := io.ReadAll(lr) + t.Logf("%s\n", data) +} + +func TestBufIo(t *testing.T) { + // 写入100M文件 + file := bytes.NewBufferString("hello world\ngolang") + buf := bufio.NewReader(file) + for { + line, err := buf.ReadString('\n') + if err == io.EOF { + t.Logf("%s", line) + break + } + t.Logf("%s", line) + } +} + +// 凯撒加密 +type decode struct { + r io.Reader +} + +func (d *decode) Read(p []byte) (int, error) { + // 读取数据 + n, err := d.r.Read(p) + if err != nil { + if err != io.EOF { + return n, err + } + } + // 解密 + for i := 0; i < n; i++ { + p[i]++ + } + return n, err +} + +func TestDecodePipe(t *testing.T) { + body := bytes.NewBuffer([]byte("gdkkn\u001Fvnqkc")) + r, w := io.Pipe() + defer r.Close() + go func() { + defer w.Close() + // 读取加密数据解密 + _, _ = io.Copy(w, &decode{r: body}) + }() + // 读取解密数据,保存文件 + data, _ := io.ReadAll(r) + t.Logf("%s\n", data) +} diff --git a/docs/dev/language/golang/code/slice_test.go b/docs/dev/language/golang/code/slice_test.go index facf1eb..74093b3 100644 --- a/docs/dev/language/golang/code/slice_test.go +++ b/docs/dev/language/golang/code/slice_test.go @@ -2,10 +2,11 @@ package code import ( "fmt" - "github.com/stretchr/testify/assert" "reflect" "testing" "unsafe" + + "github.com/stretchr/testify/assert" ) func TestSliceArray(t *testing.T) { diff --git a/docs/dev/language/golang/go-io.md b/docs/dev/language/golang/go-io.md new file mode 100644 index 0000000..992510c --- /dev/null +++ b/docs/dev/language/golang/go-io.md @@ -0,0 +1,148 @@ +# Golang IO相关操作 + +- io包提供了基础的io操作,几乎所有的io操作都是基于io.Reader和io.Writer接口的。 +- ioutil 包提供了一些方便的io操作函数,但是在go 1.16中已经被废弃,放进了io包。 +- bufio实现了缓冲io,可以提高io效率。 +- bytes 包中提供了Buffer类型,可以用来做io操作。 + +## 核心接口 + +### Reader/Writer/Seeker + +Reader和Writer是io包中最重要的接口,它们定义了io操作的基本行为。像一些文件操作,网络操作等等,都是基于这两个接口的。 + +Seeker接口定义了Seek方法,可以在Reader/Writer中定位到指定的位置,文件操作中常用。 + +## 常用函数 + +下面我们来看一些常用的io操作函数,更多的函数可以查看官方文档,我只是列出一些我常用到的。 + +- io.ReadeAll 读取所有数据,返回一个bytes +- io.MultiWriter/Reader 可以将多个Reader/Writer合并成一个 +- io.LimitReader 限制Reader的读取长度,读取n个长度后返回io.EOF +- io.TeeReader 读取数据的同时写入到另一个Writer中 +- io.Pipe 创建同步内存管道 +- io.Copy/CopyN 复制Reader到Writer +- io.NopCloser 给一个io.Reader加上Close方法 +- bytes.NewBuffer 创建一个Buffer +- bufio.NewReader/Writer 创建一个缓冲Reader/Writer + +## 常见小坑 + +> 我暂时能想到的坑记录下来,欢迎补充 + +### io.EOF + +io.EOF是io包中定义的一个错误,表示读取到文件末尾。在读取文件时,当读取到文件末尾时,会返回io.EOF错误。 + +请注意,当读取到文件末尾时,返回的数据可能不是nil,而是文件的最后一部分数据,需要注意处理。 + +### 大文件的操作 + +在处理大文件时,需要注意内存的使用,尽量使用io.Reader和io.Writer来处理文件,避免一次性读取整个文件到内存中。 + +### bufio.Writer.Flush + +在使用bufio.Writer时,需要注意Flush方法,因为bufio.Writer是带缓冲的,数据并不是实时写入到Writer中的,需要调用Flush方法来刷新缓冲区。 + +### io.Pipe + +io.Pipe是一个同步的内存管道,请注意在使用时,Reader与Writer必须在不同的goroutine中,且需要注意Reader与Writer的速度需要一致,否则会造成死锁或线程阻塞。 + +并且需要使用Close方法来关闭Reader或Writer,否则会造成内存泄漏。 + +## 高级用例 + +下面介绍一些高级用例,你也可以从中学习到一些高级的io操作技巧。 + +### 计算上传文件的hash + +下面的例子是计算上传文件的md5和sha1的hash值,我们使用io.TeeReader将文件内容同时写入到md5和sha1的hash计算器中。 +像我们在http上传文件时,可以在上传的同时计算文件的hash值,这样可以保证文件的完整性。同时也可以打开文件,再将文件的Writer放到io.Copy中,这样可以同时写入文件和计算hash值。 + +```go +func TestIOHash(t *testing.T) { + file := bytes.NewBuffer([]byte("hello world")) + md5 := md5.New() + sha1 := sha1.New() + r := io.TeeReader(file, io.MultiWriter(md5, sha1)) + _, _ = io.Copy(io.Discard, r) + t.Logf("md5: %x\n", md5.Sum(nil)) + t.Logf("sha1: %x\n", sha1.Sum(nil)) +} +``` + +### 读取文件的部分内容 + +下面的例子是读取文件的部分内容,我们使用io.LimitReader限制读取的长度,读取n个长度后返回io.EOF,这在解析文件头部信息时非常有用。 + +```go +func TestLimitReader(t *testing.T) { + file := bytes.NewBuffer([]byte("hello world")) + lr := io.LimitReader(file, 5) + data, _ := io.ReadAll(lr) + t.Logf("%s\n", data) +} +``` + +### 大文件按行读取 + +下面的例子是读取大文件的每一行,我们使用bufio.Reader来读取文件的每一行,这样可以避免一次性读取整个文件到内存中。 + +在读一些io比较慢的文件时(网络文件),也可以使用这种方式来读取文件。写文件也是类似的,可以使用bufio.Writer来写文件,提高磁盘io效率,避免频繁的io操作。 + +```go +func TestBufIo(t *testing.T) { + file := bytes.NewBufferString("hello world\ngolang") + buf := bufio.NewReader(file) + for { + line, err := buf.ReadString('\n') + if err == io.EOF { + t.Logf("%s", line) + break + } + t.Logf("%s", line) + } +} +``` + +### 使用Pipe将上传文件解密写入文件 + +有时候上传的文件是一个加密的,我们需要在上传的同时解密文件,然后同时写入到磁盘中,这时候可以使用io.Pipe来实现。 + +```go + +// 凯撒加密 +type decode struct { + r io.Reader +} + +func (d *decode) Read(p []byte) (int, error) { + // 读取数据 + n, err := d.r.Read(p) + if err != nil { + if err != io.EOF { + return n, err + } + } + // 解密 + for i := 0; i < n; i++ { + p[i]++ + } + return n, err +} + +func TestDecodePipe(t *testing.T) { + body := bytes.NewBuffer([]byte("gdkkn\u001Fvnqkc")) + r, w := io.Pipe() + defer r.Close() + go func() { + defer w.Close() + // 读取加密数据解密 + _, _ = io.Copy(w, &decode{r: body}) + }() + // 读取解密数据,保存文件 + data, _ := io.ReadAll(r) + t.Logf("%s\n", data) +} +``` diff --git a/docs/dev/language/golang/go-slice.md b/docs/dev/language/golang/go-slice.md index d50885c..a9ef33b 100644 --- a/docs/dev/language/golang/go-slice.md +++ b/docs/dev/language/golang/go-slice.md @@ -4,7 +4,7 @@ title: Go 切片、数组、字符串 ## 切片和数组 -切片由三个部分组成:指针(指向底层数组),长度(当前切片使用的长度),容量(切片能包含多少个成员),可以看`reflect.SliceHeader`的定义,不过这个结构体已经在1.20标注为废弃。 +切片由三个部分组成:指针(指向底层数组),长度(当前切片使用的长度),容量(切片能包含多少个成员),可以看`reflect.SliceHeader`的定义,不过这个结构体已经在1.20标注为废弃,这个废弃应该不算真废弃,而是不推荐使用,官方曾在1.18、1.19反复横跳过。 当调用一个函数的时候,函数的每个调用参数将会被赋值给函数内部的参数变量,所以函数参数变量接收的是一个复制的副本,并不是原始调用的变量。(所以数组作为参数,是低效的,还需要进行一次数组的拷贝,可以使用数组指针) @@ -14,23 +14,23 @@ title: Go 切片、数组、字符串 ```go func TestSliceArray(t *testing.T) { - var arr = [...]int{1, 2, 3} - var slice = []int{1, 2, 3} - ModifyArray(arr) - ModifySlice(slice) - t.Logf("%v %v\n", arr, slice) - assert.NotEqual(t, arr[2], 1) - assert.Equal(t, slice[2], 1) + var arr = [...]int{1, 2, 3} + var slice = []int{1, 2, 3} + ModifyArray(arr) + ModifySlice(slice) + t.Logf("%v %v\n", arr, slice) + assert.NotEqual(t, arr[2], 1) + assert.Equal(t, slice[2], 1) } func ModifyArray(arr [3]int) { - fmt.Println(arr) - arr[2] = 1 + fmt.Println(arr) + arr[2] = 1 } func ModifySlice(slice []int) { - fmt.Println(slice) - slice[2] = 1 + fmt.Println(slice) + slice[2] = 1 } ``` @@ -41,24 +41,24 @@ func ModifySlice(slice []int) { `切片是由:数组指针,长度,容量组成的`,来划一下重点. 副本传的也是上面这些东西.然后修改切片的时候呢,实际上是通过切片里面的数组指针去修改了,并没有修改切片的值(数组指针,长度,容量).我们可以用unsafe包来看看切片与数组的结构: -``` +```go func TestSlice(t *testing.T) { - slice := []int{1, 2, 3} - arr := [...]int{1, 2, 3} - fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n", - unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice))) - SlicePointer(slice) - fmt.Printf("数组:底层数组指针:%d 数组指针:%d\n", unsafe.Pointer(&arr), unsafe.Pointer(&arr[0])) - ArrayPointer(arr) + slice := []int{1, 2, 3} + arr := [...]int{1, 2, 3} + fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n", + unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice))) + SlicePointer(slice) + fmt.Printf("数组:底层数组指针:%d 数组指针:%d\n", unsafe.Pointer(&arr), unsafe.Pointer(&arr[0])) + ArrayPointer(arr) } func SlicePointer(slice []int) { - fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n", - unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice))) + fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n", + unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice))) } func ArrayPointer(arr [3]int) { - fmt.Printf("数组:底层数组指针:%d 数组指针:%d\n", unsafe.Pointer(&arr), unsafe.Pointer(&arr[0])) + fmt.Printf("数组:底层数组指针:%d 数组指针:%d\n", unsafe.Pointer(&arr), unsafe.Pointer(&arr[0])) } ``` @@ -75,14 +75,14 @@ func ArrayPointer(arr [3]int) { ```go func Test_DefineSlice(t *testing.T) { - var arr = [...]int{1, 2, 3} - var slice1 = []int{1, 2, 3} - var slice2 = make([]int, 3) - var slice3 = arr[:] - fmt.Printf("arr type=%v len=%d cap=%d\n", reflect.TypeOf(arr).String(), len(arr), cap(arr)) - fmt.Printf("slice1 type=%v len=%d cap=%d\n", reflect.TypeOf(slice1).String(), len(slice1), cap(slice1)) - fmt.Printf("slice2 type=%v len=%d cap=%d\n", reflect.TypeOf(slice2).String(), len(slice2), cap(slice2)) - fmt.Printf("slice3 type=%v len=%d cap=%d\n", reflect.TypeOf(slice3).String(), len(slice3), cap(slice3)) + var arr = [...]int{1, 2, 3} + var slice1 = []int{1, 2, 3} + var slice2 = make([]int, 3) + var slice3 = arr[:] + fmt.Printf("arr type=%v len=%d cap=%d\n", reflect.TypeOf(arr).String(), len(arr), cap(arr)) + fmt.Printf("slice1 type=%v len=%d cap=%d\n", reflect.TypeOf(slice1).String(), len(slice1), cap(slice1)) + fmt.Printf("slice2 type=%v len=%d cap=%d\n", reflect.TypeOf(slice2).String(), len(slice2), cap(slice2)) + fmt.Printf("slice3 type=%v len=%d cap=%d\n", reflect.TypeOf(slice3).String(), len(slice3), cap(slice3)) } //Result: //arr type=[3]int len=3 cap=3 @@ -98,17 +98,17 @@ func Test_DefineSlice(t *testing.T) { ```go func TestModify(t *testing.T) { - arr := [...]int{1, 2, 3, 4, 5, 6, 7} - slice := arr[:] - slice[4] = 8 - t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4]) - assert.Equal(t, slice[4], arr[4]) - slice = append(slice, 9) - slice[5] = 10 - t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4]) - assert.Equal(t, slice[4], arr[4]) - t.Logf("arr[5]=%v,slice[5]=%v\n", arr[5], slice[5]) - assert.NotEqual(t, slice[5], arr[5]) + arr := [...]int{1, 2, 3, 4, 5, 6, 7} + slice := arr[:] + slice[4] = 8 + t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4]) + assert.Equal(t, slice[4], arr[4]) + slice = append(slice, 9) + slice[5] = 10 + t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4]) + assert.Equal(t, slice[4], arr[4]) + t.Logf("arr[5]=%v,slice[5]=%v\n", arr[5], slice[5]) + assert.NotEqual(t, slice[5], arr[5]) } ``` @@ -118,12 +118,12 @@ func TestModify(t *testing.T) { ```go func TestModifyTwoSlice(t *testing.T) { - arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9} - slice1 := arr[1:5] - slice2 := arr[3:8] - slice1[2] = 8 - t.Logf("%v %v %v\n", arr, slice1, slice2) - assert.Equal(t, slice1[2], slice2[0], arr[3]) + arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9} + slice1 := arr[1:5] + slice2 := arr[3:8] + slice1[2] = 8 + t.Logf("%v %v %v\n", arr, slice1, slice2) + assert.Equal(t, slice1[2], slice2[0], arr[3]) } ``` @@ -135,12 +135,12 @@ func TestModifyTwoSlice(t *testing.T) { ```go func Test_Append(t *testing.T) { - slice := []int{1, 2, 3} - println(slice) - slice = append(slice, 1) - println(slice) - slice = append(slice, 1) - println(slice) + slice := []int{1, 2, 3} + println(slice) + slice = append(slice, 1) + println(slice) + slice = append(slice, 1) + println(slice) } // Result: // slice type=[]int len=3 cap=3 @@ -159,45 +159,43 @@ func Test_Append(t *testing.T) { ```go func ModifySlice(slice []int) { - slice[2] = 1 - slice = append(slice, 4) - slice[2] = 3 + slice[2] = 1 + slice = append(slice, 4) + slice[2] = 3 } ``` 我把之前的`ModifySlice`方法修改了一下,然后成员没加,后面再修改回去为 3 也没有发生变化了. 这是因为 append 的时候因为容量不够扩容了,导致底层数组指针发生了改变,但是传进来的切片是外面切片的副本,修改这个切片里面的数组指针不会影响到外面的切片 - - ## 切片扩容 在1.18之前,切片的扩容是原来的2倍,但是当容量超过1024时,每次容量变成原来的1.25倍,直到大于期望容量。在1.18后更换了新的机制: -https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L267 + -- 当新切片>旧切片*2时,直接安装新切片容量计算 +- 当新切片>旧切片\*2时,直接安装新切片容量计算 - 如果旧切片\<256,新切片容量为旧切片\*2 -- 如果旧切片>=256,按` newcap+=(newcap + 3*threshold) >> 2`循环计算(1.25倍-2倍平滑增长),直到大于或者等于目标容量 +- 如果旧切片>=256,按`newcap+=(newcap + 3*threshold) >> 2`循环计算(1.25倍-2倍平滑增长),直到大于或者等于目标容量 ```go func TestCap(t *testing.T) { - slice := []int{1, 2, 3, 4} - fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) - slice = append(slice, make([]int, 11)...) - fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量 - slice = append(slice, make([]int, 260)...) - fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量 - slice = append(slice, make([]int, 1024)...) - fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // 1.25倍循环扩容 + slice := []int{1, 2, 3, 4} + fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) + slice = append(slice, make([]int, 11)...) + fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量 + slice = append(slice, make([]int, 260)...) + fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量 + slice = append(slice, make([]int, 1024)...) + fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // 1.25倍循环扩容 } ``` 至于实际的结果为什么没有和上述说的一样,可以看到,在`nextslicecap`计算出容量后续,还有对`newcap`的一系列操作,这是内存对齐的一系列计算。 -https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L188 + 逻辑比较复杂,可以进入调试模式跟踪逻辑,这里就不多展开了 @@ -205,8 +203,6 @@ https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/r 而且我按照最新的源码来看,与网上大多数的教程说得也有出入,网上很多人都是直接说的1.25倍增长,也许1.18时是这样的(这个我就没有探究了),在现在1.22已经修改成为了上述的公示,可以在1.25-2倍之间平滑增长。 - - ## 字符串 字符串是一种特殊的切片,在go中是一个不可变的字节序列,和数组不同的是,字符串的元素不可修改,可以从`reflect.StringHeader`看到它的底层结构:由一个len长度与data数组指针组成。 @@ -219,12 +215,12 @@ https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/r ```go func TestString(t *testing.T) { - str := "hello" - fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) - str = "world" - fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) - str = str[:3] - fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + str := "hello" + fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + str = "world" + fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + str = str[:3] + fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) } ``` @@ -234,12 +230,12 @@ func TestString(t *testing.T) { ```go func TestStringAsSlice(t *testing.T) { - str := "hello,world" - hello := str[:5] - world := str[6:] - fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) - fmt.Printf("%d %+v\n", &hello, (*reflect.StringHeader)(unsafe.Pointer(&hello))) - fmt.Printf("%d %+v\n", &world, (*reflect.StringHeader)(unsafe.Pointer(&world))) + str := "hello,world" + hello := str[:5] + world := str[6:] + fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + fmt.Printf("%d %+v\n", &hello, (*reflect.StringHeader)(unsafe.Pointer(&hello))) + fmt.Printf("%d %+v\n", &world, (*reflect.StringHeader)(unsafe.Pointer(&world))) } ``` @@ -247,48 +243,46 @@ func TestStringAsSlice(t *testing.T) { 一些高性能的编程技巧,其实大多都是为了避免内存拷贝而产生的性能消耗,以下是我想到的几种场景,以供参考,也欢迎指教。 +注意:其中使用了`unsafe`包,这个包是不安全的,请谨慎使用。 + ### 零拷贝 在进行string->bytes的转换时使用零拷贝,否则会产生一次内存拷贝,你可以写一个Benchmark来对比一下: ```go func TestCopy(t *testing.T) { - str := "hello,world" - fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) - b := []byte(str) - fmt.Printf("%d %+v\n", unsafe.Pointer(&b), (*reflect.SliceHeader)(unsafe.Pointer(&b))) + str := "hello,world" + fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + b := []byte(str) + fmt.Printf("%d %+v\n", unsafe.Pointer(&b), (*reflect.SliceHeader)(unsafe.Pointer(&b))) } func TestZeroCopy(t *testing.T) { - str := "hello,world" - fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) - b := unsafe.Slice(unsafe.StringData(str), len(str)) - fmt.Printf("%s %+v\n", b, (*reflect.SliceHeader)(unsafe.Pointer(&b))) + str := "hello,world" + fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + b := unsafe.Slice(unsafe.StringData(str), len(str)) + fmt.Printf("%s %+v\n", b, (*reflect.SliceHeader)(unsafe.Pointer(&b))) } func TestConvertZeroCopy(t *testing.T) { - n := []int64{1, 2, 3, 4} - // 注意精度问题 - fmt.Printf("%+v\n", (*reflect.SliceHeader)(unsafe.Pointer(&n))) - f := unsafe.Slice(unsafe.SliceData(n), len(n)) - fmt.Printf("%v %+v\n", f, (*reflect.SliceHeader)(unsafe.Pointer(&f))) + n := []int64{1, 2, 3, 4} + // 注意精度问题 + fmt.Printf("%+v\n", (*reflect.SliceHeader)(unsafe.Pointer(&n))) + f := unsafe.Slice(unsafe.SliceData(n), len(n)) + fmt.Printf("%v %+v\n", f, (*reflect.SliceHeader)(unsafe.Pointer(&f))) } ``` - - ### 字符串高性能替换 ```go func TestReplace(t *testing.T) { - str := "hello,world" - fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str))) - b := []byte(str) - b[1] = 'a' - str = unsafe.String(unsafe.SliceData(b), len(b)) - fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + str := "hello,world" + fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str))) + b := []byte(str) + // 大量字符串操作 + b[1] = 'a' + str = unsafe.String(unsafe.SliceData(b), len(b)) + fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str))) } ``` - - - diff --git a/docs/dev/language/golang/io包.md b/docs/dev/language/golang/io包.md deleted file mode 100644 index fda65d1..0000000 --- a/docs/dev/language/golang/io包.md +++ /dev/null @@ -1 +0,0 @@ -# Golang IO包 diff --git a/docs/note/为什么换掉Wordpress.md b/docs/note/为什么换掉Wordpress.md index cff1294..f28c8cc 100644 --- a/docs/note/为什么换掉Wordpress.md +++ b/docs/note/为什么换掉Wordpress.md @@ -3,7 +3,7 @@ title: 我为什么换掉Wordpress选择Docusaurus? keyword: [笔记, 博客] --- -## 为什么换掉Wordpress? +## 为什么换掉 Wordpress? 在此之前,我一直使用 Wordpress 来做我的博客,最近准备重新开始写,为什么决定换掉它呢? @@ -54,7 +54,7 @@ Docusaurus 是有 blog 功能的,但是 blog 不能支持左侧的目录树, - 增加 Docs 文章时间排序 - 接入 giscus 评论 - 接入 Google Analytics 统计 -- 增加了 Markdown lint(vscode 插件) +- 增加了 Markdown lint 校验与格式化(vscode 插件) 后续或许还会写一个后端,来实现其他更多的功能。