Golang 操作TSV文件的实战示例

 更新时间:2023年03月22日 16:24:59   作者:梦想画家  
本文主要介绍了Golang 操作TSV文件的实战示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

本文介绍TSV文件类型及其应用,同时介绍Golang语句读取TSV文件并转为struct的实现过程。

认识TSV文件

也许你之前不了解TSV文件,无需担心,它很简单、很常用。TSV(tab-separated values)文件表示以tab分割值的文件格式,也就是说,TSV文件包括一系列数据信息,其中数据使用tab符(也称制表符,\t)进行分割。与CSV文件格式类似,CSV使用半角逗号(,)分割。

TSV文件和CSV文件一样,非常通用,被大多数平台或处理软件支持,但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)
}

解析为结构体

一般我们希望读取tsv文件并解析为struct,下面一起看一些开源代码实现。tsv文件可能包括标题行,同时字段增加tsv标签,示例如下:

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      // 标题数组
    Reader     *csv.Reader   // 读取器
    Data       interface{}   // 希望解析为结构体的类型
    ref        reflect.Value // 反射值
    indices    []int // indices is field index list of header array
    structMode bool  // 结构模式,结构体有tsv标签
    normalize  norm.Form     // 解析UTF8方式
}

定义无标题行的机构函数:

// 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
}

带标题行的解析构造函数:

// 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'

    // 读取一行,即标题行;函数字符串数组
    headers, err := r.Read()

    if err != nil {
        return nil, err
    }

    // 循环给标题数组赋值
    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
}

与上面无标题行相比,多了解析tsv标签的逻辑。

下面开始解析每行数据,我们看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
}

上面主要逻辑就是通过反射解析并存储每行数据,并填充结构体的过程。这里仅考虑了string、bool、Int三种类型,当然我们可以扩展支持更多类型。

下面通过main函数进行测试:

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)
  }

}

打开文件,定义结构体对象,然后定义解析器,传入文件和结构体对象作为参数。解析结果存储在结构体对象中。上面代码参考tsv开源项目:https://github.com/dogenzaka/tsv。还有咱们更强大的开源库:https://github.com/shenwei356/csvtk,不仅解析CSV/TSV文件,还能实现不同格式的转换。

到此这篇关于Golang 操作TSV文件的实战示例的文章就介绍到这了,更多相关Golang 操作TSV文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang如何去除 context 的 deadline

    golang如何去除 context 的 deadline

    在使用 context 的时候遇到了开协程处理任务的情况,但是直接在协程里使用主线程的 context 会导致当主线程返回时协程任务也会因为 context cancel 而失败,本文提供了两种办法可以取消掉 context 里的 timeout 和 deadline,再设置一个新的 timeout 上去
    2023-03-03
  • Golang 负载均衡算法实现示例

    Golang 负载均衡算法实现示例

    在Go语言中,负载均衡算法通常由代理、反向代理或者应用层负载均衡器来实现,在这些实现中,有一些经典的负载均衡算法,跟随本文来一一探究
    2024-01-01
  • sublime text3解决Gosublime无法自动补全代码的问题

    sublime text3解决Gosublime无法自动补全代码的问题

    本文主要介绍了sublime text3解决Gosublime无法自动补全代码的问题,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • Go语言kube-scheduler深度剖析与开发之pod调度

    Go语言kube-scheduler深度剖析与开发之pod调度

    这篇文章主要为大家介绍了Go语言kube-scheduler深度剖析与开发,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • 如何在golang中使用shopspring/decimal来处理精度问题

    如何在golang中使用shopspring/decimal来处理精度问题

    本文主要介绍了如何在golang中使用shopspring/decimal来处理精度问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Golang中的强大Web框架Fiber详解

    Golang中的强大Web框架Fiber详解

    在不断发展的Web开发领域中,选择正确的框架可以极大地影响项目的效率和成功,介绍一下Fiber,这是一款令人印象深刻的Golang(Go语言)Web框架,在本文中,我们将深入了解Fiber的世界,探讨其独特的特性,并理解为什么它在Go生态系统中引起了如此大的关注
    2023-10-10
  • GoLang实现Viper库的封装流程详解

    GoLang实现Viper库的封装流程详解

    Viper是一个用于Go语言应用程序的配置管理库,它提供了一种简单而灵活的方式来处理应用程序的配置,支持多种格式的配置文件,这篇文章主要介绍了GoLang封装Viper库的流程,感兴趣的同学可以参考下文
    2023-05-05
  • Golang中HTTP路由设计的使用与实现

    Golang中HTTP路由设计的使用与实现

    这篇文章主要介绍了Golang中HTTP路由设计的使用与实现,为什么要设计路由规则,因为路由规则是HTTP的请求按照一定的规则 ,匹配查找到对应的控制器并传递执行的逻辑,需要的朋友可以参考下
    2023-05-05
  • go新工具gonew生成模板工具链使用详解

    go新工具gonew生成模板工具链使用详解

    这篇文章主要为大家介绍了go新工具gonew生成模板工具链使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • Go 数据结构之堆排序示例详解

    Go 数据结构之堆排序示例详解

    这篇文章主要为大家介绍了Go 数据结构之堆排序示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08

最新评论