Go輸入與輸出格式化案例詳解
一、Go語言輸入與輸出概述
Go語言中,輸入與輸出(I/O)操作主要通過 fmt 包和 bufio 包實現(xiàn)。fmt 包提供了基本的格式化輸入輸出功能,而 bufio 包則用于更高效的緩沖讀寫操作。
二、輸出格式化詳解
2.1 常用輸出函數(shù)
Go語言中,fmt 包提供了以下常用輸出函數(shù):
fmt.Print(a ...interface{}):打印參數(shù),不添加換行。fmt.Println(a ...interface{}):打印參數(shù),自動添加空格和換行。fmt.Printf(format string, a ...interface{}):按格式化字符串打印。fmt.Sprintf(format string, a ...interface{}):返回格式化后的字符串,不打印。fmt.Fprintf(w io.Writer, format string, a ...interface{}):將格式化內(nèi)容寫入io.Writer。
2.2 格式化動詞(Verbs)
格式化動詞用于控制輸出格式,常用動詞包括:
| 動詞 | 說明 |
|---|---|
%v | 值的默認格式 |
%+v | 類似 %v,但輸出結(jié)構(gòu)體時會添加字段名 |
%#v | 值的 Go 語法表示 |
%T | 值的類型 |
%% | 百分號 |
%d | 十進制整數(shù) |
%b | 二進制整數(shù) |
%x | 十六進制整數(shù)(小寫) |
%f | 浮點數(shù) |
%s | 字符串 |
%p | 指針地址 |
2.3 案例
package main
import (
"fmt"
"os"
)
type Person struct {
Name string
Age int
}
func main() {
// 基本輸出
fmt.Print("Hello, ")
fmt.Println("World!")
// 格式化輸出
name := "Alice"
age := 25
fmt.Printf("Name: %s, Age: %d\n", name, age)
// 結(jié)構(gòu)體格式化
person := Person{Name: "Bob", Age: 30}
fmt.Printf("Person: %v\n", person) // 輸出: Person: {Bob 30}
fmt.Printf("Person: %+v\n", person) // 輸出: Person: {Name:Bob Age:30}
fmt.Printf("Person: %#v\n", person) // 輸出: Person: main.Person{Name:"Bob", Age:30}
// 返回格式化字符串
formattedStr := fmt.Sprintf("Name: %s, Age: %d", name, age)
fmt.Println("Formatted string:", formattedStr)
// 寫入文件
file, err := os.Create("output.txt")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
fmt.Fprintf(file, "This is written to a file. Name: %s, Age: %d\n", name, age)
fmt.Println("Data written to file.")
}三、輸入格式化詳解
3.1 常用輸入函數(shù)
Go語言中,fmt 包提供了以下常用輸入函數(shù):
fmt.Scan(a ...interface{}):從標準輸入讀取數(shù)據(jù),按空格分隔。fmt.Scanln(a ...interface{}):類似Scan,但讀取到換行符時停止。fmt.Scanf(format string, a ...interface{}):按格式化字符串讀取輸入。
3.2 案例
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
// 使用 fmt.Scan 讀取輸入
var name string
var age int
fmt.Print("Enter your name and age (separated by space): ")
fmt.Scan(&name, &age)
fmt.Printf("Name: %s, Age: %d\n", name, age)
// 使用 fmt.Scanln 讀取輸入
fmt.Print("Enter your name: ")
fmt.Scanln(&name)
fmt.Printf("Name: %s\n", name)
// 使用 fmt.Scanf 讀取格式化輸入
fmt.Print("Enter your name and age (format: Name Age): ")
fmt.Scanf("%s %d", &name, &age)
fmt.Printf("Name: %s, Age: %d\n", name, age)
// 使用 bufio 讀取整行輸入
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter a sentence: ")
sentence, _ := reader.ReadString('\n')
sentence = strings.TrimSpace(sentence)
fmt.Printf("You entered: %s\n", sentence)
}代碼說明
- 輸出部分:
Print和Println用于簡單輸出,后者會自動換行。Printf支持格式化動詞,可以靈活控制輸出格式。Sprintf返回格式化后的字符串,不直接打印。Fprintf將格式化內(nèi)容寫入io.Writer,如文件。
- 輸入部分:
Scan和Scanln用于讀取標準輸入,按空格或換行符分隔。Scanf支持格式化輸入,適用于特定格式的數(shù)據(jù)讀取。bufio.NewReader提供更高效的緩沖讀取功能,適合讀取整行輸入。
四、Go語言bufio詳解
bufio 是 “buffered I/O” 的縮寫,它通過在 I/O 操作中引入緩沖區(qū)來提升性能。這個包包裝了 io.Reader 和 io.Writer 對象,創(chuàng)建了新的、帶緩沖的 Reader 和 Writer 實例,提供了額外的功能。
4.1 為什么需要bufio?(核心思想)
想象一下沒有緩沖區(qū)的 I/O 操作,就像用一個非常小的勺子(比如一次只能裝一個字節(jié))從一個大水桶(文件或網(wǎng)絡連接)里取水。每次取水都需要一個完整的“伸手-舀水-收回”的動作,這個過程的開銷很大。bufio 的作用就是引入一個“水杯”(緩沖區(qū))?,F(xiàn)在你的操作變成了:
- 用勺子從水桶里快速舀滿一杯水(一次性讀取一大塊數(shù)據(jù)到緩沖區(qū))。
- 需要用水時,直接從水杯里倒(從緩沖區(qū)讀取數(shù)據(jù)給程序)。
這個過程大大減少了“伸手-舀水-收回”的次數(shù),從而顯著提高了 I/O 效率,尤其是在處理大量小數(shù)據(jù)塊或網(wǎng)絡通信時。
核心優(yōu)勢:
- 減少系統(tǒng)調(diào)用:將多次小的讀/寫操作合并為少數(shù)幾次大的操作,減少了昂貴的系統(tǒng)調(diào)用次數(shù)。
- 提供高級功能:除了性能提升,
bufio還提供了許多便捷的方法,如按行讀取、按單詞讀取等。
4.2bufio.Reader:帶緩沖的讀取器
bufio.Reader 是一個結(jié)構(gòu)體,它包裝了一個 io.Reader 接口。
1、創(chuàng)建 bufio.Reader:使用 bufio.NewReader 函數(shù)創(chuàng)建
func NewReader(rd io.Reader) *Reader
示例: 從標準輸入創(chuàng)建一個緩沖讀取器。
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// os.Stdin 實現(xiàn)了 io.Reader 接口
reader := bufio.NewReader(os.Stdin)
fmt.Printf("創(chuàng)建了一個緩沖區(qū)大小為 %d 字節(jié)的 Reader\n", reader.Size())
}2、核心方法:bufio.Reader 提供了一系列強大的讀取方法。
a) ReadString(delim byte) - 按分隔符讀取
這是最常用的方法之一。它會讀取數(shù)據(jù),直到遇到指定的分隔符 delim,然后返回包含分隔符在內(nèi)的字符串。
func (b *Reader) ReadString(delim byte) (string, error)
示例: 逐行讀取用戶輸入,直到用戶輸入 “exit”。
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Println("請輸入內(nèi)容(輸入 'exit' 退出):")
for {
fmt.Print("> ")
// 讀取直到遇到換行符 '\n'
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("讀取錯誤:", err)
return
}
// 去除字符串兩端的空白字符(包括換行符)
input = strings.TrimSpace(input)
if input == "exit" {
fmt.Println("程序退出。")
break
}
fmt.Println("你輸入了:", input)
}
}b) ReadBytes(delim byte) - 按分隔符讀取字節(jié)切片
與 ReadString 類似,但返回的是 []byte 類型,在處理二進制數(shù)據(jù)或需要更高性能時非常有用。
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
c) ReadLine() - 讀取一行(不包含行尾符)
這是一個底層方法,它會讀取一行,但不包含行尾的 \r 或 \n。它返回三個值:line (內(nèi)容), isPrefix (是否過長), err (錯誤)。通常我們更常用 ReadString('\n'),因為它更簡單直觀。
d) ReadSlice(delim byte) - 讀取到分隔符的切片
這個方法返回的是對緩沖區(qū)內(nèi)部數(shù)據(jù)的引用,而不是拷貝。這意味著下一次讀取操作可能會覆蓋這次返回的數(shù)據(jù)。它性能更高,但使用起來需要更小心。
e) Peek(n int) - 預覽數(shù)據(jù)Peek 返回緩沖區(qū)接下來的 n 個字節(jié),但不會移動讀取指針。也就是說,下次調(diào)用 Read 或其他讀取方法時,仍然會從這次 Peek 的位置開始讀。非常適合用于“偷看”一下接下來的內(nèi)容是什么。
func (b *Reader) Peek(n int) ([]byte, error)
f) Read(p []byte) - 實現(xiàn) io.Reader 接口bufio.Reader 本身也實現(xiàn)了 io.Reader 接口。當你調(diào)用它的 Read 方法時,它會嘗試從自己的緩沖區(qū)中填滿你提供的字節(jié)切片 p。如果緩沖區(qū)空了,它會從底層的 io.Reader 中讀取數(shù)據(jù)來填滿緩沖區(qū),然后再滿足你的請求。
4.3bufio.Writer:帶緩沖的寫入器
bufio.Writer 包裝了一個 io.Writer 接口,將寫入操作先存入內(nèi)存緩沖區(qū),當緩沖區(qū)滿了或被顯式刷新時,才一次性寫入底層的 io.Writer。
創(chuàng)建 bufio.Writer:使用 bufio.NewWriter 函數(shù)創(chuàng)建:
func NewWriter(w io.Writer) *Writer
示例: 創(chuàng)建一個寫入標準輸出的緩沖寫入器。
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// os.Stdout 實現(xiàn)了 io.Writer 接口
writer := bufio.NewWriter(os.Stdout)
fmt.Printf("創(chuàng)建了一個緩沖區(qū)大小為 %d 字節(jié)的 Writer\n", writer.Size())
}核心方法
a) Write(p []byte) - 寫入字節(jié)切片
這是最核心的寫入方法。它將數(shù)據(jù)寫入緩沖區(qū)。如果緩沖區(qū)有足夠空間,數(shù)據(jù)就暫存于此;如果空間不足,它會先將緩沖區(qū)的內(nèi)容刷到底層 Writer,然后再寫入新數(shù)據(jù)。
b) WriteString(s string) - 寫入字符串WriteString 是一個便捷方法,內(nèi)部也是調(diào)用 Write。
c) Flush() - 強制刷新
這是 bufio.Writer 最重要的方法。它會將緩沖區(qū)中所有(無論是否已滿)的數(shù)據(jù)都寫入到底層的 io.Writer 中。
為什么 Flush 如此重要?
因為如果不調(diào)用 Flush(),當程序結(jié)束時,緩沖區(qū)里可能還有未寫入的數(shù)據(jù),這些數(shù)據(jù)就會丟失!
示例: 向文件寫入多行內(nèi)容。
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
// 1. 創(chuàng)建文件
file, err := os.Create("output.txt")
if err != nil {
log.Fatal(err)
}
// 確保文件在函數(shù)結(jié)束時關(guān)閉
defer file.Close()
// 2. 創(chuàng)建緩沖寫入器
writer := bufio.NewWriter(file)
// 3. 寫入數(shù)據(jù)(此時數(shù)據(jù)在內(nèi)存緩沖區(qū)中)
fmt.Fprintln(writer, "這是第一行。")
fmt.Fprintln(writer, "這是第二行。")
writer.WriteString("這是第三行,使用 WriteString。\n")
// 4. **必須刷新緩沖區(qū),將數(shù)據(jù)寫入文件**
err = writer.Flush()
if err != nil {
log.Fatal("刷新緩沖區(qū)失敗:", err)
}
fmt.Println("數(shù)據(jù)已成功寫入 output.txt")
}在這個例子中,如果注釋掉 writer.Flush(),output.txt 文件將是空的。
4.4bufio.Scanner:更高級的讀取工具
從 Go 1.1 開始,bufio 包引入了 Scanner 類型,它提供了一個更簡單、更健壯的方式來讀取數(shù)據(jù),特別是按“token”(標記)分割數(shù)據(jù)。核心思想:Scanner 的核心是 SplitFunc 類型的函數(shù),它定義了如何將輸入流分割成一個個標記。默認的分割函數(shù)是 ScanLines,即按行分割。
使用步驟
- 創(chuàng)建 Scanner:
bufio.NewScanner(r io.Reader) - (可選)設置分割函數(shù):
scanner.Split(customSplitFunc) - 循環(huán)掃描: 使用
scanner.Scan()作為循環(huán)條件,它會嘗試讀取下一個標記。 - 獲取標記: 在循環(huán)體內(nèi),使用
scanner.Text()(返回string) 或scanner.Bytes()(返回[]byte) 獲取當前標記的內(nèi)容。 - 檢查錯誤: 循環(huán)結(jié)束后,使用
scanner.Err()檢查是否發(fā)生了錯誤。
案例1:逐行讀取文件
使用 Scanner 逐行讀取文件比 Reader.ReadString 更簡潔,且能更好地處理行尾符。
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
)
func main() {
// 1. 打開文件
file, err := os.Open("my_file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 2. 創(chuàng)建 Scanner
scanner := bufio.NewScanner(file)
lineCount := 0
wordCount := 0
// 3. 循環(huán)掃描
for scanner.Scan() {
line := scanner.Text()
lineCount++
// 簡單地按空格分割來計算單詞數(shù)
words := strings.Fields(line)
wordCount += len(words)
fmt.Printf("行 %d: %s\n", lineCount, line)
}
// 4. 檢查錯誤
if err := scanner.Err(); err != nil {
log.Fatal("掃描時出錯:", err)
}
fmt.Printf("\n掃描完成。共 %d 行,%d 個單詞。\n", lineCount, wordCount)
}案例2:自定義分割函數(shù)Scanner 的強大之處在于可以自定義分割邏輯。例如,我們可以按單詞讀取。
示例: 使用 ScanWords 分割函數(shù)按單詞讀取。
// ... (前面的代碼相同)
scanner := bufio.NewScanner(strings.NewReader("Hello world, this is a test."))
// 設置分割函數(shù)為按單詞分割
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
// ...
}輸出:
Hello
world,
this
is
a
test.
4.5 使用對比
| 類型 | 適用場景 | 優(yōu)點 | 注意事項 |
|---|---|---|---|
bufio.Reader | 需要精細控制讀取過程,如讀取固定字節(jié)數(shù)、預覽數(shù)據(jù)、讀取到特定分隔符。 | 功能強大,控制力強,性能高。 | API 相對底層,使用時需注意錯誤處理。 |
bufio.Writer | 需要頻繁進行小塊寫入,以減少系統(tǒng)調(diào)用,提高性能。 | 顯著提升寫入性能。 | 必須記得調(diào)用 Flush(),否則數(shù)據(jù)可能丟失。 |
bufio.Scanner | 需要方便地按行、按單詞等“標記”來處理文本流。 | API 簡潔易用,代碼可讀性高,錯誤處理方便。 | 功能相對固定,不如 Reader 靈活。不適合讀取二進制流。 |
Go語言的輸入與輸出格式化功能非常強大,通過 fmt 包和 bufio 包,可以滿足各種輸入輸出需求。掌握這些函數(shù)和格式化動詞,有助于編寫清晰、易讀的 Go 程序。
到此這篇關(guān)于Go基礎(chǔ):輸入與輸出格式化詳解的文章就介紹到這了,更多相關(guān)Go輸入與輸出格式化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言中Struct與繼承與匿名字段和內(nèi)嵌結(jié)構(gòu)體全面詳解
這篇文章主要介紹了Go語言中Struct與繼承與匿名字段和內(nèi)嵌結(jié)構(gòu)體,Go語言中通過結(jié)構(gòu)體的內(nèi)嵌再配合接口比面向?qū)ο缶哂懈叩臄U展性和靈活性,感興趣的可以了解一下2023-04-04
Go操作Kafka的實現(xiàn)示例(kafka-go)
本文介紹了使用kafka-go庫在Go語言中與Kafka進行交互,涵蓋了kafka-go的安裝、API使用、消息發(fā)送與消費方法,以及如何通過DockerCompose快速搭建Kafka環(huán)境,文章還比較了其他兩個常用的Kafka客戶端庫,感興趣的可以了解一下2024-10-10

