Голанг ставит рекорд на пит-стопе (1)

Go

В этой серии в основном записаны некоторые ямки, на которые наступили во время разработки, что удобно для будущих запросов.

ямы читать построчно

Когда вам нужно прочитать файл построчно, примерный код может выглядеть так

func main() {
    scanner := bufio.NewScanner(io.Reader)
    for scanner.Scan() {
        line := scanner.Text()
    }
}

Здесь имеется в виду входной источник io.Reader

Однако недавно возникла проблема, возникшая при разработке, в соответствии с вышеупомянутым образом появляется необъяснимая плоскость, файл не был просканирован и завершается, и процесс не сообщил об ошибках; повторно проверьте код и не обнаружили проблем, подозреваемыйbufio.ScannerЗатем я перечитал документацию по Go и нашел пример на официальном сайте:

scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
    fmt.Println(scanner.Text()) // Println will add back the final '\n'
}
if err := scanner.Err(); err != nil {
    fmt.Fprintln(os.Stderr, "reading standard input:", err)
}

оригинальныйscannerесть еще одинErr()метод, официальное заявлениеErr returns the first non-EOF error that was encountered by the Scanner., то есть еслиscanner.Scan()При возникновении ошибки сообщение об ошибке должно пройтиErr()Метод Get, моя программа пошла ошибаться проигнорировала это и увидеть эту ошибку после кода, чтобы добавить полный:bufio.Scanner: token too long

затем проверьтеScan()Исходный код, нашел следующую логику

// Is the buffer full? If so, resize.
if s.end == len(s.buf) {
	// Guarantee no overflow in the multiplication below.
	const maxInt = int(^uint(0) >> 1)
	if len(s.buf) >= s.maxTokenSize || len(s.buf) > maxInt/2 {
		s.setErr(ErrTooLong)
		return false
	}
	newSize := len(s.buf) * 2
	if newSize == 0 {
		newSize = startBufSize
	}
	if newSize > s.maxTokenSize {
		newSize = s.maxTokenSize
	}
	newBuf := make([]byte, newSize)
	copy(newBuf, s.buf[s.start:s.end])
	s.buf = newBuf
	s.end -= s.start
	s.start = 0
}

оригинальныйScannerЕсть настройка при инициализацииmaxTokenSize, это значение по умолчанию равноMaxScanTokenSize = 64 * 1024, когда длина линии больше, чем64*1024который65536После этого появитсяErrTooLongэта ошибка

Также найдено в официальной документации:

Scanning stops unrecoverably at EOF, the first I/O error, or a token too large to fit in the buffer. When a scan stops, the reader may have advanced arbitrarily far past the last token. Programs that need more control over error handling or large tokens, or must run sequential scans on a reader, should use bufio.Reader instead.

Это примерно означает, что если токен слишком велик (слишком длинная строка), используйтеbufio.Reader,ноbufio.ReaderЭтой проблемы не существует?

Прочтите следующееbufio.ReaderОбнаружено, что код имеет ограничение на размер буфера, и размер буфера по умолчанию равен4096, но есть функцияNewReaderSizeРазмер этого буфера можно регулировать.

Проверятьbufio.Readerкоторый предоставилReadLine()функция

ReadLine is a low-level line-reading primitive. Most callers should use ReadBytes('\n') or ReadString('\n') instead or use a Scanner.

Общий смысл в том, что эта функция относительно низкоуровневая, рекомендуется использоватьReadBytes('\n')илиReadString('\n')илиScanner, продолжить смотреть инструкции

ReadLine tries to return a single line, not including the end-of-line bytes.If the line was too long for the buffer then isPrefix is set and the beginning of the line is returned. The rest of the line will be returned from future calls. isPrefix will be false when returning the last fragment of the line. The returned buffer is only valid until the next call to ReadLine. ReadLine either returns a non-nil line or it returns an error,never both.

Отсюда мы можем видеть роль установки буфера,ReadLineПопытается прочитать и вернуть полную строку, но если строка слишком длинная и буфер заполнен, он не вернет полную строку, а вернет содержимое буфера и установитisPrefixзаtrue; В это время нужно продолжать звонитьReadLineПока вся строка не будет прочитана, внешней вызывающей программе необходимо соединить блоки вместе, чтобы сформировать полную строку. не только иметь дело сisPrefix, но и с приставкой разбираться тоже хлопотно! Если мы активно не устанавливаем размер буфера, но предполагается, что вы должны знать длину самой длинной строки, в большинстве случаев это невозможно предсказать заранее. Не зря рекомендуется использоватьReadBytesилиReadStringилиScanner.

тогда см.ReadBytesиReadStringисходник, нашелReadStringназываетсяReadBytesReadBytesПроблема с размером буфера была решена

for {
	var e error
	frag, e = b.ReadSlice(delim)
	if e == nil { // got final fragment
		break
	}
	if e != ErrBufferFull { // unexpected error
		err = e
		break
	}

	// Make a copy of the buffer.
	buf := make([]byte, len(frag))
	copy(buf, frag)
	full = append(full, buf)
}

по сравнению сReadLineБыть удобным, но обязательно сделайте это разделитель PIT MARK, если ваши файлы написаны в «A \ NB \ NC \ ND», используйтеReadStringКогда вы пропустите этоdвывод, но в последнийlineВ нем все еще есть данные, поэтому вам нужно вручную судить об этом самостоятельно

Из вышеизложенного делаем вывод:

  • ScannerОграничение размера одной строки в 65536
  • bufio.ReaderРазмер буфера 4096
  • Если данные не являются особыми случаями, использование чтения построчноReadStringНаиболее удобно