Как протестировать EOF на io.Reader в Go?

В документации Go io.Reader указано, что Read() может возвращать ненулевое значение n и io.EOF одновременно. К сожалению, метод Read() для File этого не делает.

Когда EOF достигнут и некоторые байты все еще могут быть прочитаны, метод чтения файла возвращает ненулевую ошибку n и nil. Только когда мы пытаемся прочитать, когда уже в конце файла, мы возвращаем ноль n и io.EOF как ошибку.

Я не смог найти простой способ проверить, достигнут ли EOF, не пытаясь прочитать данные из файла. Если мы выполним Read() с буфером из 0 байт, мы получим нулевую ошибку n и nil, хотя мы находимся в конце файла.

Чтобы избежать этого последнего чтения, единственное решение, которое я нашел, - это самостоятельно отслеживать количество байтов, оставшихся для чтения в файле. Есть ли более простое решение?


person chmike    schedule 18.12.2016    source источник
comment
не афаик. вы можете использовать поиск или что-то еще, чтобы получить текущую позицию, но, поскольку для этого потребуется гораздо больше системных вызовов, это будет медленнее.   -  person Not_a_Golfer    schedule 18.12.2016
comment
Почему бы не создать тип, который всегда считывает несколько байтов и возвращает EOF?   -  person JimB    schedule 18.12.2016
comment
@JimB Это потрясающий трюк. Я приму это решение. Но вместо опережающего чтения я буду отслеживать байты, оставшиеся для чтения, чтобы вовремя возвращать io.EOF. Причина, по которой мне это нравится больше всего, заключается в том, что мне не нужно изменять код где-либо в моей программе. Мне нужно только предоставить оболочку для File. Если вы напишете хороший ответ с двумя вариантами, я вам его предоставлю.   -  person chmike    schedule 18.12.2016


Ответы (1)


Вы можете создать новый тип, который отслеживает количество прочитанных байтов. Затем, во время проверки EOF, вы можете сравнить ожидаемое количество прочитанных байтов с фактическим количеством прочитанных байтов. Вот пример реализации. eofReader отслеживает количество прочитанных байтов и сравнивает его с размером файла, если базовым типом является файл:

package main

// ... imports 

// eofReader can be checked for EOF, without a Read. 
type eofReader struct {
    r     io.Reader
    count uint64
}

// AtEOF returns true, if the number of bytes read equals the file size.
func (r *eofReader) AtEOF() (bool, error) {
    f, ok := r.r.(*os.File)
    if !ok {
        return false, nil
    }
    fi, err := f.Stat()
    if err != nil {
        return false, err
    }
    return r.Count() == uint64(fi.Size()), nil
}

// Read reads and counts.
func (r *eofReader) Read(buf []byte) (int, error) {
    n, err := r.r.Read(buf)
    atomic.AddUint64(&r.count, uint64(n))
    return n, err
}

// Count returns the count.
func (r *eofReader) Count() uint64 {
    return atomic.LoadUint64(&r.count)
}

Вы можете использовать этот тип, обернув любой ридер в eofReader:

func main() {
    f, err := os.Open("main.go")
    if err != nil {
        log.Fatal(err)
    }

    r := &eofReader{r: f}
    log.Println(r.AtEOF())

    if _, err = ioutil.ReadAll(r); err != nil {
        log.Fatal(err)
    }

    log.Println(r.AtEOF())
}

// 2016/12/19 03:49:35 false <nil>
// 2016/12/19 03:49:35 true <nil>

Закодируйте как суть.

person miku    schedule 19.12.2016