对“ chan字符串”使用csv.Reader()的有效方法

时间:2018-09-15 18:46:38

标签: go

我有一个“ chan string”,其中每个条目都是一个CSV日志行,我想将其转换为“ [] string”列,目前,我(效率低下)创建了一个csv.NewReader(strings.NewReader (i))看起来比实际需要多得多的工作:

for i := range feederChan {
    r := csv.NewReader(strings.NewReader(i))
    a, err := r.Read()
    if err != nil {
         // log error...
         continue
    }
    // then do stuff with 'a'
    // ...
}

因此,如果有一种更有效的方法,例如共享一次创建csv.Reader,然后以某种方式将chan内容(将“ chan”内容流式传输到实现“ io”的内容),我将非常感激共享。读者界面?)。

2 个答案:

答案 0 :(得分:3)

使用以下命令将字符串通道转换为阅读器:

type chanReader struct {
    c   chan string
    buf string
}

func (r *chanReader) Read(p []byte) (int, error) {

    // Fill the buffer when we have no data to return to the caller
    if len(r.buf) == 0 {
        var ok bool
        r.buf, ok = <-r.c
        if !ok {
            // Return eof on channel closed
            return 0, io.EOF
        }
    }

    n := copy(p, r.buf)
    r.buf = r.buf[n:]
    return n, nil
}

像这样使用它:

r := csv.NewReader(&chanReader{c: feederChan})
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}

Run it on the playground

如果应用程序假定换行符分隔了从通道接收的值,则在每个接收到的值后面添加一个换行符:

        ...
        var ok bool
        r.buf, ok = <-r.c
        if !ok {
            // Return eof on channel closed
            return 0, io.EOF
        }
        r.buf += "\n"
        ...

+= "\n"复制字符串。如果这不满足应用程序的效率要求,则引入一个新字段来管理行分隔符。

type chanReader struct {
    c chan string  // source of lines
    buf string     // the current line
    nl bool        // true if line separator is pending
}

func (r *chanReader) Read(p []byte) (int, error) {

    // Fill the buffer when we have no data to return to the caller
    if len(r.buf) == 0 && !r.nl {
        var ok bool
        r.buf, ok = <-r.c
        if !ok {
            // Return eof on channel closed
            return 0, io.EOF
        }
        r.nl = true
    }

    // Return data if we have it
    if len(r.buf) > 0 {
        n := copy(p, r.buf)
        r.buf = r.buf[n:]
        return n, nil
    }

    // No data, return the line separator
    n := copy(p, "\n")
    r.nl = n == 0
    return n, nil
}

Run it on the playground

另一种方法是按照问题注释中的建议,使用io.Pipe和goroutine将通道转换为io.Reader。这种方法的第一步是:

var nl = []byte("\n")

func createChanReader(c chan string) io.Reader {
    r, w := io.Pipe()
    go func() {
        defer w.Close()
        for s := range c {
            io.WriteString(w, s)
            w.Write(nl)
            }
        }
    }()
    return r
}

像这样使用它:

r := csv.NewReader(createChanReader(feederChan))
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}

在io.Pipe解决方案中的第一次通过会在应用程序退出循环之前泄漏goroutine,然后再将管道读取到EOF。由于CSV阅读器检测到语法错误,由于程序员错误或其他多种原因而使应用程序感到恐慌,因此该应用程序可能会提前崩溃。

要修复goroutine泄漏,请在出现写入错误时退出正在写入的goroutine,并在完成读取后关闭管道读取器。

var nl = []byte("\n")

func createChanReader(c chan string) *io.PipeReader {
    r, w := io.Pipe()
    go func() {
        defer w.Close()
        for s := range c {
            if _, err := io.WriteString(w, s); err != nil {
                return
            }
            if _, err := w.Write(nl); err != nil {
                return
            }
        }
    }()
    return r
}

像这样使用它:

cr := createChanReader(feederChan)
defer cr.Close() // Required for goroutine cleanup
r := csv.NewReader(cr)
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}

Run it on the playground

答案 1 :(得分:2)

尽管“ ThunderCat”的答案确实有用并受到赞赏,但最终还是使用了io.Pipe()“如mh-cbon提到的”,它更简单并且看起来更有效(如下所述):

rp, wp := io.Pipe()
go func() {
    defer wp.Close()
    for i := range feederChan {
        fmt.Fprintln(wp, i)
    }
}()

r := csv.NewReader(rp)
for { // keep reading
    a, err := r.Read()
    if err == io.EOF {
        break
    }
    // do stuff with 'a'
    // ...
}

io.Pipe()是同步的,并且应该相当有效:它将数据从写入器传送到读取器;我将csv.NewReader()喂入了读取器部分,并创建了一个goroutine,将chan写入到写入器部分。

非常感谢。

编辑:ThunderCat在他的答案中添加了io.Pipe方法(我猜是在我发布之后)...他的答案更加全面,因此被接受了。