處理輸入的簡單程式都有一個差不多的設計結構:一個處理輸入的迴圈,在每個元素上執行計算處理,在處理的同時或最後產生輸出。
示例程式碼(以stdin
作為輸入):
// Dup prints the text of each line that appears more than
// once in the standard input, preceded by its count.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
//分行讀取標準輸入s,將s放入map統計出現次數
input := bufio.NewScanner(os.Stdin)
statistic := make(map[string]int)
for input.Scan() {
line := input.Text()
statistic[line]++
}
//列印出現重複的輸入
for line, num := range statistic {
if num > 1 {
fmt.Printf("%s\t%d\n", line, num)
}
}
}
複製程式碼
input := bufio.NewScanner(os.Stdin)
os.Stdin
是標準輸入描述結構,類似C語言中的FILE
。處理輸入的時候將該結構使用bufio
包中的Scanner
型別進行包裝。每次呼叫input.Scan()
,即讀入下一行,並移除行末的換行符,讀取的內容可以使用input.Text()
獲取。
statistic := make(map[string]int)
這條語句定義了一個map
變數。map
是儲存了鍵/值(key/value
)的集合,對集合元素,提供常數時間的存、取或測試操作。鍵可以是任意型別,只要其值能用==
運算子比較,最常見的例子是字串。值可以是任意型別。這個例子中的鍵是字串,值是整數。內建函式make
建立空map
,此外還有別的作用。make
有些神奇,它的引數是什麼形式的?以後觀察。
示例程式碼(以檔案作為輸入):
// Dup2 prints the count and text of lines that appear more than once
// in the input. It reads from stdin file
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
//讀取檔名
var file string
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "Paramater number error!")
return
} else {
file = os.Args[1]
}
//開啟檔案
f, err := os.Open(file)
if err != nil {
fmt.Fprintf(os.Stderr, "open file error: %v\n", err)
return
}
//分行讀取檔案,將內容放入map統計出現次數
input := bufio.NewScanner(f)
statistic := make(map[string]int)
for input.Scan() {
line := input.Text()
statistic[line]++
}
f.Close()
//列印出現重複的輸入
for line, num := range statistic {
if num > 1 {
fmt.Printf("%s\t%d\n", line, num)
}
}
}
複製程式碼
os.Open
函式返回兩個值。第一個值是被開啟的檔案(*os.File
),其後被Scanner
讀取。
os.Open
返回的第二個值是內建error
型別的值。如果err
等於內建值nil
,那麼檔案被成功開啟。讀取檔案,直到檔案結束,然後呼叫Close
關閉該檔案,並釋放佔用的所有資源。相反的話,如果err
的值不是nil
,說明開啟檔案時出錯了。這種情況下,錯誤值描述了所遇到的問題。我們的錯誤處理非常簡單,只是使用Fprintf
與表示任意型別預設格式值的動詞%v,向標準錯誤流列印一條資訊。
Go中錯誤處理在所有的高階語言中簡直是一股清流啊,這其實貼合Go的開發思想,簡單、直觀。事實上,我們在設計程式碼邏輯的時候,本來就是在考慮兩種情況,成功了往下走,失敗了怎麼辦?我們在開發產品的時候,本質上在不斷取捨,在可能出現錯誤的地方用分支語句處理就可以了,既符合邏輯又直白簡單。
以上兩種讀取輸入的方式都是使用的“流”模式,並根據需要拆分成多個行。理論上,這些程式可以處理任意數量的輸入資料。還有另一個方法,就是一口氣把全部輸入資料讀到記憶體中,一次分割為多行,然後處理它們。
示例程式碼(以檔案作為輸入):
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
func main() {
//讀取檔名
var file string
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "Paramater number error!")
return
} else {
file = os.Args[1]
}
//讀取檔案所有內容
data, err := ioutil.ReadFile(file)
if err != nil {
fmt.Fprintf(os.Stderr, "open file error: %v\n", err)
return
}
//分行統計資料
content := string(data)
statistic := make(map[string]int)
for _, line := range strings.Split(content, "\r\n") {
statistic[line]++
}
//列印出現重複的輸入
for line, num := range statistic {
if num > 1 {
fmt.Printf("%s\t%d\n", line, num)
}
}
}
複製程式碼
ReadFile
函式返回一個位元組切片(byte slice
),必須把它轉換為string
,才能用strings.Split
分割。實現上,bufio.Scanner
、ioutil.ReadFile
和 ioutil.WriteFIle
都使用 *os.File
的Read
和 Write
方法,但是,大多數程式設計師很少需要直接呼叫那些低階函式。高階函式,像 bufio
和 io/ioutil
包中所提供的那些,用起來要容易點