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

3 回复

你好,

这是来自 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))
}

关键修改点

  1. 避免在遍历中修改map:使用切片keys预先存储所有键,然后遍历这个切片
  2. 延迟添加操作:将要添加的条目先存储在pendingAdditions切片中,遍历完成后再统一添加
  3. 修复逻辑错误:在第二个分支中使用正确的添加逻辑qaPair[one] = two
  4. 移除不必要的并发:直接在主goroutine中执行所有操作

这样修改后,map的追加操作会正常生效,程序也不会提前终止,配对数量会正确反映所有添加的条目。

回到顶部