douqiao6015 2014-07-10 13:02
浏览 27
已采纳

如何从一个io.Reader拥有多个使用者?

I am working on a small script which uses bufio.Scanner and http.Request as well as go routines to count words and lines in parallel.

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net/http"
    "time"
)

func main() {
    err := request("http://www.google.com")

    if err != nil {
        log.Fatal(err)
    }

    // just keep main alive with sleep for now
    time.Sleep(2 * time.Second)
}

func request(url string) error {
    res, err := http.Get(url)

    if err != nil {
        return err
    }

    go scanLineWise(res.Body)
    go scanWordWise(res.Body)

    return err
}

func scanLineWise(r io.Reader) {
    s := bufio.NewScanner(r)
    s.Split(bufio.ScanLines)

    i := 0

    for s.Scan() {
        i++
    }

    fmt.Printf("Counted %d lines.
", i)
}

func scanWordWise(r io.Reader) {
    s := bufio.NewScanner(r)
    s.Split(bufio.ScanLines)

    i := 0

    for s.Scan() {
        i++
    }

    fmt.Printf("Counted %d words.
", i)
}

Source

As more or less expected from streams scanLineWise will count a number while scalWordWise will count zero. This is because scanLineWise already reads everything from req.Body.

I would know like to know: How to solve this elegantly?

My first thought was to build a struct which implements io.Reader and io.Writer. We could use io.Copy to read from req.Body and write it to the writer. When the scanners read from this writer then writer will copy the data instead of reading it. Unfortunately this will just collect memory over time and break the whole idea of streams...

  • 写回答

2条回答 默认 最新

  • doudan5136 2014-07-10 13:35
    关注

    The options are pretty straightforward -- you either maintain the "stream" of data, or you buffer the body.

    If you really do need to read over the body more then once sequentially, you need to buffer it somewhere. There's no way around that.

    There's a number of way you could stream the data, like having the line counter output lines into the word counter (preferably through channels). You could also build a pipeline using io.TeeReader and io.Pipe, and supply a unique reader for each function.

    ...
    pipeReader, pipeWriter := io.Pipe()
    bodyReader := io.TeeReader(res.Body, pipeWriter)
    go scanLineWise(bodyReader)
    go scanWordWise(pipeReader)
    ...
    

    That can get unwieldy with more consumers though, so you could use io.MultiWriter to multiplex to more io.Readers.

    ...
    pipeOneR, pipeOneW := io.Pipe()
    pipeTwoR, pipeTwoW := io.Pipe()
    pipeThreeR, pipeThreeW := io.Pipe()
    
    go scanLineWise(pipeOneR)
    go scanWordWise(pipeTwoR)
    go scanSomething(pipeThreeR)
    
    // of course, this should probably have some error handling
    io.Copy(io.MultiWriter(pipeOneW, pipeTwoW, pipeThreeW), res.Body)
    ...
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 乌班图ip地址配置及远程SSH
  • ¥15 怎么让点阵屏显示静态爱心,用keiluVision5写出让点阵屏显示静态爱心的代码,越快越好
  • ¥15 PSPICE制作一个加法器
  • ¥15 javaweb项目无法正常跳转
  • ¥15 VMBox虚拟机无法访问
  • ¥15 skd显示找不到头文件
  • ¥15 机器视觉中图片中长度与真实长度的关系
  • ¥15 fastreport table 怎么只让每页的最下面和最顶部有横线
  • ¥15 java 的protected权限 ,问题在注释里
  • ¥15 这个是哪里有问题啊?