IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 【Go实战】如何正确使用切片数据 -> 正文阅读

[JavaScript知识库]【Go实战】如何正确使用切片数据

这篇文章是为了给自己烙印上正确使用切片的印记,并加深对切片地址的理解。原因是线上代码产生的结果不符合预期,排查下来,是因为对slice的理解不够,采用了错误的用法,导致bug出现,因此记录下来,并修复这个问题。

先上代码,以下是线上代码的简单还原,输出结果被分割成了三部分,以下用第一部分,第二部分,第三部分来命名各部分数据。

type SimpleStruct struct {
	ID string
	A  int
	B  int
}

func TestUnexpectedFun(t *testing.T) {
	var dataArr = []SimpleStruct{
		{"a", 1, 2},
		{"b", 3, 4},
		{"c", 5, 6},
		{"d", 7, 8},
	}

	t.Logf("ptr of dataArr %p", &dataArr)
	for _, v := range dataArr {
		t.Logf("id=%s, %p", v.ID, &v)
	}

	t.Log("====================================")

	var dataMap = make(map[string]*SimpleStruct)
	var wg sync.WaitGroup
	var mu sync.RWMutex
	wg.Add(1)
	go func() {
		defer wg.Done()
		mu.Lock()
		t.Logf("ptr of dataArr %p", &dataArr)
		for _, v := range dataArr {
			t.Logf("id=%s, %p", v.ID, &v)
			if _, ok := dataMap[v.ID]; !ok {
				dataMap[v.ID] = &v
				continue
			}
			dataMap[v.ID].A = v.A
			dataMap[v.ID].B = v.B
		}
		mu.Unlock()
	}()
	wg.Wait()

	t.Log("====================================")
	t.Logf("ptr of dataMap %p", &dataMap)
	for id, v := range dataMap {
		t.Logf("id->%p:%+v", &v, v)
	}
}

输出结果:

=== RUN   TestUnexpectedFun
    req_test.go:94: ptr of dataArr 0xc000004a50
    req_test.go:96: id=a, 0xc000048a40
    req_test.go:96: id=b, 0xc000048a40
    req_test.go:96: id=c, 0xc000048a40
    req_test.go:96: id=d, 0xc000048a40
    req_test.go:99: ====================================
    req_test.go:108: ptr of dataArr 0xc000004a50
    req_test.go:110: id=a, 0xc000048b40
    req_test.go:110: id=b, 0xc000048b40
    req_test.go:110: id=c, 0xc000048b40
    req_test.go:110: id=d, 0xc000048b40
    req_test.go:122: ====================================
    req_test.go:123: ptr of dataMap 0xc000006628
    req_test.go:125: id:a->0xc000006630:&{ID:d A:7 B:8}
    req_test.go:125: id:b->0xc000006630:&{ID:d A:7 B:8}
    req_test.go:125: id:c->0xc000006630:&{ID:d A:7 B:8}
    req_test.go:125: id:d->0xc000006630:&{ID:d A:7 B:8}
--- PASS: TestUnexpectedFun (0.00s)
PASS

从第三部分最终输出的结果来看,我们拿到的数据都是异常的,所有数据都赋值了id=d的数据。

这个问题的原因很好理解,是因为我们赋值给dataMap的数是dataArr里数据的地址,但是dataArr里每个元素的地址都一样,所以最后取到的数据都相同。这篇文章go语言关于切片类型内存地址的理解很好的解释了这个问题。

这个问题有两种解决办法:
1、改变源数据dataArr的数据类型,由 []SimpleStruct 改为 []*SimpleStruct,然后修改dataMap的赋值方式,由 dataMap[v.ID] = &v 改为 dataMap[v.ID] = v,输出结果如下,虽然各个元素的地址仍然一样,但是改变了dataMap的赋值方式,可以有效的解决该问题

=== RUN   TestUnexpectedFun
    req_test.go:94: ptr of dataArr 0xc00009ca38
    req_test.go:96: id=a, 0xc0000c4618
    req_test.go:96: id=b, 0xc0000c4618
    req_test.go:96: id=c, 0xc0000c4618
    req_test.go:96: id=d, 0xc0000c4618
    req_test.go:99: ====================================
    req_test.go:108: ptr of dataArr 0xc00009ca38
    req_test.go:110: id=a, 0xc0000c4628
    req_test.go:110: id=b, 0xc0000c4628
    req_test.go:110: id=c, 0xc0000c4628
    req_test.go:110: id=d, 0xc0000c4628
    req_test.go:122: ====================================
    req_test.go:123: ptr of dataMap 0xc0000c4620
    req_test.go:125: id:a->0xc0000c4630:&{ID:a A:1 B:2}
    req_test.go:125: id:b->0xc0000c4630:&{ID:b A:3 B:4}
    req_test.go:125: id:c->0xc0000c4630:&{ID:c A:5 B:6}
    req_test.go:125: id:d->0xc0000c4630:&{ID:d A:7 B:8}
--- PASS: TestUnexpectedFun (0.00s)
PASS

2、不改变源数据dataArr的数据类型,只改变dataMap的赋值方式,每个dataMap的元素都重新实例化,然后再赋值,代码如下

if _, ok := dataMap[v.ID]; !ok {
    dataMap[v.ID] = &SimpleStruct{}
}
dataMap[v.ID].ID = v.ID
dataMap[v.ID].A = v.A
dataMap[v.ID].B = v.B

输出结果如下:

=== RUN   TestUnexpectedFun
    req_test.go:95: ptr of dataArr 0xc000004a50
    req_test.go:97: id=a, 0xc000048a40
    req_test.go:97: id=b, 0xc000048a40
    req_test.go:97: id=c, 0xc000048a40
    req_test.go:97: id=d, 0xc000048a40
    req_test.go:97: id=b, 0xc000048a40
    req_test.go:100: ====================================
    req_test.go:109: ptr of dataArr 0xc000004a50
    req_test.go:111: id=a, 0xc000048b60
    req_test.go:111: id=b, 0xc000048b60
    req_test.go:111: id=c, 0xc000048b60
    req_test.go:111: id=d, 0xc000048b60
    req_test.go:111: id=b, 0xc000048b60
    req_test.go:123: ====================================
    req_test.go:124: ptr of dataMap 0xc000006628
    req_test.go:126: id:b->0xc000006630:&{ID:b A:4 B:5}
    req_test.go:126: id:c->0xc000006630:&{ID:c A:5 B:6}
    req_test.go:126: id:d->0xc000006630:&{ID:d A:7 B:8}
    req_test.go:126: id:a->0xc000006630:&{ID:a A:1 B:2}
--- PASS: TestUnexpectedFun (0.00s)
PASS

可以看到,结果跟方法1是一样的。

以上两种处理方案,看个人喜好选择采用。个人更倾向于第一种方案,从踩坑来看,如果时间长了忘记了踩过的这个坑,写了同样的逻辑,那么新代码就不会产生结果与预期不符的问题。

个人建议:如果你有一个好的习惯,那一定不要为了迎合,而放弃了你这个好习惯。

就像这个bug的产生一样,往常我的一贯写法都是第一种方案,但是因为某个同学说第一种写法产生的源数据,容易在后期被修改,所以我把写法改成了第二种,但是这种临时变量的修改是可控的,需要修改时才会被修改。而如果我保持了原来的代码风格,那这个坑就可以避免。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-04 15:27:10  更:2022-03-04 15:27:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:52:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码