Search code examples
gobytestream

How to implement a exercise in The Go Programming Language


Exercise 7.5:
The LimitReader function in the io package accepts an io.Reader r and a number of bytes n, and returns another Reader that reads from r but reports an end-of-file condition after n bytes. Implement it.

func LimitReader(r io.Reader, n int64) io.Reader

How to implement it?


Below is my code, I know it not right.

type MyReader struct {
    bytes []byte
    length int
}

func (myReader *MyReader) Read (p []byte) (int, error) {
    fmt.Println("be invoked")
    myReader.bytes = p
    myReader.length = len(p)
    fmt.Printf("lenght: %d\n" , myReader.length)
    return len(p), nil
}
func LimitReader(r io.Reader, n int64) io.Reader {
    filecontent, _:= ioutil.ReadFile("./testfile.txt")
    r.Read(filecontent[:n])
    return r
}

func main() {
    myReader := &MyReader{}
    r := LimitReader(myReader, 100)
    filecontent, _:= ioutil.ReadFile("./testfile.txt")
    fmt.Println(r.Read(filecontent))
}

Solution

  • It seems you are not clear with the concepts of what you need to do.

    You need to create a function with the following signature:

    func LimitReader(r io.Reader, n int64) io.Reader
    

    That is, an r io.Reader and an n number is passed to it, and you have to return a new io.Reader. When someone reads from the returned reader, the read data must come from the passed r reader, and it should automatically count the read bytes; and if the read bytes exceeds the n number, it must not read further bytes from r but return io.EOF which is used to indicate that the end of the stream is reached. It could be that the r reader has more bytes (meaning more bytes could be read from it), but that should not be read and returned: that's the purpose of the LimitedReader.

    If you think you understand the problem now, stop reading the answer for now, and try to implement this yourself. What follows is an example solution.


    This exact functionality already exists in the io package, and goes by the same name: io.LimitReader().

    // LimitReader returns a Reader that reads from r
    // but stops with EOF after n bytes.
    // The underlying implementation is a *LimitedReader.
    func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
    

    It returns a value of *io.LimitedReader, whose implementation is this:

    // A LimitedReader reads from R but limits the amount of
    // data returned to just N bytes. Each call to Read
    // updates N to reflect the new amount remaining.
    type LimitedReader struct {
        R Reader // underlying reader
        N int64  // max bytes remaining
    }
    
    func (l *LimitedReader) Read(p []byte) (n int, err error) {
        if l.N <= 0 {
            return 0, EOF
        }
        if int64(len(p)) > l.N {
            p = p[0:l.N]
        }
        n, err = l.R.Read(p)
        l.N -= int64(n)
        return
    }
    

    Take a minute or 2 to try to understand the code yourself. If you're stuck or not everything is clear, read on.

    Explaining the code:

    io.LimitedReader is a struct, which contains the source reader it reads from, and the number of bytes that can still be read without having to report io.EOF. So LimitReader() simply just returns a value of this struct where the parameters r and n are assigned to the fields R and N of the struct. More specifically an address of this struct is returned, because LimitedReader.Read() has pointer receiver, and so only a pointer to it implements io.Reader. And it has a pointer receiver, because the Read() method (may) modify the fields of the struct, so a pointer is required to be able to do that (else only a copy would be modified which is discarded after the Read() method returns).

    LimitedReader.Read() first checks the N field which tells how many more bytes can we return, and if there is no more "allowed", being a well-behaved Limited-reader, it returns io.EOF immedately without reading any more bytes from the source:

    if l.N <= 0 {
        return 0, EOF
    }
    

    If N is positive, that means some bytes may be read and returned, but no more than N, so if the p slice passed to Read() has a greater length, we re-slice it so a call to the source reader will not read more than what we should allow:

    if int64(len(p)) > l.N {
        p = p[0:l.N]
    }
    

    And the last part is nothing more that actually doing the reading, from the source reader:

    n, err = l.R.Read(p)
    

    Which returns the number of actually read bytes, and an optional error (whether reading encountered some error). Since we're about to return these, we have to administer that this amount of bytes are now returned, we have to subtract this from the allowed remaining bytes:

    l.N -= int64(n)
    return