前言:很久没写手记,不是不想写,而是不知道写什么。虽然这段时间进步很快,也学到了很多东西,但是一下笔就发现将要分享的内容没有什么新意,完全是炒冷饭,也就放弃了。不过太久没写,总觉得生活中缺少了一些东西,会有种学了一堆没用知识的错觉,因此偶尔分享下,也算对自己学习的鞭策。
今天分享的内容是go语言并发操作文件的代码。经过这段时间的学习,个人觉得go语言优雅轻量,有很多思想和设计是javascript中所欠缺的。底层设计特别是并发机制基本上是javascript完全缺乏的,因此本篇分享这个内容,以下是正文:
package main import ( "fmt" "runtime" "path" "os" "bufio" "strings" "sync" ) //将文件相对路径转化为绝对路径 func getFileAbsPath3(relativePath ... string) []string { var ret = []string{} _, file, _, _ := runtime.Caller(1) for _, dir := range relativePath { var filePath = path.Join(path.Dir(file), dir) ret = append(ret, filePath) } return ret } func filterRepeat3() { var absPaths = getFileAbsPath3("./2.txt", "./3.txt", "./4.txt") //设置一个并发控制wgp wgp := sync.WaitGroup{} //设置一个存储map var valuesMap map[string]int = map[string]int{} for _, dir := range absPaths { wgp.Add(1) //并发打开多个文件并读取文件内容 go func(filePath string) { fmt.Println("并发打开文件的顺序为: ", filePath) file, error := os.Open(filePath) //此处一定要记住close,否则可能会导致文件描述符用完 defer file.Close() if (error != nil) { fmt.Println("error: ", error) } //此处建议使用scanner,如果使用NewReader还需要处理空行和只有一行的情况,不推荐 scanner := bufio.NewScanner(file) //逐行扫描,知道直接或出错 for scanner.Scan() { str := scanner.Text() str = strings.TrimSpace(str) valuesMap[str] += 1 } wgp.Add(-1) }(dir) } //等待并发程序全部结束 wgp.Wait() for key, value := range valuesMap { if value > 1 { fmt.Println("找到重复行,重复的行值为:", key, "重复次数为:", value) } } fmt.Println(valuesMap) } func main() { filterRepeat3() }
这个版本看上去很清晰,代码也比较简单,但是这是一个有问题的代码,问题就出在go语言是并发变成的语言,可能会报错go fatal error: concurrent map writes(同时写或者读的时候在写)。如何处理呢?两种方式,一种是采用go语言锁,sync.mutex。一种是采用通道,串行写入。这里我采用锁的方式,将读和写都锁定,这样就避免了并发导致的读写冲突问题,代码如下:
package main import ( "bufio" "fmt" "os" "path" "runtime" "strings" "sync" ) //将文件相对路径转化为绝对路径 func getFileAbsPath3(relativePath ...string) []string { var ret = []string{} _, file, _, _ := runtime.Caller(1) for _, dir := range relativePath { var filePath = path.Join(path.Dir(file), dir) ret = append(ret, filePath) } return ret } var valuesMap = map[string]int{} var mutex sync.Mutex //加上读写锁,否则会出现同时读写的冲突 func write(k string) { mutex.Lock() defer mutex.Unlock() valuesMap[k] += 1 } func filterRepeat3() { var absPaths = getFileAbsPath3("./2.txt", "./3.txt", "./4.txt") //设置一个并发控制wgp wgp := sync.WaitGroup{} //设置一个存储map for _, dir := range absPaths { wgp.Add(1) //并发打开多个文件并读取文件内容 go func(filePath string) { fmt.Println("并发打开文件的顺序为: ", filePath) file, error := os.Open(filePath) //此处一定要记住close,否则可能会导致文件描述符用完 defer file.Close() if error != nil { fmt.Println("error: ", error) } //此处建议使用scanner,如果使用NewReader还需要处理空行和只有一行的情况,不推荐 scanner := bufio.NewScanner(file) //逐行扫描,知道直接或出错 for scanner.Scan() { str := scanner.Text() str = strings.TrimSpace(str) write(str) } wgp.Add(-1) }(dir) } //等待并发程序全部结束 wgp.Wait() for key, value := range valuesMap { if value > 1 { fmt.Println("找到重复行,重复的行值为:", key, "重复次数为:", value) } } fmt.Println(valuesMap) } func main() { for i := 0; i < 5000; i++ { filterRepeat3() } }
代码并不复杂,有些稍微需要描述的地方都有代码注释。另外最后推荐两本go语言书籍,特别值得一看:《go并发编程实战第二版》《go程序设计语言》
thx~