=Start=
缘由:
每学习一门新语言,我就会拿刚学会的语法去实现一些小功能/程序,以此加深对语法的认识和熟悉程度。这其中比较好玩又实用的就包括统计文件中单词的个数,涉及到文件操作、字符串操作、去重操作等,是一个比较好的学习案例。
参考解答:
废话不多说,代码如下:
package main import ( "bufio" "fmt" "io" "log" "os" "path/filepath" "runtime" "sort" "strings" "unicode" "unicode/utf8" ) func main() { if len(os.Args) == 1 || os.Args[1] == "-h" || os.Args[1] == "--help" { fmt.Printf("usage: %s <file1> [<file2> [... <fileN>]]\n", filepath.Base(os.Args[0])) os.Exit(1) } frequencyForWord := map[string]int{} // Same as: make(map[string]int) for _, filename := range commandLineFiles(os.Args[1:]) { updateFrequencies(filename, frequencyForWord) } // fmt.Println(frequencyForWord) // reportByWords(frequencyForWord) wordsForFrequency := invertStringIntMap(frequencyForWord) // fmt.Println(wordsForFrequency) reportByFrequency(wordsForFrequency) } func commandLineFiles(files []string) []string { if runtime.GOOS == "windows" { args := make([]string, 0, len(files)) for _, name := range files { if matches, err := filepath.Glob(name); err != nil { args = append(args, name) // Invalid pattern } else if matches != nil { // At least one match args = append(args, matches...) } } return args } return files } func updateFrequencies(filename string, frequencyForWord map[string]int) { var file *os.File var err error if file, err = os.Open(filename); err != nil { log.Println("failed to open the file: ", err) return } defer file.Close() reader := bufio.NewReader(file) for { line, err := reader.ReadString('\n') for _, word := range SplitOnNonLetters(strings.TrimSpace(line)) { if len(word) > utf8.UTFMax || utf8.RuneCountInString(word) > 1 { frequencyForWord[strings.ToLower(word)] += 1 } } if err != nil { if err != io.EOF { log.Println("failed to finish reading the file: ", err) } break } } } // SplitOnNonLetters() 对 s 用 非字母字符 进行切分,返回 s 中包含的单词列表 func SplitOnNonLetters(s string) []string { notALetter := func(char rune) bool { return !unicode.IsLetter(char) } return strings.FieldsFunc(s, notALetter) } func reportByWords(frequencyForWord map[string]int) { words := make([]string, 0, len(frequencyForWord)) wordWidth, frequencyWidth := 0, 0 for word, frequency := range frequencyForWord { words = append(words, word) if width := utf8.RuneCountInString(word); width > wordWidth { wordWidth = width } if width := len(fmt.Sprint(frequency)); width > frequencyWidth { frequencyWidth = width } } sort.Strings(words) gap := wordWidth + frequencyWidth - len("Word") - len("Frequency") fmt.Printf("Word %*s%s\n", gap, " ", "Frequency") // fmtp.Printf 的 %*s 接收两个参数——最大宽度 & 要打印的字符串 for _, word := range words { fmt.Printf("%-*s %*d\n", wordWidth, word, frequencyWidth, frequencyForWord[word]) } } // 对 map[string][int] 进行反转时,需要注意 int 可能会有相同大小,而 string 不应该被覆盖,而应该被添加 // 所以反转之后的类型为 map[int][]string func invertStringIntMap(stringIntMap map[string]int) map[int][]string { intStrArrayMap := make(map[int][]string, len(stringIntMap)) for key, value := range stringIntMap { intStrArrayMap[value] = append(intStrArrayMap[value], key) } return intStrArrayMap } func reportByFrequency(wordsForFrequency map[int][]string) { frequencies := make([]int, 0, len(wordsForFrequency)) // length = 0, capacity = len(wordsForFrequency) for frequency := range wordsForFrequency { frequencies = append(frequencies, frequency) } sort.Ints(frequencies) width := len(fmt.Sprint(frequencies[len(frequencies)-1])) // 因为 frequencies 已经是排过序了的,所以最后一个元素是最宽的 fmt.Println("Frequency → Words") for _, frequency := range frequencies { words := wordsForFrequency[frequency] sort.Strings(words) fmt.Printf("%*d %s\n", width, frequency, strings.Join(words, ", ")) } }
参考链接:
- 《Go语言程序设计》
=EOF=
《 “用Go统计文件中单词的个数” 》 有 11 条评论
Go 的三种不同 md5 计算方式性能比较
http://holys.im/2016/11/24/3-kind-of-md5-sum/
`
ioutil.ReadFile
io.Copy
io.Copy + bufio.Reader
以上这三种不同的 md5 计算方式在执行时间上都差不多,区别最大的是内存的分配上;
bufio 在处理 I/O 还是很有优势的,优先选择;
尽量避免 ReadAll 这种用法。
`
探测局域网里面的设备
http://blog.cyeam.com/network/2015/03/16/fing
https://github.com/mnhkahn/go_code/blob/master/fing.go
Golang实现多线程并发下载
http://blog.cyeam.com/network/2015/07/02/goget
https://github.com/mnhkahn/go_code/blob/master/goget/goget.go
使用Go编写代码明信片生成器
http://cjting.me/golang/write-a-code-post-generator-with-go/
https://github.com/fate-lovely/codeposter
用Go开发可以内网活跃主机嗅探器 #1
https://github.com/timest/goscan/issues/1
`
程序思路:
通过内网IP和子网掩码计算出内网IP范围
向内网广播ARP Request
监听并抓取ARP Response包,记录IP和Mac地址
发活跃IP发送MDNS和NBNS包,并监听和解析Hostname
根据Mac地址计算出厂家信息
`
[译]使用os/exec执行命令
http://colobu.com/2017/06/19/advanced-command-execution-in-Go-with-os-exec/
https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html
https://github.com/kjk/go-cookbook/blob/master/advanced-exec
udp编程的那些事与golang udp的实践
https://my.oschina.net/u/2374678/blog/983427
用go实现常用算法与数据结构——跳跃表(Skip list)
http://www.cnblogs.com/DilonWu/p/8857061.html
https://github.com/AceDarkknight/ConcurrentSkipList
golang bufio、ioutil读文件的速度比较(性能测试)和影响因素分析
https://segmentfault.com/a/1190000011680507
`
当每次读取块的大小小于4KB,建议使用bufio.NewReader(f), 大于4KB用bufio.NewReaderSize(f,缓存大小)
要读Reader, 图方便用ioutil.ReadAll()
一次性读取文件,使用ioutil.ReadFile()
不同业务场景,选用不同的读取方式
`
Golang学习 – bufio 包
http://www.cnblogs.com/golove/p/3282667.html
Golang学习 – io 包
http://www.cnblogs.com/golove/p/3276678.html
数据说话:Go语言的Switch和Map性能实测
https://segmentfault.com/a/1190000011361164
https://hashrocket.com/blog/posts/switch-vs-map-which-is-the-better-way-to-branch-in-go
Filebeat优化实践
https://wilhelmguo.tk/blog/post/william/Filebeat%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5
记一次获得3倍性能的Go程序优化实践
https://mp.weixin.qq.com/s/0iYRCAq5-vXJfuk3ueIppA
深度解密Go语言之map
https://mp.weixin.qq.com/s/2CDpE5wfoiNXm1agMAq4wA
`
什么是 map
为什么要用 map
map 的底层如何实现
map 内存模型
创建 map
哈希函数
key 定位过程
map 的两种 get 操作
如何进行扩容
map 的遍历
map 的赋值
map 的删除
map 进阶
可以边遍历边删除吗?
key可以是float类型吗?
总结
参考资料
`
以Go的map是否并发安全为例,介绍最权威的Go语言资料的使用方法
https://www.lijiaocn.com/%E7%BC%96%E7%A8%8B/2019/06/11/golang-map-concurrent.html
`
找到正确的资料、能够正确的使用、正确的理解,是最关键的一步。除非是初学者,否则不要使用二手、三手和倒了无数手的资料,长期来看使用非一手资料,就是在浪费时间和引入错误。 第一手的资料常常晦涩难懂,需要经过长时间的积累和沉淀,才能比较熟练的运用,刚开始的时候可以用二手、三手的资料帮助理解,但一定要逼迫自己在一手资料中找到解答,这个过程会极大地提升认知。
说明
正确使用正确的资料
最权威的 Go 语言资料是?
Go 语言的 map 是否是并发安全的?
扩大搜索范围
找到答案不等于结束
为什么要执着于一手资料?
参考
`