Irbisa · Шпаргалка

Стандартная библиотека на практике

io и bufio, encoding/json, time, strconv, regexp, embed

Middle-разработчик · 23 вопроса

  1. 01

    Какие поля структуры кодирует encoding/json и что может изменить тег json:"..."?

    encoding/json видит только экспортируемые поля: те, что начинаются с заглавной буквы.

    // wrong: nothing is exported: Marshal returns "{}" and a nil errortype order struct {	id     int64	total  int64 `json:"total"` // a tag on an unexported field does nothing	status string}…
  2. 02

    Как прочитать файл в Go (os.ReadFile, os.Open с bufio или io.Copy) и когда что выбирать?

    os.ReadFile загружает весь файл в память одним вызовом и подходит для небольших файлов вроде конфигов.

    // small file: one call, memory = file sizefunc loadJSON(path string, v any) error {	b, err := os.ReadFile(path)	if err != nil {		return err	}…
  3. 03

    Что bufio.Writer делает с записанными в него байтами и почему перед закрытием файла обязателен Flush?

    bufio.Writer держит вывод в буфере в памяти (по умолчанию 4 КБ) и пишет в нижележащий файл, только когда буфер заполнился или когда вы вызвали Flush.

    type Row struct {	ID   int64	Name string} func export(path string, rows []Row) (err error) {…
  4. 04

    Что выведет этот код и как правильно превратить число в строку и обратно?

    func main() {	n := 65	a := string(n)	b := strconv.Itoa(n)	c, err := strconv.Atoi("65 ")	fmt.Printf("%q %q %d %v\n", a, b, c, err)}

    Выведет "A" "65" 0 strconv.Atoi: parsing "65 ": invalid syntax.

  5. 05

    Почему в Go дата форматируется layout'ом 2006-01-02 15:04:05, а не YYYY-MM-DD?

    Layout в Go представляет собой пример одного эталонного времени Mon Jan 2 15:04:05 MST 2006, записанный так, как вы хотите видеть результат.

    const layout = "2006-01-02 15:04:05" // an example, not a pattern func main() {	t := time.Date(2024, 3, 5, 9, 7, 0, 0, time.UTC) 	// wrong: Format never errors: unknown letters are copied through as themselves…
  6. 06

    Что делает //go:embed, какие типы переменных он умеет заполнять и почему он чинит путь к файлу, который работает из директории проекта, но не на сервере?

    //go:embed копирует файлы из директории пакета в бинарник на этапе сборки: в string, []byte или embed.FS.

    //go:embed version.txtvar version string // one file as a string //go:embed templates/*.htmlvar tmplFS embed.FS…
  7. 07

    Когда нужен crypto/rand, а не math/rand, и что изменилось с появлением math/rand/v2 и в сидировании?

    crypto/rand нужен для всего, что атакующий не должен угадать: токенов, id сессий, ключей, ссылок для сброса пароля.

    import (	crand "crypto/rand"	"math/rand/v2"	"time")…
  8. 08

    Как загрузить файл, попутно сжимая его gzip, считая хеш и подсчитывая объём (за один проход и не держа его в памяти), только средствами стандартной библиотеки?

    Все потоковые типы стандартной библиотеки опираются на два интерфейса с одним методом (io.Reader и io.Writer), поэтому сжатие, хеширование и подсчёт сводятся к обёрткам, которые надеваются друг на друга, а всю цепочку крутит io.Copy.

    // One pass: gzip to dst, sha256 of the plaintext, count of compressed bytes.func upload(dst io.Writer, name string) (sum []byte, n int64, err error) {	f, err := os.Open(name)	if err != nil {		return nil, 0, err	}…
  9. 09

    Чем bufio.Scanner отличается от bufio.Reader и почему Scanner на некоторых файлах останавливается с token too long?

    bufio.Scanner работает как удобный токенизатор (строка или слово на каждый Scan()), но он отказывается от любого токена больше 64 КБ (bufio.MaxScanTokenSize).

    func importLines(r io.Reader, handle func([]byte) error) error {	s := bufio.NewScanner(r)	s.Buffer(make([]byte, 0, 64*1024), 4*1024*1024) // 64 KB start, 4 MB cap	for s.Scan() {		// s.Bytes() aliases the scanner's buffer: copy it if you keep it		if err := handle(s.Bytes()); err != nil {…
  10. 10

    Что omitempty считает пустым и почему нулевой time.Time всё равно попадает в JSON?

    omitempty пропускает false, 0, "", nil-указатели и интерфейсы, а также слайсы, мапы и массивы нулевой длины; структура для него пустой не бывает, поэтому нулевой time.Time уходит как "0001-01-01T00:00:00Z".

    type Profile struct {	Tags    []string  `json:"tags"`              // nil -> null, []string{} -> []	Nick    string    `json:"nick,omitempty"`    // "" -> key absent	Score   int       `json:"score,omitempty"`   // 0 -> absent: "score 0" cannot be sent	Seen    *int      `json:"seen,omitempty"`    // nil -> absent, &0 -> "seen":0	Created time.Time `json:"created,omitempty"` // wrong: a struct is never "empty"…
  11. 11

    Ретранслятор вебхуков декодирует payload в map[string]any и кодирует обратно. Что выведет этот код и почему большие id приходят изменёнными?

    const raw = `{"order_id":12345678901234567}` func main() {	var m map[string]any	json.Unmarshal([]byte(raw), &m)	fmt.Printf("%T\n", m["order_id"]) 	out, _ := json.Marshal(m)	fmt.Println(string(out))}

    Выведет float64, а затем {"order_id":12345678901234568} (на единицу больше).

  12. 12

    Ключ конфига max_conns стал maxConnections, и сервис молча поднялся с пулом в ноль. Какие правила encoding/json это допустили и как заставить его падать?

    Неизвестные ключи молча отбрасываются, поэтому переименованный ключ исчез, а MaxConns остался нулём; известные ключи к тому же сопоставляются без учёта регистра, что прячет другие опечатки.

    type Config struct {	Addr     string `json:"addr"`	MaxConns int    `json:"max_conns"`} func load(r io.Reader) (Config, error) {…
  13. 13

    Найдите баг: у некоторых заказов появляется купон, которого клиент не вводил.

    type Order struct {	ID     string `json:"id"`	Coupon string `json:"coupon"`} func parseOrders(lines [][]byte) ([]Order, error) {	var out []Order	var o Order	for _, line := range lines {		if err := json.Unmarshal(line, &o); err != nil {			return nil, err		}		out = append(out, o)	}	return out, nil}

    o объявлена один раз, вне цикла, а json.Unmarshal заполняет только те поля, что есть во входных данных.

  14. 14

    Этот MarshalJSON должен кодировать Event.At простой датой. Почему первый же вызов роняет весь процесс и как это исправить?

    type Event struct {	Name string    `json:"name"`	At   time.Time `json:"at"`} func (e Event) MarshalJSON() ([]byte, error) {	type payload = Event // "a copy of Event without the method"	return json.Marshal(struct {		payload		At string `json:"at"`	}{payload(e), e.At.Format(time.DateOnly)})}

    json.Marshal проверяет, реализует ли тип json.Marshaler, раньше, чем смотрит на поля.

  15. 15

    Что меняет экспериментальный encoding/json/v2 из Go 1.25 по сравнению с encoding/json и когда команды всё ещё выбирают easyjson или sonic?

    encoding/json/v2 появился в Go 1.25 как эксперимент за флагом GOEXPERIMENT=jsonv2: переписанная реализация со строгими настройками по умолчанию и быстрым декодированием.

  16. 16

    Что такое рефлексия в Go, где на неё опирается стандартная библиотека и чем она обходится?

    Пакет reflect позволяет коду изучать и менять значения, тип которых известен только во время выполнения: их вид, поля, теги структур и методы.

    type Config struct {	Host string `env:"APP_HOST"`	Port int    `env:"APP_PORT"`} // Fills string and int fields from the env vars named in `env` tags.…
  17. 17

    Почему regexp в Go гарантированно работает за линейное время и чего он не умеет?

    regexp в Go реализует RE2: он сопоставляет через автоматы, а не перебором с возвратами, поэтому время линейно от размера входа, и никакой паттерн или строка не вызовут катастрофический backtracking (ReDoS).

  18. 18

    Как загрузить конфигурацию Go-сервиса только средствами стандартной библиотеки (дефолты, переменные окружения, флаги) и где место валидации?

    Начните со структуры с дефолтами, пусть переменные окружения их переопределяют, а флаги командной строки переопределяют и то и другое; затем один раз при старте провалидируйте готовую структуру и откажитесь стартовать, если что-то не так.

    type Config struct {	Addr        string	DatabaseURL string	Timeout     time.Duration}…
  19. 19

    Как написать TCP-сервер на net.Listen и Accept и что нужно добавить для дедлайнов и graceful shutdown?

    net.Listen("tcp", addr) возвращает listener; цикл вызывает Accept и отдаёт каждое net.Conn в собственную горутину.

    type Server struct {	ln    net.Listener	wg    sync.WaitGroup	conns sync.Map      // live connections	done  chan struct{} // closed by Shutdown: the quit signal}…
  20. 20

    NTP переводит часы на 400 мс назад: time.Since(start) остаётся верным, а end.UnixNano() - start.UnixNano() уходит в минус. Что несёт в себе time.Time, из-за чего такая разница?

    time.Time, полученный из time.Now(), несёт двое часов: настенное показание и монотонное.

  21. 21

    Как загрузить NDJSON-файл на 40 ГБ, где форма data зависит от поля type каждой строки, в постоянной памяти и декодируя каждую строку в Go-значения один раз?

    Один json.Decoder поверх reader'а декодирует по одному значению за раз в постоянной памяти, а поле json.RawMessage придерживает байты data недекодированными, пока type не скажет, в какую структуру они ложатся; так каждая строка декодируется в Go-значения один раз.

    type Envelope struct {	Type string          `json:"type"`	Data json.RawMessage `json:"data"` // raw bytes until Type is known} type Push struct{ Ref string `json:"ref"` }…
  22. 22

    Что unsafe.Pointer умеет такого, чего не может обычный Go, и почему преобразование []byte в string без копирования через unsafe.String становится багом, если байты меняются?

    unsafe.Pointer преобразуется в любой тип указателя и в uintptr и обратно, позволяя переинтерпретировать память и делать арифметику указателей в обход системы типов.

    func main() {	buf := []byte("alice") 	// wrong: zero-copy, so name shares buf's bytes	name := unsafe.String(unsafe.SliceData(buf), len(buf))	seen := map[string]bool{name: true}…
  23. 23

    Найдите баг: checksum от gzip.Reader обычно не совпадает с sha256sum распакованного файла, хотя ошибки нет.

    // called as: zr, _ := gzip.NewReader(f); sum, err := checksum(zr)func checksum(r io.Reader) ([]byte, error) {	h := sha256.New()	buf := make([]byte, 32*1024)	for {		n, err := r.Read(buf)		if err == io.EOF {			break		}		if err != nil {			return nil, err		}		h.Write(buf[:n])	}	return h.Sum(nil), nil}

    Цикл проверяет err раньше, чем использует n.