Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В Go пустой интерфейс
interface{}
является особым типом, который может содержать значение любого типа. Это связано с тем, что в Go любой тип реализует пустой интерфейс, поскольку в нем нет методов, которые нужно реализовать. Поскольку пустой интерфейс не требует реализации каких-либо методов, любой тип в Go автоматически реализует этот интерфейс. Это делает пустой интерфейс универсальным контейнером для значений любых типов.
type interface{} interface {}
Типа конкретного значения
Самого значения
Когда значение присваивается переменной типа интерфейс, Go сохраняет информацию о типе и значении этого значения. Для пустого интерфейса эта информация может быть любого типа.
Когда значение из пустого интерфейса приводится к конкретному типу, происходит проверка типа во время выполнения. Если значение внутри интерфейса действительно является указанным типом, приведение успешно. В противном случае приведение не удается, и возвращается значение
nil
или происходит паника, если приведение выполнено без проверки.Присваивание значений пустому интерфейсу
package main
import "fmt"
func main() {
var i interface{}
i = 42
fmt.Println(i) // 42
i = "hello"
fmt.Println(i) // hello
}
Утверждение типа (Type Assertion)
package main
import "fmt"
func main() {
var i interface{} = "hello"
// Утверждение типа с проверкой
s, ok := i.(string)
if ok {
fmt.Println("String:", s)
} else {
fmt.Println("Not a string")
}
// Утверждение типа без проверки
// Это вызовет панику, если тип не соответствует
s = i.(string)
fmt.Println("String:", s)
}
Использование switch для проверки типа
package main
import "fmt"
func printType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Println("String:", v)
case int:
fmt.Println("Integer:", v)
case bool:
fmt.Println("Boolean:", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
func main() {
printType("hello")
printType(42)
printType(true)
printType(3.14)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Денормализация нужна, когда система испытывает проблемы с производительностью при чтении данных. Особенно полезна, если запросы часто требуют объединения нескольких таблиц. Это снижает нагрузку на базу, уменьшает количество операций соединения и ускоряет выборки — за счёт хранения дублирующих данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это гибкий и мощный инструмент для работы с последовательностями элементов. Они предоставляют более высокоуровневый интерфейс для работы с массивами. Рассмотрим, как с ними работать, почему они нужны и какие операции можно выполнять.
Это динамическая последовательность элементов одного типа, которая предоставляет доступ к части или всем элементам массива без копирования данных. Он содержит три компонента:
Указатель на массив.
Длина (количество элементов в слайсе).
Ёмкость (максимальное количество элементов, которые могут быть включены в слайс без перераспределения памяти).
Слайсы позволяют работать с массивами более гибко:
В отличие от массивов, длина слайса может изменяться.
Слайсы можно передавать в функции и возвращать из них, не копируя данные.
Предоставляют множество встроенных функций для работы с последовательностями данных.
Из массива
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // слайс содержит элементы {2, 3, 4}
Используя make
slice := make([]int, 5) // создаёт слайс длиной и ёмкостью 5, заполненный нулями
Литерал слайса
slice := []int{1, 2, 3, 4, 5}
Доступ к элементам
fmt.Println(slice[0]) // выводит первый элемент слайса
Изменение элементов
slice[1] = 10 // изменяет второй элемент слайса
Добавление элементов
slice = append(slice, 6, 7) // добавляет элементы 6 и 7 к слайсу
Срезка (slicing)
newSlice := slice[1:3] // создаёт новый слайс с элементами с 1-го по 3-й
Рассмотрим пример функции, которая добавляет элемент в слайс и возвращает новый слайс
package main
import "fmt"
func main() {
nums := []int{1, 2, 3}
nums = append(nums, 4) // добавление элемента
fmt.Println(nums) // выводит [1, 2, 3, 4]
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это структура данных, которая используется для хранения и поиска пар "ключ-значение". Обеспечивают быстрый доступ к данным по ключу, обычно с константным временем доступа в среднем случае. Основой работы хэш-таблицы является хеш-функция, которая преобразует ключ в индекс, по которому хранится значение.
Функция, которая принимает ключ и преобразует его в индекс массива, называемого "хэш-таблицей". Хорошая хеш-функция распределяет ключи равномерно по хэш-таблице, минимизируя количество коллизий.
Массив фиксированного размера, где каждый элемент называется "корзиной" (bucket). Корзина может содержать одно или несколько значений.
Ситуация, когда два разных ключа хешируются в один и тот же индекс. Коллизии решаются с помощью различных методов, таких как цепочки (chaining) или открытая адресация (open addressing).
Хеш-функция вычисляет индекс для данного ключа. Значение помещается в соответствующую корзину по этому индексу. Если возникает коллизия, используется метод разрешения коллизий.
Хеш-функция вычисляет индекс для ключа. Корзина по этому индексу проверяется на наличие значения. Если значение найдено, оно возвращается; если нет, возвращается индикатор отсутствия значения.
Хеш-функция вычисляет индекс для ключа. Значение удаляется из соответствующей корзины. При необходимости корректируются ссылки или структура данных для разрешения коллизий.
Среднее время доступа к элементу составляет O(1).
Обеспечивает простой интерфейс для вставки, поиска и удаления данных.
Требуют дополнительных механизмов для разрешения, что может усложнить реализацию.
Эффективность хэш-таблицы зависит от качества хеш-функции.
При увеличении количества элементов может потребоваться перераспределение и увеличение размера таблицы, что временно снижает производительность.
package main
import "fmt"
func main() {
// Создание карты
myMap := make(map[string]int)
// Вставка значений
myMap["Alice"] = 25
myMap["Bob"] = 30
// Поиск значений
value, exists := myMap["Alice"]
if exists {
fmt.Println("Alice:", value) // Alice: 25
} else {
fmt.Println("Alice not found")
}
// Удаление значений
delete(myMap, "Alice")
_, exists = myMap["Alice"]
if !exists {
fmt.Println("Alice has been deleted") // Alice has been deleted
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Пустой интерфейс нужен для универсальности: он позволяет писать код, не привязанный к конкретным типам. Примеры использования включают универсальные контейнеры (например, массивы и словари) и функции, работающие с любыми типами. Например, в логировании часто используют interface{} для передачи любых данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В многопоточных (параллельных) программах горутины (goroutines) могут одновременно изменять одни и те же данные. Если не синхронизировать доступ, это приведёт к гонке данных (data race), когда несколько потоков читают/пишут одно и то же значение одновременно.
Используется для блокировки критической секции кода, чтобы в один момент только одна горутина могла изменять данные.
package main
import (
"fmt"
"sync"
)
var (
counter int
mutex sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем доступ
counter++ // Изменяем данные
mutex.Unlock() // Разблокируем доступ
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Итоговый счетчик:", counter) // 1000
}
Позволяет нескольким горутинам читать данные одновременно, но блокирует запись.
var (
data int
mutex sync.RWMutex
)
func readData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.RLock() // Разрешаем чтение
fmt.Println("Читаем данные:", data)
mutex.RUnlock()
}
func writeData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем на запись
data++
mutex.Unlock()
}
Позволяет дождаться завершения всех горутин без блокировки данных.
var wg sync.WaitGroup
wg.Add(2) // Ожидаем 2 горутины
go func() {
defer wg.Done()
fmt.Println("Горутина 1 завершилась")
}()
go func() {
defer wg.Done()
fmt.Println("Горутина 2 завершилась")
}()
wg.Wait() // Ждём завершения всех горутин
fmt.Println("Все горутины завершены")
Атомарные операции быстрее мьютексов и гарантируют безопасное обновление переменных без гонок данных.
import "sync/atomic"
var counter int64
func incrementAtomic() {
atomic.AddInt64(&counter, 1) // Атомарное увеличение
}
В Go рекомендуется избегать блокировок и использовать каналы для передачи данных между горутинами.
package main
import "fmt"
func main() {
ch := make(chan int) // Канал для передачи данных
go func() {
ch <- 42 // Отправляем данные
}()
data := <-ch // Получаем данные
fmt.Println("Получено:", data)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Карты (maps) представляют собой ассоциативные массивы, которые связывают ключи с соответствующими значениями. Работа с картами включает получение и запись значений, и Go предоставляет удобный синтаксис для этих операций. Рассмотрим особенности синтаксиса получения и записи значений в карты.
Для этого используется синтаксис индексирования
value := myMap[key]
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
value := myMap["Alice"]
fmt.Println("Alice:", value) // Alice: 25
}
Для этого используется синтаксис двойного присваивания
value, exists := myMap[key]
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
value, exists := myMap["Charlie"]
if exists {
fmt.Println("Charlie:", value)
} else {
fmt.Println("Charlie not found")
}
}
Для этого используется синтаксис индексирования:
myMap[key] = value
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{}
// Добавление значений
myMap["Alice"] = 25
myMap["Bob"] = 30
fmt.Println("Map:", myMap) // Map: map[Alice:25 Bob:30]
// Обновление значения
myMap["Alice"] = 26
fmt.Println("Updated Map:", myMap) // Updated Map: map[Alice:26 Bob:30]
}
Для этого используется встроенная функция
delete
:delete(myMap, key)
Пример
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
}
// Удаление значения по ключу
delete(myMap, "Alice")
fmt.Println("Map after deletion:", myMap) // Map after deletion: map[Bob:30]
}
Если ключ отсутствует в карте, при попытке получения значения будет возвращено нулевое значение типа значения карты. Например, для карты
map[string]int
это будет 0
.package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
}
value := myMap["Bob"] // Ключ "Bob" отсутствует
fmt.Println("Value:", value) // Value: 0
}
Нельзя записывать значения в
nil
карту. Это приведет к панике времени выполнения.package main
func main() {
var myMap map[string]int // nil карта
myMap["Alice"] = 25 // Вызовет панику: runtime error: assignment to entry in nil map
}
Порядок итерации по карте не определен и может различаться между разными запусками программы.
package main
import "fmt"
func main() {
myMap := map[string]int{
"Alice": 25,
"Bob": 30,
"Carol": 35,
}
for key, value := range myMap {
fmt.Printf("%s: %d\n", key, value)
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Work stealing — это стратегия параллельной обработки задач, при которой неактивный поток берёт задачу у перегруженного потока.
Это помогает равномерно распределять нагрузку и повышает эффективность многопоточности. Используется в современных планировщиках, в том числе в языках с горутинами и акторной моделью.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Может быть как безопасным, так и небезопасным, в зависимости от контекста и конкретного использования.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := make([]byte, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
buffer[i] = byte(i) // Несколько горутин одновременно модифицируют буфер
}(i)
}
wg.Wait()
fmt.Println("Buffer:", buffer)
}
Если несколько горутин только читают из буфера, это безопасно. Пример безопасного использования:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")
for i := 0; i < len(buffer); i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Printf("buffer[%d]: %c\n", i, buffer[i])
}(i)
}
wg.Wait()
}
Для синхронизации доступа к буферу используйте мьютексы (
sync.Mutex
). Это гарантирует, что только одна горутина в данный момент модифицирует буфер.package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
buffer := make([]byte, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
mu.Lock()
buffer[i] = byte(i)
mu.Unlock()
}(i)
}
wg.Wait()
fmt.Println("Buffer:", buffer)
}
Если каждая горутина должна работать с независимой копией буфера, создавайте копии для каждой горутины.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
buffer := []byte("Hello, World!")
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
localBuffer := make([]byte, len(buffer))
copy(localBuffer, buffer)
localBuffer[i] = byte(i)
fmt.Printf("Goroutine %d: %s\n", i, localBuffer)
}(i)
}
wg.Wait()
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Всё зависит от типа числа:
- int, uint — обычно 8 байт на 64-битных системах.
- int32, uint32 — 4 байта.
- int64, uint64 — 8 байт.
- float32 — 4 байта, float64 — 8 байт.
Go явно различает размеры, что позволяет эффективно управлять памятью.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Каналы — это мощные инструменты для обмена данными между горутинами, обеспечивающие синхронизацию и безопасную коммуникацию. Основное различие между буферизированными и небуферизированными каналами заключается в их поведении при отправке и получении данных.
Не имеют внутренней емкости, т.е. они не могут хранить значения. Эти каналы требуют, чтобы отправитель и получатель были готовы обмениваться данными одновременно. Если одна сторона не готова, другая будет заблокирована:
Отправка данных в него блокирует отправителя до тех пор, пока получатель не прочитает данные из канала.
Получение данных из него блокирует получателя до тех пор, пока другая горутина не отправит данные в канал.
ch := make(chan int) // Создание небуферизированного канала
go func() {
val := <-ch // Блокируется, ожидая данные
fmt.Println("Received:", val)
}()
ch <- 3 // Блокируется, пока данные не будут получены
Имеют внутреннюю емкость, что позволяет хранить одно или несколько элементов без непосредственного получателя данных. Отправка или получение данных работает следующим образом:
Отправка блокируется, только если буфер заполнен. До этого момента данные могут быть отправлены без блокировки, даже если получатель не готов их принять.
Получение из буферизированного канала блокируется, только если канал пуст. Если в канале есть данные, получение происходит без блокировки.
ch := make(chan int, 2) // Создание буферизированного канала с емкостью 2
ch <- 1 // Отправка данных без блокировки
ch <- 2 // Отправка данных без блокировки
go func() {
val := <-ch // Получение данных без блокировки
fmt.Println("Received:", val)
}()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Да, JOIN можно использовать со вложенными (subquery) запросами. Варианты:
- JOIN с подзапросом, возвращающим таблицу (SELECT ... FROM (SELECT ...) AS subquery JOIN ...).
- JOIN с подзапросом в ON (SELECT ... FROM table1 JOIN (SELECT ...) AS subquery ON ...).
- Использование подзапроса в WHERE или IN, но это менее эффективно, чем JOIN.
Вложенные запросы могут снижать производительность, поэтому лучше использовать индексы и анализировать EXPLAIN.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это функции, которые принимают набор значений и возвращают одно агрегированное значение. В языке Go нет встроенных агрегатных функций, как в SQL, но их можно реализовать самостоятельно.
суммирует все элементы.
вычисляет среднее значение.
находит минимальный элемент.
находит максимальный элемент.
считает количество элементов.
Функция суммы (
SUM
) func Sum(nums []int) int {
sum := 0
for _, num := range nums {
sum += num
}
return sum
}
Функция среднего (
AVG
) func Average(nums []int) float64 {
if len(nums) == 0 {
return 0
}
return float64(Sum(nums)) / float64(len(nums))
}
Функция минимума (
MIN
) func Min(nums []int) int {
if len(nums) == 0 {
panic("empty slice")
}
min := nums[0]
for _, num := range nums {
if num < min {
min = num
}
}
return min
}
Функция максимума (
MAX
) func Max(nums []int) int {
if len(nums) == 0 {
panic("empty slice")
}
max := nums[0]
for _, num := range nums {
if num > max {
max = num
}
}
return max
}
Функция подсчёта (
COUNT
) func Count(nums []int) int {
return len(nums)
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Компилятору не нужно сообщать это явно – соответствие интерфейсу проверяется автоматически. Однако для явной декларации можно использовать конструкцию вида var _ InterfaceName = (*StructName)(nil).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это целочисленные значения, которые используются для доступа к элементам упорядоченных структур данных. В контексте Go индексы чаще всего применяются для работы со строками, массивами, срезами, а также картами (косвенно, через ключи).
Индексы позволяют обращаться к конкретным элементам массива, строки или среза. Например, если у нас есть массив чисел, индекс указывает, какой именно элемент извлечь.
С помощью индексов можно перебирать элементы массива, строки или среза, например, используя циклы.
В изменяемых структурах данных, таких как срезы или массивы, индекс позволяет присвоить новое значение конкретному элементу.
Индексы упрощают и ускоряют доступ к данным, потому что доступ осуществляется за O(1) (константное время) в массивах или срезах.
В строках индексы используются для доступа к конкретным байтам.
package main
import "fmt"
func main() {
str := "Привет"
fmt.Println(str[0]) // 208 (байт, не символ!)
fmt.Printf("%c\n", str[0]) // П (символ, представленный первым байтом UTF-8)
}
В массивах и срезах индексы используются для извлечения и изменения значений
package main
import "fmt"
func main() {
arr := [5]int{10, 20, 30, 40, 50}
fmt.Println(arr[2]) // 30
// Изменение значения по индексу
arr[2] = 100
fmt.Println(arr) // [10 20 100 40 50]
}
Обычно индексы используются для итерации по элементам коллекции с помощью цикла
for
.package main
import "fmt"
func main() {
nums := []int{10, 20, 30, 40, 50}
for i, v := range nums {
fmt.Printf("Индекс: %d, Значение: %d\n", i, v)
}
}
Индексы полезны для извлечения подстрок с использованием срезов:
package main
import "fmt"
func main() {
str := "Привет, Мир!"
fmt.Println(str[8:12]) // Мир
}
Если попытаться обратиться к элементу по индексу, который выходит за пределы коллекции, Go выдаст runtime panic:
package main
func main() {
nums := []int{1, 2, 3}
fmt.Println(nums[5]) // panic: runtime error: index out of range
}
Если неверно учитывать байтовое представление символов UTF-8, можно получить некорректный результат.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
1. Primary Index – создается автоматически на первичном ключе таблицы.
2. Unique Index – предотвращает дублирование значений в колонке.
3. Composite Index (составной индекс) – индекс на несколько столбцов.
4. Full-Text Index – используется для быстрого поиска по тексту.
5. Spatial Index – индекс для геоданных (только MyISAM).
6. Clustered Index – хранит строки в отсортированном порядке (InnoDB).
7. Non-Clustered Index – указывает на строки без изменения порядка хранения.
8. Hash Index – используется в MEMORY таблицах, обеспечивает быстрый доступ к данным.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Prometheus – это мощная система мониторинга с временными рядами (time series), которая собирает метрики из сервисов, хранит их и позволяет строить графики и отправлять алерты.
Pull-модель – сам запрашивает метрики у сервисов (в отличие от push-модели, как в StatsD).
Формат временных рядов – каждая метрика привязана ко времени и меткам (
labels
). Язык запросов PromQL – позволяет анализировать и агрегировать метрики.
Автодетектирование сервисов – поддержка Kubernetes, Docker, Consul.
Хранение данных в базе TSDB (Time Series Database).
Гибкая система алертов – интеграция с Alertmanager (уведомления в Slack, Telegram и др.).
Экспортеры/сервисы предоставляют метрики через HTTP-эндпоинт (
/metrics
). Prometheus сам запрашивает данные по расписанию.
Метрики хранятся в базе TSDB.
Можно строить графики в Grafana или запрашивать данные через API.
Алерты отправляются в Alertmanager при достижении пороговых значений.
Go-сервис может отдавать метрики через HTTP с помощью prometheus/client_golang
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Создаём метрику
var httpRequests = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
})
func main() {
// Регистрируем метрику
prometheus.MustRegister(httpRequests)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
httpRequests.Inc() // Увеличиваем счётчик при каждом запросе
w.Write([]byte("Hello, Prometheus!"))
})
// Эндпоинт для сбора метрик
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
Пример запроса в PromQL
http_requests_total
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Если у метода ресивер с *, это означает, что метод работает с указателем на объект, а значит:
- изменения внутри метода повлияют на оригинальный объект;
- метод может модифицировать поля структуры;
- вызов возможен как на указателе, так и на значении (Go сам "разыменует").
Такой метод можно вызывать и на value, и на pointer — Go сделает автоматическую конвертацию.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM