03 - 查找重复行1 深一步了解go

思路

  • 对文件做拷贝、打印、搜索、排序、统计或类似事情的程序都有一个差不多的程序结构:一个处理输入的循环,在每个元素上执行计算处理,在处理的同时或最后产生输出。
  • 接下来根据 Linux 的 uniq 命令,其寻找响铃的重复行,我们将会用 go 语言编写三个版本的 dup, 方便我们更加详细的详解 go 语言的结构

例如:

// 输入文件
xiaomi
hello
world
xiaomi
hello

结果程序筛选后, 将获得 xiaomi 出现了2次,hello 出现了2次,world出现了1次

// 输出文件
2    xiaomi
2    hello
1    world

一、dup1

根据我们的思路,我们首先通过标准输入导入程序当中,在每一轮循环当中,利用容器 map 的 key value结构形式,把字符串作为 key, 其key出现的次数作为 value,然后累计统计。

  • 而在 C/C++ 当中我们将会使用 std::map<std::string, int> 结构来统计,在 C/C++ 当中 map 的底层实现是使用了红黑树的数据结构特性,但是在 go 语言当中我们也使用 map[string]int 容器,但是其底层的数据结构使用的哈希的原理,而每一次使用 for 访问 map[string]int 容器而打印出来的值均不一样。关于 go 语言中的 map 的实现原理后续将会深入讨论
  • 该程序当中引入了 标准输入,因此我们需要引入 bufio 包和 os 包,关于 bufio 包os 包的相关 api 可以参考英文文档 https://golang.org/pkg/ 和中文文档 https://studygolang.com/pkgdoc

我需要了解的源代码如下:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main(){
    counts := make(map[string]int)
    input := bufio.NewScanner(os.Stdin)
    for input.Scan(){
        counts[input.Text()]++
    }

    for line, n := range counts {
        fmt.Printf("%d\t%s\n", n, line)
    }
}

展示

  • 路径: $GOPATH/src/gopl/ch1/dup
    $ go run dup1.go < input.txt
    
      运行结果
    我们发现在第二次运行的时候打印的值的顺序和其他的不一样, 这是 go 的 map[string]int 容器的内部实现有关

二、程序分析

1. map

  • map 存储了键/值(key/value)的集合,对集合元素,提供常数时间的存、取或测试操作。键可以是任意类型,只要其值能用==运算符比较,值则可以是任意类型。上述例子中的 key 是字符串,value 是整数。内置函数make创建空map,此外,它还有别的作用。

  • C/C++go 中map的定义方式是不同的,在 C++ 中为std::map<std::string, int> 而在 go 中,则是map[string]int

  • 关于 map[string]int 我们一般实用make 创建一个内置类型,并且返回 map[string]intslice 类型,如果使用 var mp map[string]int方式声明的变量只是一个 nil map, 将会产生 panic:assignment to entry in nil map

    // 以下代码若放入 go 语言中运行将会产生panic 错误
    var mp map[string]int
    mp["xiaomi"] = 0
    

2. bufio

  • bufio包实现了有缓冲的 I/O*。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
  • func NewScanner(r io.Reader) *Scanner,传入标准输入 os.Stdin建立一个能重 r 中读取数据的 Scanner,并且返回其中指针类型
  • Scanner 类型提供了方便的读取数据的接口,如从换行符分隔的文本里读取每一行。成功调用的 Scan方法 会逐步提供文件的 token,跳过 token 之间的字节。token 由 SplitFunc类型 的分割函数指定;默认的分割函数会将输入分割为多个行,并去掉行尾的换行标志。本包预定义的分割函数可以将文件分割为行、字节、unicode码值、空白分隔的word。 调用者可以定制自己的分割函数。
    input := bufio.NewScanner(os.Stdin) // 建立一个能重 r 中读取数据的 Scanner
    for input.Scan(){  
        counts[input.Text()]++
    }
    

总结

猜你喜欢

转载自blog.csdn.net/xiaomiCJH/article/details/85784636