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

