我有一个“ 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”的内容),我将非常感激共享。读者界面?)。
答案 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
}
如果应用程序假定换行符分隔了从通道接收的值,则在每个接收到的值后面添加一个换行符:
...
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
}
另一种方法是按照问题注释中的建议,使用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
}
答案 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方法(我猜是在我发布之后)...他的答案更加全面,因此被接受了。