Если вы не хотите читать, а просто пропускаете строки, которые вы прочитали ранее, вам нужно усвоить позицию, на которой вы остановились.
Различные решения представлены в виде функции, которая берет входные данные для чтения и начальную позицию (байтовую позицию) для начала чтения строк, например:
func solution(input io.ReadSeeker, start int64) error
Используется специальный ввод io.Reader
, который также реализует io.Seeker
, общий интерфейс, который позволяет пропускать данные, не читая их. *os.File
реализует это, поэтому вам разрешено передавать *File
этим функциям. Хорошо. «Объединенный» интерфейс io.Reader
и io.Seeker
— это io.ReadSeeker
.
Если вам нужен чистый старт (чтобы начать чтение с начала файла), просто передайте start = 0
. Если вы хотите возобновить предыдущую обработку, передайте позицию байта, в которой последняя обработка была остановлена/прервана. Эта позиция является значением локальной переменной pos
в функциях (решениях) ниже.
Все приведенные ниже примеры вместе с тестовым кодом можно найти на Go Playground.
1. С bufio.Scanner
bufio.Scanner
не поддерживает позицию, но мы можем очень легко расширить ее, чтобы сохранить позицию ( читать байты), поэтому, когда мы хотим перезапустить следующий, мы можем искать эту позицию.
Чтобы сделать это с минимальными усилиями, мы можем использовать новую функцию разделения, которая разбивает ввод на токены (строки). Мы можем использовать Scanner.Split()
для установки функции разделения (логика определения границ токенов/линий). Функция разделения по умолчанию: bufio.ScanLines()
.
Давайте взглянем на объявление разделенной функции: bufio.SplitFunc
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
Он возвращает количество байтов для продвижения: advance
. Именно то, что нам нужно для сохранения позиции файла. Таким образом, мы можем создать новую функцию разделения, используя встроенную функцию bufio.ScanLines()
, поэтому нам даже не нужно реализовывать ее логику, просто используйте возвращаемое значение advance
для сохранения позиции:
func withScanner(input io.ReadSeeker, start int64) error {
fmt.Println("--SCANNER, start:", start)
if _, err := input.Seek(start, 0); err != nil {
return err
}
scanner := bufio.NewScanner(input)
pos := start
scanLines := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
advance, token, err = bufio.ScanLines(data, atEOF)
pos += int64(advance)
return
}
scanner.Split(scanLines)
for scanner.Scan() {
fmt.Printf("Pos: %d, Scanned: %s\n", pos, scanner.Text())
}
return scanner.Err()
}
2. С bufio.Reader
В этом решении мы используем тип bufio.Reader
вместо Scanner
. bufio.Reader
уже имеет метод ReadBytes()
, который очень похож на функцию "чтения строки". если мы передаем байт '\n'
в качестве разделителя.
Это решение похоже на решение JimB, с добавлением обработки всех допустимых последовательностей конца строки, а также их удаление из строки чтения (они нужны очень редко); в нотации регулярных выражений это \r?\n
.
func withReader(input io.ReadSeeker, start int64) error {
fmt.Println("--READER, start:", start)
if _, err := input.Seek(start, 0); err != nil {
return err
}
r := bufio.NewReader(input)
pos := start
for {
data, err := r.ReadBytes('\n')
pos += int64(len(data))
if err == nil || err == io.EOF {
if len(data) > 0 && data[len(data)-1] == '\n' {
data = data[:len(data)-1]
}
if len(data) > 0 && data[len(data)-1] == '\r' {
data = data[:len(data)-1]
}
fmt.Printf("Pos: %d, Read: %s\n", pos, data)
}
if err != nil {
if err != io.EOF {
return err
}
break
}
}
return nil
}
Примечание. Если содержимое заканчивается пустой строкой (разделитель строки), это решение обработает пустую строку. Если вы этого не хотите, вы можете просто проверить это следующим образом:
if len(data) != 0 {
fmt.Printf("Pos: %d, Read: %s\n", pos, data)
} else {
// Last line is empty, omit it
}
Тестирование решений:
Тестовый код будет просто использовать содержимое "first\r\nsecond\nthird\nfourth"
, которое содержит несколько строк с разным окончанием строки. Мы будем использовать strings.NewReader()
для получения io.ReadSeeker
, источником которого является string
.
Тестовый код сначала вызывает withScanner()
и withReader()
, передавая 0
начальную позицию: чистый старт. В следующем раунде мы передадим начальную позицию start = 14
, которая является позицией 3. строки, поэтому мы не увидим обработанных (напечатанных) первых 2 строк: resume симуляция.
func main() {
const content = "first\r\nsecond\nthird\nfourth"
if err := withScanner(strings.NewReader(content), 0); err != nil {
fmt.Println("Scanner error:", err)
}
if err := withReader(strings.NewReader(content), 0); err != nil {
fmt.Println("Reader error:", err)
}
if err := withScanner(strings.NewReader(content), 14); err != nil {
fmt.Println("Scanner error:", err)
}
if err := withReader(strings.NewReader(content), 14); err != nil {
fmt.Println("Reader error:", err)
}
}
Выход:
--SCANNER, start: 0
Pos: 7, Scanned: first
Pos: 14, Scanned: second
Pos: 20, Scanned: third
Pos: 26, Scanned: fourth
--READER, start: 0
Pos: 7, Read: first
Pos: 14, Read: second
Pos: 20, Read: third
Pos: 26, Read: fourth
--SCANNER, start: 14
Pos: 20, Scanned: third
Pos: 26, Scanned: fourth
--READER, start: 14
Pos: 20, Read: third
Pos: 26, Read: fourth
Попробуйте решения и тестируйте код на Go Playground.
person
icza
schedule
07.01.2016
scanner.Scan()
30 раз независимо от значения вашего счетчика. Если вы считаете, что это слишком медленно (что сомнительно для файла конфигурации), сохраните смещение и используйте golang.org /pkg/os/#File.ReadAt - person kopiczko   schedule 07.01.2016