Go 将csv.Reader()用于“chan字符串”的有效方法

Go 将csv.Reader()用于“chan字符串”的有效方法,go,Go,我有一个chan字符串,其中每个条目都是一个CSV日志行,我想将其转换为列[]字符串,目前我正在为每个条目无效地创建一个CSV.NewReaderString.NewReaderi,这看起来比实际需要的工作量大得多: for i := range feederChan { r := csv.NewReader(strings.NewReader(i)) a, err := r.Read() if err != nil { // log error...

我有一个chan字符串,其中每个条目都是一个CSV日志行,我想将其转换为列[]字符串,目前我正在为每个条目无效地创建一个CSV.NewReaderString.NewReaderi,这看起来比实际需要的工作量大得多:

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内容以某种方式传送到实现“io.Reader”接口的东西上?

使用以下方法将字符串通道转换为读卡器:

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
}
如果应用程序假定换行符分隔从通道接收到的值,则在每个接收到的值后附加一个换行符:

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

另一种方法是使用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(&chanReader{c: feederChan})
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
r := csv.NewReader(createChanReader(feederChan))
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
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
}
当应用程序在将管道读取到EOF之前退出循环时,io.Pipe解决方案的第一次传递会泄漏GOROUTE。由于CSV读取器检测到语法错误、由于程序员错误或任何其他原因,应用程序可能会提前中断

要修复goroutine泄漏,请退出写入goroutine on write错误,并在读取完成后关闭管道读取器

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
}
像这样使用它:

r := csv.NewReader(&chanReader{c: feederChan})
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
r := csv.NewReader(createChanReader(feederChan))
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
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
}

.

使用以下方法将字符串通道转换为读卡器:

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
}
如果应用程序假定换行符分隔从通道接收到的值,则在每个接收到的值后附加一个换行符:

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

另一种方法是使用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(&chanReader{c: feederChan})
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
r := csv.NewReader(createChanReader(feederChan))
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
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
}
当应用程序在将管道读取到EOF之前退出循环时,io.Pipe解决方案的第一次传递会泄漏GOROUTE。由于CSV读取器检测到语法错误、由于程序员错误或任何其他原因,应用程序可能会提前中断

要修复goroutine泄漏,请退出写入goroutine on write错误,并在读取完成后关闭管道读取器

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
}
像这样使用它:

r := csv.NewReader(&chanReader{c: feederChan})
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
r := csv.NewReader(createChanReader(feederChan))
for {
    a, err := r.Read()
    if err != nil {
        // handle error, break out of loop
    }
    // do something with a
}
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
}

.

尽管ThunderCat的答案非常有用,也很受欢迎,但我最终还是使用了mh cbon提到的io.Pipe,它更简单,看起来更高效,如下所述:

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方法,我想。。。他的答案更全面,因此被接受。

尽管ThunderCat的答案非常有用和受人赞赏,但我最终还是使用了mh cbon提到的io.Pipe,它更简单,看起来更有效,如下所述:

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方法,我想。。。他的回答更全面,因此被接受。

创建一次csv.Reader,然后以某种方式将内容输入到它。您可以使用io.Pipe,将chan的内容放入写入部分,将读取部分交给csv阅读器,排空csv阅读器。创建csv.reader一次,然后以某种方式向其提供chan内容。您可以使用io.Pipe,将chan的内容放入写入部分,将读取部分交给csv读取器,然后排空csv读取器。