1. 程式人生 > >03 - 查詢重複行1 深一步瞭解go

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()]++
    }
    

總結