Golang 操作TSV文件的實戰(zhàn)示例
本文介紹TSV文件類型及其應(yīng)用,同時介紹Golang語句讀取TSV文件并轉(zhuǎn)為struct的實現(xiàn)過程。
認(rèn)識TSV文件
也許你之前不了解TSV文件,無需擔(dān)心,它很簡單、很常用。TSV(tab-separated values)文件表示以tab分割值的文件格式,也就是說,TSV文件包括一系列數(shù)據(jù)信息,其中數(shù)據(jù)使用tab符(也稱制表符,\t)進行分割。與CSV文件格式類似,CSV使用半角逗號(,)分割。
TSV文件和CSV文件一樣,非常通用,被大多數(shù)平臺或處理軟件支持,但TSV文件采用不可見制表符作為分隔符,被用戶誤用的概率較低,相對CSV容錯性更好。
Golang 讀取TSV文件
golang 包encoding/csv提供了csv文件的讀寫功能,我們值得tsv和csv的差異僅為分隔符,因此下面代碼可以很容易讀取tsv:
package main import ( ? ? "encoding/csv" ? ? "fmt" ? ? "log" ? ? "os" ) func main() { ? ? f, err := os.Open("users.csv") ? ? if err != nil { ? ? ? ? log.Fatal(err) ? ? } ? ? r := csv.NewReader(f) ? ? r.Comma = '\t' ? ? r.Comment = '#' ? ? records, err := r.ReadAll() ? ? if err != nil { ? ? ? ? log.Fatal(err) ? ? } ? ? fmt.Print(records) }
解析為結(jié)構(gòu)體
一般我們希望讀取tsv文件并解析為struct,下面一起看一些開源代碼實現(xiàn)。tsv文件可能包括標(biāo)題行,同時字段增加tsv標(biāo)簽,示例如下:
type TestTaggedRow struct { Age int `tsv:"age"` Active bool `tsv:"active"` Gender string `tsv:"gender"` Name string `tsv:"name"` }
因此定義Parse類型:
// Parser has information for parser type Parser struct { Headers []string // 標(biāo)題數(shù)組 Reader *csv.Reader // 讀取器 Data interface{} // 希望解析為結(jié)構(gòu)體的類型 ref reflect.Value // 反射值 indices []int // indices is field index list of header array structMode bool // 結(jié)構(gòu)模式,結(jié)構(gòu)體有tsv標(biāo)簽 normalize norm.Form // 解析UTF8方式 }
定義無標(biāo)題行的機構(gòu)函數(shù):
// NewParserWithoutHeader creates new TSV parser with given io.Reader func NewParserWithoutHeader(reader io.Reader, data interface{}) *Parser { ?? ?r := csv.NewReader(reader) ?? ?r.Comma = '\t' ?? ?p := &Parser{ ?? ??? ?Reader: ? ?r, ?? ??? ?Data: ? ? ?data, ?? ??? ?ref: ? ? ? reflect.ValueOf(data).Elem(), ?? ??? ?normalize: -1, ?? ?} ?? ?return p }
帶標(biāo)題行的解析構(gòu)造函數(shù):
// NewStructModeParser creates new TSV parser with given io.Reader as struct mode func NewParser(reader io.Reader, data interface{}) (*Parser, error) { ?? ?r := csv.NewReader(reader) ?? ?r.Comma = '\t' ?? ?// 讀取一行,即標(biāo)題行;函數(shù)字符串?dāng)?shù)組 ?? ?headers, err := r.Read() ?? ?if err != nil { ?? ??? ?return nil, err ?? ?} ? ? // 循環(huán)給標(biāo)題數(shù)組賦值 ?? ?for i, header := range headers { ?? ??? ?headers[i] = header ?? ?} ?? ?p := &Parser{ ?? ??? ?Reader: ? ? r, ?? ??? ?Headers: ? ?headers, ?? ??? ?Data: ? ? ? data, ?? ??? ?ref: ? ? ? ?reflect.ValueOf(data).Elem(), ?? ??? ?indices: ? ?make([]int, len(headers)), ?? ??? ?structMode: false, ?? ??? ?normalize: ?-1, ?? ?} ?? ?// get type information ?? ?t := p.ref.Type() ?? ?for i := 0; i < t.NumField(); i++ { ?? ??? ?// get TSV tag ?? ??? ?tsvtag := t.Field(i).Tag.Get("tsv") ?? ??? ?if tsvtag != "" { ?? ??? ??? ?// find tsv position by header ?? ??? ??? ?for j := 0; j < len(headers); j++ { ?? ??? ??? ??? ?if headers[j] == tsvtag { ?? ??? ??? ??? ??? ?// indices are 1 start ?? ??? ??? ??? ??? ?p.indices[j] = i + 1 ?? ??? ??? ??? ??? ?p.structMode = true ?? ??? ??? ??? ?} ?? ??? ??? ?} ?? ??? ?} ?? ?} ?? ?if !p.structMode { ?? ??? ?for i := 0; i < len(headers); i++ { ?? ??? ??? ?p.indices[i] = i + 1 ?? ??? ?} ?? ?} ?? ?return p, nil }
與上面無標(biāo)題行相比,多了解析tsv標(biāo)簽的邏輯。
下面開始解析每行數(shù)據(jù),我們看Next()方法:
// Next puts reader forward by a line func (p *Parser) Next() (eof bool, err error) { ?? ?// Get next record ?? ?var records []string ?? ?for { ?? ??? ?// read until valid record ?? ??? ?records, err = p.Reader.Read() ?? ??? ?if err != nil { ?? ??? ??? ?if err.Error() == "EOF" { ?? ??? ??? ??? ?return true, nil ?? ??? ??? ?} ?? ??? ??? ?return false, err ?? ??? ?} ?? ??? ?if len(records) > 0 { ?? ??? ??? ?break ?? ??? ?} ?? ?} ?? ?if len(p.indices) == 0 { ?? ??? ?p.indices = make([]int, len(records)) ?? ??? ?// mapping simple index ?? ??? ?for i := 0; i < len(records); i++ { ?? ??? ??? ?p.indices[i] = i + 1 ?? ??? ?} ?? ?} ?? ?// record should be a pointer ?? ?for i, record := range records { ?? ??? ?idx := p.indices[i] ?? ??? ?if idx == 0 { ?? ??? ??? ?// skip empty index ?? ??? ??? ?continue ?? ??? ?} ?? ??? ?// get target field ?? ??? ?field := p.ref.Field(idx - 1) ?? ??? ?switch field.Kind() { ?? ??? ?case reflect.String: ?? ??? ??? ?// Normalize text ?? ??? ??? ?if p.normalize >= 0 { ?? ??? ??? ??? ?record = p.normalize.String(record) ?? ??? ??? ?} ?? ??? ??? ?field.SetString(record) ?? ??? ?case reflect.Bool: ?? ??? ??? ?if record == "" { ?? ??? ??? ??? ?field.SetBool(false) ?? ??? ??? ?} else { ?? ??? ??? ??? ?col, err := strconv.ParseBool(record) ?? ??? ??? ??? ?if err != nil { ?? ??? ??? ??? ??? ?return false, err ?? ??? ??? ??? ?} ?? ??? ??? ??? ?field.SetBool(col) ?? ??? ??? ?} ?? ??? ?case reflect.Int: ?? ??? ??? ?if record == "" { ?? ??? ??? ??? ?field.SetInt(0) ?? ??? ??? ?} else { ?? ??? ??? ??? ?col, err := strconv.ParseInt(record, 10, 0) ?? ??? ??? ??? ?if err != nil { ?? ??? ??? ??? ??? ?return false, err ?? ??? ??? ??? ?} ?? ??? ??? ??? ?field.SetInt(col) ?? ??? ??? ?} ?? ??? ?default: ?? ??? ??? ?return false, errors.New("Unsupported field type") ?? ??? ?} ?? ?} ?? ?return false, nil }
上面主要邏輯就是通過反射解析并存儲每行數(shù)據(jù),并填充結(jié)構(gòu)體的過程。這里僅考慮了string、bool、Int三種類型,當(dāng)然我們可以擴展支持更多類型。
下面通過main函數(shù)進行測試:
import ( ? ? "fmt" ? ? "os" ? ? ) type TestRow struct { ? Name ? string // 0 ? Age ? ?int ? ?// 1 ? Gender string // 2 ? Active bool ? // 3 } func main() { ? file, _ := os.Open("example.tsv") ? defer file.Close() ? data := TestRow{} ? parser, _ := NewParser(file, &data) ? for { ? ? eof, err := parser.Next() ? ? if eof { ? ? ? return ? ? } ? ? if err != nil { ? ? ? panic(err) ? ? } ? ? fmt.Println(data) ? } }
打開文件,定義結(jié)構(gòu)體對象,然后定義解析器,傳入文件和結(jié)構(gòu)體對象作為參數(shù)。解析結(jié)果存儲在結(jié)構(gòu)體對象中。上面代碼參考tsv開源項目:https://github.com/dogenzaka/tsv。還有咱們更強大的開源庫:https://github.com/shenwei356/csvtk,不僅解析CSV/TSV文件,還能實現(xiàn)不同格式的轉(zhuǎn)換。
到此這篇關(guān)于Golang 操作TSV文件的實戰(zhàn)示例的文章就介紹到這了,更多相關(guān)Golang 操作TSV文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
sublime text3解決Gosublime無法自動補全代碼的問題
本文主要介紹了sublime text3解決Gosublime無法自動補全代碼的問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01Go語言kube-scheduler深度剖析與開發(fā)之pod調(diào)度
這篇文章主要為大家介紹了Go語言kube-scheduler深度剖析與開發(fā),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04如何在golang中使用shopspring/decimal來處理精度問題
本文主要介紹了如何在golang中使用shopspring/decimal來處理精度問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04Golang中HTTP路由設(shè)計的使用與實現(xiàn)
這篇文章主要介紹了Golang中HTTP路由設(shè)計的使用與實現(xiàn),為什么要設(shè)計路由規(guī)則,因為路由規(guī)則是HTTP的請求按照一定的規(guī)則 ,匹配查找到對應(yīng)的控制器并傳遞執(zhí)行的邏輯,需要的朋友可以參考下2023-05-05

Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解