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 (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
<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
|
||||
<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)))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
# Golang IO包
|
@ -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 插件)
|
||||
|
||||
后续或许还会写一个后端,来实现其他更多的功能。
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user