Golang中map追加操作无效的问题及反向解决方法
Golang中map追加操作无效的问题及反向解决方法
以下代码从映射中读取并打印内容,等待扫描用户输入。如果用户输入是"?",则向映射添加内容。除非使用qaPair[two] = one这样的写法,否则添加操作似乎无法生效,而我需要在某个if代码块中使用qaPair[one] = two的写法,在另一个代码块中使用前者。
有时在使用用户输入添加内容后,程序会提前终止。例如,当配对数量为13时(在10的基础上增加了3),有时会在12/13处终止。
能否有人帮我分析一下代码逻辑?我似乎无法理解这些异常现象的原因
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"math/rand"
"os"
"os/exec"
"runtime"
"time"
"github.com/fatih/color"
)
var (
flagNoColor = flag.Bool("no-color", false, "disable color output")
csvPath = flag.String("src", "src.csv", "source")
lang = flag.Int("l", -1, "language")
num = flag.Int("n", -1, "number")
)
var clear map[string]func()
func init() {
clear = make(map[string]func())
clear["linux"] = func() {
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
cmd.Run()
}
clear["windows"] = func() {
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
cmd.Run()
}
}
func CallClear() {
value, ok := clear[runtime.GOOS]
if ok {
value()
} else {
panic("Panic!")
}
}
func random(min int, max int) int {
return rand.Intn((max-min) + 1) + min
}
func main() {
flag.Parse()
file, err := os.Open(*csvPath)
if err != nil {
log.Println(err)
return
}
defer file.Close()
csvReader := csv.NewReader(file)
csvData, err := csvReader.ReadAll()
if err != nil {
log.Println(err)
return
}
qaRaw := make(map[string]string, len(csvData))
for _, data := range csvData {
qaRaw[data[0]] = data[1]
}
qaPair := make(map[string]string)
if *num != -1 {
i := 1
for one, two := range qaRaw {
if i <= *num {
qaPair[one] = two
}
i++
}
} else {
for one, two := range qaRaw {
qaPair[one] = two
}
}
done := make(chan bool)
go func() {
if *flagNoColor {
color.NoColor = true
}
white := color.New(color.FgWhite)
boldWhite := white.Add(color.Bold)
qNum := 0
for one, two := range qaPair {
rand.Seed(time.Now().UnixNano())
randomNum := random(1, 2)
CallClear()
if *lang != -1 {
randomNum = *lang
}
qNum++
var userInput string
boldWhite.Printf("\n #\t%d / %d\n", qNum, len(qaPair))
if randomNum == 1 {
boldWhite.Printf("\n Q\t%s", one)
fmt.Scanln()
boldWhite.Printf("\n A\t%s", two)
fmt.Scanln(&userInput)
if userInput == "?" {
qaPair[two] = one
}
} else if randomNum == 2 {
boldWhite.Printf("\n Q\t%s", two)
fmt.Scanln()
boldWhite.Printf("\n A\t%s", one)
fmt.Scanln(&userInput)
if userInput == "?" {
qaPair[two] = one
}
}
CallClear()
}
done <- true
}()
select {
case <-done:
}
}
这是一个包含示例数据的.csv文件
"hola","hi"
"sí","yes"
"¿qué pasa?","what's up?"
"¡vamos!","let's go!"
"¡salud!","cheers!; bless you!"
"no","no"
"por favor","please"
"lo siento","I'm sorry"
"buenos días","good morning"
"buenas noches","good night"
更多关于Golang中map追加操作无效的问题及反向解决方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你好,
这是来自 Go 语言规范的内容:
“……一个 nil map 等同于一个空 map,区别在于不能向其添加任何元素……”
更多关于Golang中map追加操作无效的问题及反向解决方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我不太确定您的意思/期望是什么,而且示例有点长。如果在 play.golang.org 上提供一个简短的复现示例会更好。
一般来说,您不会对映射进行"追加"操作。映射包含键值对——为某个键设置值会覆盖之前的值(如果存在),如果不存在则会创建新的键值对。
在分析你的代码后,我发现了几个关键问题导致map追加操作无效和程序提前终止:
问题分析
1. 在遍历过程中修改map导致的问题
当你在for one, two := range qaPair循环中执行qaPair[two] = one时,实际上是在遍历过程中修改map,这在Go中会导致未定义行为,包括:
- 新添加的条目可能不会被遍历到
- 遍历可能提前终止
- 可能引发并发访问panic
2. 并发安全问题
你在goroutine中修改map,而主goroutine在等待,这存在数据竞争。
3. 逻辑错误
在两个分支中都使用了相同的添加逻辑qaPair[two] = one,这不符合你的需求。
修复方案
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"math/rand"
"os"
"os/exec"
"runtime"
"time"
"github.com/fatih/color"
)
var (
flagNoColor = flag.Bool("no-color", false, "disable color output")
csvPath = flag.String("src", "src.csv", "source")
lang = flag.Int("l", -1, "language")
num = flag.Int("n", -1, "number")
)
var clear map[string]func()
func init() {
clear = make(map[string]func())
clear["linux"] = func() {
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
cmd.Run()
}
clear["windows"] = func() {
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
cmd.Run()
}
}
func CallClear() {
value, ok := clear[runtime.GOOS]
if ok {
value()
} else {
panic("Panic!")
}
}
func random(min int, max int) int {
return rand.Intn(max-min+1) + min
}
func main() {
flag.Parse()
file, err := os.Open(*csvPath)
if err != nil {
log.Println(err)
return
}
defer file.Close()
csvReader := csv.NewReader(file)
csvData, err := csvReader.ReadAll()
if err != nil {
log.Println(err)
return
}
qaRaw := make(map[string]string, len(csvData))
for _, data := range csvData {
qaRaw[data[0]] = data[1]
}
qaPair := make(map[string]string)
if *num != -1 {
i := 1
for one, two := range qaRaw {
if i <= *num {
qaPair[one] = two
}
i++
}
} else {
for one, two := range qaRaw {
qaPair[one] = two
}
}
// 使用切片来记录需要添加的新条目,避免在遍历中修改map
type pair struct {
key string
value string
}
pendingAdditions := make([]pair, 0)
if *flagNoColor {
color.NoColor = true
}
white := color.New(color.FgWhite)
boldWhite := white.Add(color.Bold)
qNum := 0
originalLength := len(qaPair)
// 先遍历原始内容
keys := make([]string, 0, len(qaPair))
for k := range qaPair {
keys = append(keys, k)
}
for _, key := range keys {
two := qaPair[key]
one := key
rand.Seed(time.Now().UnixNano())
randomNum := random(1, 2)
CallClear()
if *lang != -1 {
randomNum = *lang
}
qNum++
var userInput string
boldWhite.Printf("\n #\t%d / %d\n", qNum, originalLength)
if randomNum == 1 {
boldWhite.Printf("\n Q\t%s", one)
fmt.Scanln()
boldWhite.Printf("\n A\t%s", two)
fmt.Scanln(&userInput)
if userInput == "?" {
// 记录要添加的条目,稍后处理
pendingAdditions = append(pendingAdditions, pair{key: two, value: one})
}
} else if randomNum == 2 {
boldWhite.Printf("\n Q\t%s", two)
fmt.Scanln()
boldWhite.Printf("\n A\t%s", one)
fmt.Scanln(&userInput)
if userInput == "?" {
// 这里应该使用 qaPair[one] = two
pendingAdditions = append(pendingAdditions, pair{key: one, value: two})
}
}
CallClear()
}
// 在处理完所有原始条目后,添加新的条目
for _, p := range pendingAdditions {
qaPair[p.key] = p.value
}
fmt.Printf("最终配对数量: %d\n", len(qaPair))
}
关键修改点
- 避免在遍历中修改map:使用切片
keys预先存储所有键,然后遍历这个切片 - 延迟添加操作:将要添加的条目先存储在
pendingAdditions切片中,遍历完成后再统一添加 - 修复逻辑错误:在第二个分支中使用正确的添加逻辑
qaPair[one] = two - 移除不必要的并发:直接在主goroutine中执行所有操作
这样修改后,map的追加操作会正常生效,程序也不会提前终止,配对数量会正确反映所有添加的条目。

