parent
71f300dbca
commit
ac55a0c6a2
@ -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")
|
|
||||||
}
|
|
75
docs/dev/language/golang/code/io_test.go
Normal file
75
docs/dev/language/golang/code/io_test.go
Normal file
@ -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)
|
||||||
|
}
|
@ -2,10 +2,11 @@ package code
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSliceArray(t *testing.T) {
|
func TestSliceArray(t *testing.T) {
|
||||||
|
148
docs/dev/language/golang/go-io.md
Normal file
148
docs/dev/language/golang/go-io.md
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
```
|
@ -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
|
```go
|
||||||
func TestSliceArray(t *testing.T) {
|
func TestSliceArray(t *testing.T) {
|
||||||
var arr = [...]int{1, 2, 3}
|
var arr = [...]int{1, 2, 3}
|
||||||
var slice = []int{1, 2, 3}
|
var slice = []int{1, 2, 3}
|
||||||
ModifyArray(arr)
|
ModifyArray(arr)
|
||||||
ModifySlice(slice)
|
ModifySlice(slice)
|
||||||
t.Logf("%v %v\n", arr, slice)
|
t.Logf("%v %v\n", arr, slice)
|
||||||
assert.NotEqual(t, arr[2], 1)
|
assert.NotEqual(t, arr[2], 1)
|
||||||
assert.Equal(t, slice[2], 1)
|
assert.Equal(t, slice[2], 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ModifyArray(arr [3]int) {
|
func ModifyArray(arr [3]int) {
|
||||||
fmt.Println(arr)
|
fmt.Println(arr)
|
||||||
arr[2] = 1
|
arr[2] = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func ModifySlice(slice []int) {
|
func ModifySlice(slice []int) {
|
||||||
fmt.Println(slice)
|
fmt.Println(slice)
|
||||||
slice[2] = 1
|
slice[2] = 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -41,24 +41,24 @@ func ModifySlice(slice []int) {
|
|||||||
`切片是由:数组指针,长度,容量组成的`,来划一下重点.
|
`切片是由:数组指针,长度,容量组成的`,来划一下重点.
|
||||||
副本传的也是上面这些东西.然后修改切片的时候呢,实际上是通过切片里面的数组指针去修改了,并没有修改切片的值(数组指针,长度,容量).我们可以用unsafe包来看看切片与数组的结构:
|
副本传的也是上面这些东西.然后修改切片的时候呢,实际上是通过切片里面的数组指针去修改了,并没有修改切片的值(数组指针,长度,容量).我们可以用unsafe包来看看切片与数组的结构:
|
||||||
|
|
||||||
```
|
```go
|
||||||
func TestSlice(t *testing.T) {
|
func TestSlice(t *testing.T) {
|
||||||
slice := []int{1, 2, 3}
|
slice := []int{1, 2, 3}
|
||||||
arr := [...]int{1, 2, 3}
|
arr := [...]int{1, 2, 3}
|
||||||
fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n",
|
fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n",
|
||||||
unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice)))
|
unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice)))
|
||||||
SlicePointer(slice)
|
SlicePointer(slice)
|
||||||
fmt.Printf("数组:底层数组指针:%d 数组指针:%d\n", unsafe.Pointer(&arr), unsafe.Pointer(&arr[0]))
|
fmt.Printf("数组:底层数组指针:%d 数组指针:%d\n", unsafe.Pointer(&arr), unsafe.Pointer(&arr[0]))
|
||||||
ArrayPointer(arr)
|
ArrayPointer(arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SlicePointer(slice []int) {
|
func SlicePointer(slice []int) {
|
||||||
fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n",
|
fmt.Printf("切片:底层数组指针:%d 切片:指针%v reflect.SliceHeader: %+v\n",
|
||||||
unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice)))
|
unsafe.SliceData(slice), unsafe.Pointer(&slice), (*reflect.SliceHeader)(unsafe.Pointer(&slice)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ArrayPointer(arr [3]int) {
|
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
|
```go
|
||||||
func Test_DefineSlice(t *testing.T) {
|
func Test_DefineSlice(t *testing.T) {
|
||||||
var arr = [...]int{1, 2, 3}
|
var arr = [...]int{1, 2, 3}
|
||||||
var slice1 = []int{1, 2, 3}
|
var slice1 = []int{1, 2, 3}
|
||||||
var slice2 = make([]int, 3)
|
var slice2 = make([]int, 3)
|
||||||
var slice3 = arr[:]
|
var slice3 = arr[:]
|
||||||
fmt.Printf("arr type=%v len=%d cap=%d\n", reflect.TypeOf(arr).String(), len(arr), cap(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("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("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))
|
fmt.Printf("slice3 type=%v len=%d cap=%d\n", reflect.TypeOf(slice3).String(), len(slice3), cap(slice3))
|
||||||
}
|
}
|
||||||
//Result:
|
//Result:
|
||||||
//arr type=[3]int len=3 cap=3
|
//arr type=[3]int len=3 cap=3
|
||||||
@ -98,17 +98,17 @@ func Test_DefineSlice(t *testing.T) {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func TestModify(t *testing.T) {
|
func TestModify(t *testing.T) {
|
||||||
arr := [...]int{1, 2, 3, 4, 5, 6, 7}
|
arr := [...]int{1, 2, 3, 4, 5, 6, 7}
|
||||||
slice := arr[:]
|
slice := arr[:]
|
||||||
slice[4] = 8
|
slice[4] = 8
|
||||||
t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4])
|
t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4])
|
||||||
assert.Equal(t, slice[4], arr[4])
|
assert.Equal(t, slice[4], arr[4])
|
||||||
slice = append(slice, 9)
|
slice = append(slice, 9)
|
||||||
slice[5] = 10
|
slice[5] = 10
|
||||||
t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4])
|
t.Logf("arr[4]=%v,slice[4]=%v\n", arr[4], slice[4])
|
||||||
assert.Equal(t, slice[4], arr[4])
|
assert.Equal(t, slice[4], arr[4])
|
||||||
t.Logf("arr[5]=%v,slice[5]=%v\n", arr[5], slice[5])
|
t.Logf("arr[5]=%v,slice[5]=%v\n", arr[5], slice[5])
|
||||||
assert.NotEqual(t, slice[5], arr[5])
|
assert.NotEqual(t, slice[5], arr[5])
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -118,12 +118,12 @@ func TestModify(t *testing.T) {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func TestModifyTwoSlice(t *testing.T) {
|
func TestModifyTwoSlice(t *testing.T) {
|
||||||
arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||||
slice1 := arr[1:5]
|
slice1 := arr[1:5]
|
||||||
slice2 := arr[3:8]
|
slice2 := arr[3:8]
|
||||||
slice1[2] = 8
|
slice1[2] = 8
|
||||||
t.Logf("%v %v %v\n", arr, slice1, slice2)
|
t.Logf("%v %v %v\n", arr, slice1, slice2)
|
||||||
assert.Equal(t, slice1[2], slice2[0], arr[3])
|
assert.Equal(t, slice1[2], slice2[0], arr[3])
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -135,12 +135,12 @@ func TestModifyTwoSlice(t *testing.T) {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func Test_Append(t *testing.T) {
|
func Test_Append(t *testing.T) {
|
||||||
slice := []int{1, 2, 3}
|
slice := []int{1, 2, 3}
|
||||||
println(slice)
|
println(slice)
|
||||||
slice = append(slice, 1)
|
slice = append(slice, 1)
|
||||||
println(slice)
|
println(slice)
|
||||||
slice = append(slice, 1)
|
slice = append(slice, 1)
|
||||||
println(slice)
|
println(slice)
|
||||||
}
|
}
|
||||||
// Result:
|
// Result:
|
||||||
// slice type=[]int len=3 cap=3
|
// slice type=[]int len=3 cap=3
|
||||||
@ -159,45 +159,43 @@ func Test_Append(t *testing.T) {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func ModifySlice(slice []int) {
|
func ModifySlice(slice []int) {
|
||||||
slice[2] = 1
|
slice[2] = 1
|
||||||
slice = append(slice, 4)
|
slice = append(slice, 4)
|
||||||
slice[2] = 3
|
slice[2] = 3
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
我把之前的`ModifySlice`方法修改了一下,然后成员没加,后面再修改回去为 3 也没有发生变化了.
|
我把之前的`ModifySlice`方法修改了一下,然后成员没加,后面再修改回去为 3 也没有发生变化了.
|
||||||
这是因为 append 的时候因为容量不够扩容了,导致底层数组指针发生了改变,但是传进来的切片是外面切片的副本,修改这个切片里面的数组指针不会影响到外面的切片
|
这是因为 append 的时候因为容量不够扩容了,导致底层数组指针发生了改变,但是传进来的切片是外面切片的副本,修改这个切片里面的数组指针不会影响到外面的切片
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 切片扩容
|
## 切片扩容
|
||||||
|
|
||||||
在1.18之前,切片的扩容是原来的2倍,但是当容量超过1024时,每次容量变成原来的1.25倍,直到大于期望容量。在1.18后更换了新的机制:
|
在1.18之前,切片的扩容是原来的2倍,但是当容量超过1024时,每次容量变成原来的1.25倍,直到大于期望容量。在1.18后更换了新的机制:
|
||||||
|
|
||||||
https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L267
|
<https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L267>
|
||||||
|
|
||||||
- 当新切片>旧切片*2时,直接安装新切片容量计算
|
- 当新切片>旧切片\*2时,直接安装新切片容量计算
|
||||||
- 如果旧切片\<256,新切片容量为旧切片\*2
|
- 如果旧切片\<256,新切片容量为旧切片\*2
|
||||||
- 如果旧切片>=256,按` newcap+=(newcap + 3*threshold) >> 2`循环计算(1.25倍-2倍平滑增长),直到大于或者等于目标容量
|
- 如果旧切片>=256,按`newcap+=(newcap + 3*threshold) >> 2`循环计算(1.25倍-2倍平滑增长),直到大于或者等于目标容量
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
||||||
func TestCap(t *testing.T) {
|
func TestCap(t *testing.T) {
|
||||||
slice := []int{1, 2, 3, 4}
|
slice := []int{1, 2, 3, 4}
|
||||||
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice))
|
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice))
|
||||||
slice = append(slice, make([]int, 11)...)
|
slice = append(slice, make([]int, 11)...)
|
||||||
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量
|
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量
|
||||||
slice = append(slice, make([]int, 260)...)
|
slice = append(slice, make([]int, 260)...)
|
||||||
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量
|
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // oldcap<256,直接使用新切片容量
|
||||||
slice = append(slice, make([]int, 1024)...)
|
slice = append(slice, make([]int, 1024)...)
|
||||||
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // 1.25倍循环扩容
|
fmt.Printf("len:%d cap: %d\n", len(slice), cap(slice)) // 1.25倍循环扩容
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
至于实际的结果为什么没有和上述说的一样,可以看到,在`nextslicecap`计算出容量后续,还有对`newcap`的一系列操作,这是内存对齐的一系列计算。
|
至于实际的结果为什么没有和上述说的一样,可以看到,在`nextslicecap`计算出容量后续,还有对`newcap`的一系列操作,这是内存对齐的一系列计算。
|
||||||
|
|
||||||
https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/runtime/slice.go#L188
|
<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倍之间平滑增长。
|
而且我按照最新的源码来看,与网上大多数的教程说得也有出入,网上很多人都是直接说的1.25倍增长,也许1.18时是这样的(这个我就没有探究了),在现在1.22已经修改成为了上述的公示,可以在1.25-2倍之间平滑增长。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 字符串
|
## 字符串
|
||||||
|
|
||||||
字符串是一种特殊的切片,在go中是一个不可变的字节序列,和数组不同的是,字符串的元素不可修改,可以从`reflect.StringHeader`看到它的底层结构:由一个len长度与data数组指针组成。
|
字符串是一种特殊的切片,在go中是一个不可变的字节序列,和数组不同的是,字符串的元素不可修改,可以从`reflect.StringHeader`看到它的底层结构:由一个len长度与data数组指针组成。
|
||||||
@ -219,12 +215,12 @@ https://github.com/golang/go/blob/27f41bb15391668fa8ba18561efe364bab9b8312/src/r
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func TestString(t *testing.T) {
|
func TestString(t *testing.T) {
|
||||||
str := "hello"
|
str := "hello"
|
||||||
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
||||||
str = "world"
|
str = "world"
|
||||||
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
||||||
str = str[:3]
|
str = str[:3]
|
||||||
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -234,12 +230,12 @@ func TestString(t *testing.T) {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func TestStringAsSlice(t *testing.T) {
|
func TestStringAsSlice(t *testing.T) {
|
||||||
str := "hello,world"
|
str := "hello,world"
|
||||||
hello := str[:5]
|
hello := str[:5]
|
||||||
world := str[6:]
|
world := str[6:]
|
||||||
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
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", &hello, (*reflect.StringHeader)(unsafe.Pointer(&hello)))
|
||||||
fmt.Printf("%d %+v\n", &world, (*reflect.StringHeader)(unsafe.Pointer(&world)))
|
fmt.Printf("%d %+v\n", &world, (*reflect.StringHeader)(unsafe.Pointer(&world)))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -247,48 +243,46 @@ func TestStringAsSlice(t *testing.T) {
|
|||||||
|
|
||||||
一些高性能的编程技巧,其实大多都是为了避免内存拷贝而产生的性能消耗,以下是我想到的几种场景,以供参考,也欢迎指教。
|
一些高性能的编程技巧,其实大多都是为了避免内存拷贝而产生的性能消耗,以下是我想到的几种场景,以供参考,也欢迎指教。
|
||||||
|
|
||||||
|
注意:其中使用了`unsafe`包,这个包是不安全的,请谨慎使用。
|
||||||
|
|
||||||
### 零拷贝
|
### 零拷贝
|
||||||
|
|
||||||
在进行string->bytes的转换时使用零拷贝,否则会产生一次内存拷贝,你可以写一个Benchmark来对比一下:
|
在进行string->bytes的转换时使用零拷贝,否则会产生一次内存拷贝,你可以写一个Benchmark来对比一下:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func TestCopy(t *testing.T) {
|
func TestCopy(t *testing.T) {
|
||||||
str := "hello,world"
|
str := "hello,world"
|
||||||
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
||||||
b := []byte(str)
|
b := []byte(str)
|
||||||
fmt.Printf("%d %+v\n", unsafe.Pointer(&b), (*reflect.SliceHeader)(unsafe.Pointer(&b)))
|
fmt.Printf("%d %+v\n", unsafe.Pointer(&b), (*reflect.SliceHeader)(unsafe.Pointer(&b)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZeroCopy(t *testing.T) {
|
func TestZeroCopy(t *testing.T) {
|
||||||
str := "hello,world"
|
str := "hello,world"
|
||||||
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
fmt.Printf("%d %+v\n", &str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
||||||
b := unsafe.Slice(unsafe.StringData(str), len(str))
|
b := unsafe.Slice(unsafe.StringData(str), len(str))
|
||||||
fmt.Printf("%s %+v\n", b, (*reflect.SliceHeader)(unsafe.Pointer(&b)))
|
fmt.Printf("%s %+v\n", b, (*reflect.SliceHeader)(unsafe.Pointer(&b)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConvertZeroCopy(t *testing.T) {
|
func TestConvertZeroCopy(t *testing.T) {
|
||||||
n := []int64{1, 2, 3, 4}
|
n := []int64{1, 2, 3, 4}
|
||||||
// 注意精度问题
|
// 注意精度问题
|
||||||
fmt.Printf("%+v\n", (*reflect.SliceHeader)(unsafe.Pointer(&n)))
|
fmt.Printf("%+v\n", (*reflect.SliceHeader)(unsafe.Pointer(&n)))
|
||||||
f := unsafe.Slice(unsafe.SliceData(n), len(n))
|
f := unsafe.Slice(unsafe.SliceData(n), len(n))
|
||||||
fmt.Printf("%v %+v\n", f, (*reflect.SliceHeader)(unsafe.Pointer(&f)))
|
fmt.Printf("%v %+v\n", f, (*reflect.SliceHeader)(unsafe.Pointer(&f)))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 字符串高性能替换
|
### 字符串高性能替换
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func TestReplace(t *testing.T) {
|
func TestReplace(t *testing.T) {
|
||||||
str := "hello,world"
|
str := "hello,world"
|
||||||
fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
||||||
b := []byte(str)
|
b := []byte(str)
|
||||||
b[1] = 'a'
|
// 大量字符串操作
|
||||||
str = unsafe.String(unsafe.SliceData(b), len(b))
|
b[1] = 'a'
|
||||||
fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
str = unsafe.String(unsafe.SliceData(b), len(b))
|
||||||
|
fmt.Printf("%v %+v\n", str, (*reflect.StringHeader)(unsafe.Pointer(&str)))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
# Golang IO包
|
|
@ -3,7 +3,7 @@ title: 我为什么换掉Wordpress选择Docusaurus?
|
|||||||
keyword: [笔记, 博客]
|
keyword: [笔记, 博客]
|
||||||
---
|
---
|
||||||
|
|
||||||
## 为什么换掉Wordpress?
|
## 为什么换掉 Wordpress?
|
||||||
|
|
||||||
在此之前,我一直使用 Wordpress 来做我的博客,最近准备重新开始写,为什么决定换掉它呢?
|
在此之前,我一直使用 Wordpress 来做我的博客,最近准备重新开始写,为什么决定换掉它呢?
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ Docusaurus 是有 blog 功能的,但是 blog 不能支持左侧的目录树,
|
|||||||
- 增加 Docs 文章时间排序
|
- 增加 Docs 文章时间排序
|
||||||
- 接入 giscus 评论
|
- 接入 giscus 评论
|
||||||
- 接入 Google Analytics 统计
|
- 接入 Google Analytics 统计
|
||||||
- 增加了 Markdown lint(vscode 插件)
|
- 增加了 Markdown lint 校验与格式化(vscode 插件)
|
||||||
|
|
||||||
后续或许还会写一个后端,来实现其他更多的功能。
|
后续或许还会写一个后端,来实现其他更多的功能。
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user