Hero Image
Go 字串格式化

Go 字串格式化 Go 語言 fmt.Printf 使用指南 結尾的動詞(verb)決定對應參數的型別與解讀方式。 d - 十進位整數 o - 八進位整數 O - 帶有 0o 前綴的八進位整數 b - 二進位整數 x - 十六進位整數(小寫) X - 十六進位整數(大寫) f - 十進位浮點數(小寫) F - 十進位浮點數(大寫) e - 科學記號(尾數/指數,小寫) E - 科學記號(尾數/指數,大寫) g - %e 或 %f 的最短表示 G - %E 或 %F 的最短表示 c - 以 Unicode 碼點表示的字元 q - 帶引號的字元 U - Unicode 逸出序列 t - true 或 false 字串 s - 字串 v - 預設格式 #v - 值的 Go 語法表示 T - 值型別的 Go 語法表示 p - 指標位址 % - 雙 %% 會輸出單一 % Go 字串格式化索引 package main import ( "fmt" ) func main() { n1 := 2 n2 := 3 n3 := 4 res := fmt.Sprintf("There are %d oranges %d apples %d plums", n1, n2, n3) fmt.Println(res) // There are 2 oranges 3 apples 4 plums res2 := fmt.Sprintf("There are %[2]d oranges %d apples %[1]d plums", n1, n2, n3) fmt.Println(res2) // There are 3 oranges 4 apples 2 plums } Go 字串格式化精度 package main import ( "fmt" ) func main() { fmt.Printf("%0.f\n", 16.540) // 17 fmt.Printf("%0.2f\n", 16.540) // 16.54 fmt.Printf("%0.3f\n", 16.540) // 16.540 fmt.Printf("%0.5f\n", 16.540) // 16.54000 } Go 字串格式化旗標 package main import ( "fmt" ) func main() { fmt.Printf("%+d\n", 1691) // +1691 fmt.Printf("%#x\n", 1691) // 0x69b fmt.Printf("%#X\n", 1691) // 0X69B fmt.Printf("%#b\n", 1691) // 0b11010011011 fmt.Printf("%10d\n", 1691) // 1691 fmt.Printf("%-10d\n", 1691) // 1691 fmt.Printf("%010d\n", 1691) // 0000001691 } Go 字串格式化寬度 package main import ( "fmt" ) func main() { w := "falcon" n := 122 h := 455.67 fmt.Printf("%s\n", w) // falcon fmt.Printf("%10s\n", w) // falcon fmt.Printf("%d\n", n). // 122 fmt.Printf("%7d\n", n) // 122 fmt.Printf("%07d\n", n) // 0000122 fmt.Printf("%10f\n", h) // 455.670000 fmt.Printf("%11f\n", h) // 455.670000 fmt.Printf("%12f\n", h) // 455.670000 }

Hero Image
golang pprof 实战

golang pprof 实战 炸弹程序 package main import ( // 略 _ "net/http/pprof" // 会自动注册 handler 到 http server,方便通过 http 接口获取程序运行采样报告 // 略 ) func main() { // 略 runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载 runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪 runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪 go func() { // 启动一个 http server,注意 pprof 相关的 handler 已经自动注册过了 // /debug/pprof/ if err := http.ListenAndServe(":6060", nil); err != nil { log.Fatal(err) } os.Exit(0) }() // 略 } http://localhost:6060/debug/pprof/ 类型 描述 备注 allocs 内存分配情况的采样信息 可以用浏览器打开,但可读性不高 blocks 阻塞操作情况的采样信息 可以用浏览器打开,但可读性不高 cmdline 显示程序启动命令及参数 可以用浏览器打开,这里会显示 ./go-pprof-practice goroutine 当前所有协程的堆栈信息 可以用浏览器打开,但可读性不高 heap 堆上内存使用情况的采样信息 可以用浏览器打开,但可读性不高 mutex 锁争用情况的采样信息 可以用浏览器打开,但可读性不高 profile CPU 占用情况的采样信息 浏览器打开会下载文件 threadcreate 系统线程创建情况的采样信息 可以用浏览器打开,但可读性不高 trace 程序运行跟踪信息 浏览器打开会下载文件,本文不涉及,可另行参阅《深入浅出 Go trace》 排查 CPU 占用过高

Hero Image
怎么选择 Go 文件读取方案

怎么选择 Go 文件读取方案 Go 提供了可一次性读取文件内容的方法:os.ReadFile 与 ioutil.ReadFile。在 Go 1.16 开始,ioutil.ReadFile 就等价于 os.ReadFile。 一次性加载文件的优缺点非常明显,它能减少 IO 次数,但它会将文件内容都加载至内存中,对于大文件,存在内存撑爆的风险。 逐行读取 Go 中 bufio.Reader 对象提供了一个 ReadLine() 方法,但其实我们更多地是使用 ReadBytes(’\n’) 或者 ReadString(’\n’) 代替。 func ReadLines(filename string) { fi, err := os.Open(filename) if err != nil{ panic(err) } defer fi.Close() reader := bufio.NewReader(fi) for { _, err = reader.ReadString('\n') if err != nil { if err == io.EOF { break } panic(err) } } } 块读取 块读取也称为分片读取,这也很好理解,我们可以将内容分成一块块的,每次读取指定大小的块内容。这里,我们将块大小设置为 4KB。 func ReadChunk(filename string) { f, err := os.Open(filename) if err != nil { panic(err) } defer f.Close() buf := make([]byte, 4*1024) r := bufio.NewReader(f) for { _, err = r.Read(buf) if err != nil { if err == io.EOF { break } panic(err) } } } result BenchmarkOsReadFile4KB-8 92877 12491 ns/op BenchmarkOsReadFile4MB-8 1620 744460 ns/op BenchmarkOsReadFile4GB-8 1 7518057733 ns/op signal: killed BenchmarkReadLines4KB-8 90846 13184 ns/op BenchmarkReadLines4MB-8 493 2338170 ns/op BenchmarkReadLines4GB-8 1 3072629047 ns/op BenchmarkReadLines16GB-8 1 12472749187 ns/op BenchmarkReadChunk4KB-8 99848 12262 ns/op BenchmarkReadChunk4MB-8 913 1233216 ns/op BenchmarkReadChunk4GB-8 1 2095515009 ns/op BenchmarkReadChunk16GB-8 1 8547054349 ns/op 在本文的测试条件下(每行数据 1KB),对于小对象 4KB 的读取,三种方式差距并不大;在 MB 级别的读取中,直接加载最快,但块读取也慢不了多少;上了 GB 后,块读取方式会最快。

Hero Image
25秒读取16GB文件,Go怎么做到的?

25 秒读取 16GB 文件,Go 怎么做到的? Reading 16GB File in Seconds, Golang 打开文件后,我们有以下两个选项可以选择: 逐行读取文件,这有助于减少内存紧张,但需要更多的时间。 一次将整个文件读入内存并处理该文件,这将消耗更多内存,但会显著减少时间。 由于文件太大,即 16 GB,因此无法将整个文件加载到内存中。但是第一种选择对我们来说也是不可行的,因为我们希望在几秒钟内处理文件。 但你猜怎么着,还有第三种选择。瞧……相比于将整个文件加载到内存中,在 Go 语言中,我们还可以使用 bufio.NewReader()将文件分块加载。 func main() { s := time.Now() args := os.Args[1:] if len(args) != 6 { // for format LogExtractor.exe -f "From Time" -t "To Time" -i "Log file directory location" fmt.Println("Please give proper command line arguments") return } startTimeArg := args[1] finishTimeArg := args[3] fileName := args[5] file, err := os.Open(fileName) if err != nil { fmt.Println("cannot able to read the file", err) return } defer file.Close() // close after checking err queryStartTime, err := time.Parse("2006-01-02T15:04:05.0000Z", startTimeArg) if err != nil { fmt.Println("Could not able to parse the start time", startTimeArg) return } queryFinishTime, err := time.Parse("2006-01-02T15:04:05.0000Z", finishTimeArg) if err != nil { fmt.Println("Could not able to parse the finish time", finishTimeArg) return } filestat, err := file.Stat() if err != nil { fmt.Println("Could not able to get the file stat") return } fileSize := filestat.Size() offset := fileSize - 1 lastLineSize := 0 for { b := make([]byte, 1) n, err := file.ReadAt(b, offset) if err != nil { fmt.Println("Error reading file ", err) break } char := string(b[0]) if char == "\n" { break } offset-- lastLineSize += n } lastLine := make([]byte, lastLineSize) _, err = file.ReadAt(lastLine, offset+1) if err != nil { fmt.Println("Could not able to read last line with offset", offset, "and lastline size", lastLineSize) return } logSlice := strings.SplitN(string(lastLine), ",", 2) logCreationTimeString := logSlice[0] lastLogCreationTime, err := time.Parse("2006-01-02T15:04:05.0000Z", logCreationTimeString) if err != nil { fmt.Println("can not able to parse time : ", err) } if lastLogCreationTime.After(queryStartTime) && lastLogCreationTime.Before(queryFinishTime) { Process(file, queryStartTime, queryFinishTime) } fmt.Println("\nTime taken - ", time.Since(s)) } func Process(f *os.File, start time.Time, end time.Time) error { linesPool := sync.Pool{New: func() interface{} { lines := make([]byte, 250*1024) return lines }} stringPool := sync.Pool{New: func() interface{} { lines := "" return lines }} r := bufio.NewReader(f) var wg sync.WaitGroup for { buf := linesPool.Get().([]byte) n, err := r.Read(buf) buf = buf[:n] if n == 0 { if err != nil { fmt.Println(err) break } if err == io.EOF { break } return err } nextUntillNewline, err := r.ReadBytes('\n') if err != io.EOF { buf = append(buf, nextUntillNewline...) } wg.Add(1) go func() { ProcessChunk(buf, &linesPool, &stringPool, start, end) wg.Done() }() } wg.Wait() return nil } func ProcessChunk(chunk []byte, linesPool *sync.Pool, stringPool *sync.Pool, start time.Time, end time.Time) { var wg2 sync.WaitGroup logs := stringPool.Get().(string) logs = string(chunk) linesPool.Put(chunk) logsSlice := strings.Split(logs, "\n") stringPool.Put(logs) chunkSize := 300 n := len(logsSlice) noOfThread := n / chunkSize if n%chunkSize != 0 { noOfThread++ } for i := 0; i < (noOfThread); i++ { wg2.Add(1) go func(s int, e int) { defer wg2.Done() // to avaoid deadlocks for i := s; i < e; i++ { text := logsSlice[i] if len(text) == 0 { continue } logSlice := strings.SplitN(text, ",", 2) logCreationTimeString := logSlice[0] logCreationTime, err := time.Parse("2006-01-02T15:04:05.0000Z", logCreationTimeString) if err != nil { fmt.Printf("\n Could not able to parse the time :%s for log : %v", logCreationTimeString, text) return } if logCreationTime.After(start) && logCreationTime.Before(end) { // fmt.Println(text) } } }(i*chunkSize, int(math.Min(float64((i+1)*chunkSize), float64(len(logsSlice))))) } wg2.Wait() logsSlice = nil }