Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2
В Go строки представляют собой неизменяемые последовательности байтов, и операция сложения строк (
+
) приводит к созданию новой строки, которая является конкатенацией двух или более исходных строк. Когда вы складываете строки, Go создает новую строку, содержащую все символы исходных строк, соединенных друг за другом. Исходные строки при этом остаются неизменными.
s1 := "Hello"
s2 := "World"
result := s1 + " " + s2
fmt.Println(result) // Output: Hello World
Так как строки в Go неизменяемы, результирующая строка создается заново, а память для нее выделяется в куче или на стеке, в зависимости от размера и контекста.
Сложение строк через оператор
+
может быть неэффективным при множественных операциях, так как каждый раз создается новая строка, а старые промежуточные результаты удаляются сборщиком мусора.result := ""
for i := 0; i < 5; i++ {
result += "Go"
}
fmt.Println(result) // Output: GoGoGoGoGo
Если требуется объединить много строк, рекомендуется использовать
strings.Builder
. Это более эффективный способ, так как Builder
работает с буфером и избегает лишних аллокаций.import "strings"
func main() {
var builder strings.Builder
for i := 0; i < 5; i++ {
builder.WriteString("Go")
}
fmt.Println(builder.String()) // Output: GoGoGoGoGo
}
Go строки поддерживают Unicode, поэтому сложение строк корректно работает с многобайтовыми символами.
s1 := "Привет"
s2 := "Мир"
result := s1 + " " + s2
fmt.Println(result) // Output: Привет Мир
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
Это структура данных, которая используется для хранения и поиска пар "ключ-значение". Обеспечивают быстрый доступ к данным по ключу, обычно с константным временем доступа в среднем случае. Основой работы хэш-таблицы является хеш-функция, которая преобразует ключ в индекс, по которому хранится значение.
Функция, которая принимает ключ и преобразует его в индекс массива, называемого "хэш-таблицей". Хорошая хеш-функция распределяет ключи равномерно по хэш-таблице, минимизируя количество коллизий.
Массив фиксированного размера, где каждый элемент называется "корзиной" (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
👍7
1. Добавление одного элемента: slice = append(slice, element).
2. Добавление нескольких элементов: slice = append(slice, elements...).
3. Слияние с другим слайсом: slice = append(slice, otherSlice...).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
В Go для проверки работы горутины часто используют каналы. Они позволяют передавать данные между горутинами и могут использоваться для сигнализации о состоянии выполнения горутины.
Горутина может отправлять сигнал (например,
true
) в канал, чтобы уведомить о своем завершении.func worker(done chan bool) {
fmt.Println("Работа началась...")
time.Sleep(2 * time.Second) // Имитация работы
fmt.Println("Работа завершена!")
done <- true // Отправляем сигнал в канал
}
func main() {
done := make(chan bool)
go worker(done) // Запускаем горутину
// Ожидаем сигнал завершения
<-done
fmt.Println("Основной поток: горутина завершена")
}
Горутина может отправлять данные в канал, чтобы сигнализировать о прогрессе выполнения.
func worker(progress chan int) {
for i := 1; i <= 5; i++ {
fmt.Printf("Шаг %d выполнен\n", i)
progress <- i // Отправляем номер шага в канал
time.Sleep(500 * time.Millisecond) // Имитация работы
}
close(progress) // Закрываем канал после завершения работы
}
func main() {
progress := make(chan int)
go worker(progress) // Запускаем горутину
// Считываем данные из канала
for step := range progress {
fmt.Printf("Получен сигнал: шаг %d завершен\n", step)
}
fmt.Println("Все шаги выполнены!")
}
Если важно знать, работает ли горутина, но при этом нужно ограничить ожидание, используется оператор
select
с тайм-аутом.func worker(status chan string) {
time.Sleep(2 * time.Second) // Имитация работы
status <- "Горутина завершена"
}
func main() {
status := make(chan string)
go worker(status)
select {
case msg := <-status:
fmt.Println(msg)
case <-time.After(1 * time.Second): // Тайм-аут 1 секунда
fmt.Println("Горутина работает слишком долго")
}
}
Закрытие канала может служить сигналом того, что горутина завершила свою работу.
func worker(done chan struct{}) {
fmt.Println("Горутина работает...")
time.Sleep(2 * time.Second)
fmt.Println("Горутина завершена!")
close(done) // Закрываем канал
}
func main() {
done := make(chan struct{})
go worker(done)
// Проверяем, когда канал закроется
<-done
fmt.Println("Основной поток: горутина завершила работу")
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Оператор "квадратные скобки" (
[]
) при применении к строке используется для доступа к отдельным байтам в этой строке. Строки представлены как последовательности байтов, и оператор []
позволяет получить байт по указанному индексу. package main
import (
"fmt"
)
func main() {
str := "hello"
// Получаем байт по индексу
firstByte := str[0]
// Выводим байт и его символ
fmt.Printf("Первый байт: %d\n", firstByte) // Выводит: 104
fmt.Printf("Первый символ: %c\n", firstByte) // Выводит: h
}
В этой строке кода мы получаем байт, расположенный по индексу
0
в строке str
. В данном случае это байт, соответствующий символу 'h'.firstByte := str[0]
Здесь мы выводим байт в виде целого числа. Поскольку символ 'h' имеет ASCII-код 104, вывод будет
104
.fmt.Printf("Первый байт: %d\n", firstByte)
Мы также можем вывести байт как символ, используя формат
%c
. Это отобразит символ 'h'.fmt.Printf("Первый символ: %c\n", firstByte)
Важно понимать, что строки являются последовательностями байтов, а не символов. Это означает, что доступ по индексу с помощью
[]
дает байт, а не руну (rune). Если строка содержит многобайтовые символы (например, символы Unicode), то доступ по индексу может вернуть только один из байтов, составляющих символ.package main
import (
"fmt"
)
func main() {
str := "Привет"
// Получаем байт по индексу
firstByte := str[0]
// Выводим байт и его символ
fmt.Printf("Первый байт: %d\n", firstByte) // Выводит: 208
fmt.Printf("Первый символ: %c\n", firstByte) // Выводит: � (неполный символ)
}
Для корректной работы с многобайтовыми символами (рунами) в строках используется преобразование строки в срез рун
package main
import (
"fmt"
)
func main() {
str := "Привет"
// Преобразуем строку в срез рун
runes := []rune(str)
// Получаем руну по индексу
firstRune := runes[0]
// Выводим руну и её символ
fmt.Printf("Первая руна: %d\n", firstRune) // Выводит: 1055 (код Unicode для 'П')
fmt.Printf("Первый символ: %c\n", firstRune) // Выводит: П
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Массивы представляют собой фиксированную последовательность элементов одного типа. Являются основополагающей структурой данных, на базе которой строятся более сложные структуры, такие как слайсы. Рассмотрим, как устроены массивы, их особенности, а также сравнение с другими структурами данных.
Размер массива задается при его объявлении и не может изменяться во время выполнения программы.
Все элементы массива имеют один и тот же тип.
В отличие от слайсов, массивы хранят свои элементы в непрерывном блоке памяти.
С указанием типа элементов и фиксированного размера. Это объявление создает массив из пяти целых чисел, инициализированных нулями.
var arr [5]int
Массивы могут быть инициализированы при объявлении
arr := [5]int{1, 2, 3, 4, 5}
Можно также инициализировать массив частично, оставив остальные элементы равными нулям:
arr := [5]int{1, 2}
Осуществляется с использованием индексов, начиная с 0
fmt.Println(arr[0]) // 1
arr[1] = 10
fmt.Println(arr[1]) // 10
Фиксирована и задается при его объявлении. Ее можно получить с помощью функции
len
fmt.Println(len(arr)) // 5
При присваивании одного массива другому копируются все элементы:
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := arr1
arr2[0] = 10
fmt.Println(arr1) // [1 2 3 4 5]
fmt.Println(arr2) // [10 2 3 4 5]
При этом копируется весь массив:
func modifyArray(a [5]int) {
a[0] = 10
}
arr := [5]int{1, 2, 3, 4, 5}
modifyArray(arr)
fmt.Println(arr) // [1 2 3 4 5]
Массивы имеют фиксированный размер, тогда как слайсы динамичны.
Массивы могут быть более производительными для небольших коллекций данных из-за отсутствия накладных расходов на управление динамическими данными.
Использование массивов
package main
import (
"fmt"
)
func main() {
// Объявление и инициализация массива
arr := [5]int{1, 2, 3, 4, 5}
// Доступ к элементам
fmt.Println("First element:", arr[0]) // First element: 1
// Изменение элементов
arr[1] = 10
fmt.Println("Modified array:", arr) // Modified array: [1 10 3 4 5]
// Длина массива
fmt.Println("Length of array:", len(arr)) // Length of array: 5
// Копирование массива
arr2 := arr
arr2[0] = 20
fmt.Println("Original array:", arr) // Original array: [1 10 3 4 5]
fmt.Println("Copied array:", arr2) // Copied array: [20 10 3 4 5]
// Передача массива в функцию
modifyArray(arr)
fmt.Println("Array after modifyArray call:", arr) // Array after modifyArray call: [1 10 3 4 5]
}
func modifyArray(a [5]int) {
a[0] = 10
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Нельзя напрямую взять ссылку на значение, хранящееся по ключу в карте (map), из-за особенностей реализации карт и управления памятью. Рассмотрим подробнее, почему это так.
Карты реализованы на основе хеш-таблиц. Внутреннее устройство карты предполагает, что значения могут перемещаться в памяти при выполнении операций, таких как добавление или удаление элементов. Хеш-таблица может перераспределять (реорганизовывать) свои внутренние структуры для оптимизации доступа к элементам. Это может происходить, например, когда карта увеличивается в размере.
Если бы была возможность брать ссылки на значения, хранящиеся в карте, то при любой операции изменения карты (добавление, удаление элементов) ссылки могли бы становиться недействительными. Это привело бы к потенциально небезопасному поведению программы, так как указатель (ссылка) мог бы указывать на уже несуществующую или перемещенную область памяти.
Здесь попытка взять ссылку на значение из карты могла бы привести к проблемам
package main
import "fmt"
func main() {
m := map[string]int{"a": 1, "b": 2}
// Нельзя делать так:
// p := &m["a"]
// Вместо этого можно работать с копией значения
value := m["a"]
p := &value
fmt.Println("Value:", *p) // 1
// Изменение карты
m["c"] = 3
// Ссылка на значение в карте могла бы стать недействительной
// fmt.Println("Value after map change:", *p)
}
Для работы с ними лучше использовать копии значений. Вот несколько способов, как это можно сделать:
Получить значение из карты и сохранить его в переменную.
value := m["a"]
Если необходимо изменить значение в карте, его нужно сначала извлечь, изменить, а затем снова записать в карту.
value := m["a"]
value = value + 10
m["a"] = value
В некоторых случаях можно использовать указатели в качестве значений карты, чтобы можно было изменять значения через указатели.
package main
import "fmt"
func main() {
m := map[string]*int{"a": new(int), "b": new(int)}
*m["a"] = 1
*m["b"] = 2
// Теперь можно брать указатели на значения
p := m["a"]
fmt.Println("Value:", *p) // 1
// Изменение значения через указатель
*p = 42
fmt.Println("Updated Value:", *m["a"]) // 42
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Это протокол или технология, позволяющая одному процессу (или программе) вызывать функции, реализованные в другом процессе, часто расположенном на удалённом сервере. Это упрощает взаимодействие между системами, скрывая детали сетевого взаимодействия.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯3
Это язык программирования, разработанный Google для создания масштабируемых и эффективных систем. Основные цели языка включают простоту, высокую производительность и надежность.
Go — компилируемый язык, обеспечивающий высокую производительность.
Язык избегает избыточности, улучшая читаемость кода.
Автоматическое управление памятью.
Встроенные средства для работы с параллельными вычислениями, такие как горутины и каналы.
Широкий набор инструментов и библиотек.
Простого веб-сервера:
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", helloHandler)
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}
Легковесные потоки для параллельных вычислений.
go func() {
fmt.Println("Hello from a goroutine!")
}()
Средства обмена данными между горутинами.
ch := make(chan int)
go func() {
ch <- 42
}()
value := <-ch
fmt.Println(value)
Абстракция поведения без привязки к реализации.
type Stringer interface {
String() string
}
Организация кода для управления и повторного использования.
package mypackage
import "fmt"
func MyFunction() {
fmt.Println("Hello from mypackage")
}
Go используется для серверного ПО, облачных сервисов, микросервисов, инструментов командной строки и систем с высокой производительностью. Известные проекты на Go включают Kubernetes, Docker, Prometheus, а также Google, Dropbox и Netflix.
Быстро компилируемый язык.
Минимум синтаксического шума.
Простые механизмы параллельных вычислений.
Широкий набор встроенных инструментов.
Ранее отсутствовали, добавлены в Go 1.18.
Используется управление ошибками через возвращаемые значения.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
😁2
Микросервисная архитектура делит приложение на независимые сервисы, каждый из которых отвечает за свою задачу. Они взаимодействуют через API и могут разрабатываться, разворачиваться и масштабироваться независимо.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Минусы: сложность разработки, взаимодействия сервисов, управление данными и отладка.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥2
Это неизменяемые последовательности байтов, обычно представляющие текст в кодировке UTF-8. Работа с ними в Go основывается на использовании встроенных функций, методов стандартной библиотеки и специальных типов данных.
Самый простой способ объединить строки — использовать оператор
+
или функцию fmt.Sprintf
.package main
import "fmt"
func main() {
s1 := "Привет"
s2 := "Мир"
result := s1 + ", " + s2 + "!"
fmt.Println(result) // Привет, Мир!
// Через fmt.Sprintf
formatted := fmt.Sprintf("%s, %s!", s1, s2)
fmt.Println(formatted) // Привет, Мир!
}
Функция
len
возвращает длину строки в байтах, а не в символах.package main
import "fmt"
func main() {
str := "Привет"
fmt.Println(len(str)) // 12, так как кириллические символы занимают 2 байта
}
Строки можно обойти как в виде байтов, так и в виде рун (Unicode символов).
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Привет"
// Итерация по байтам
for i := 0; i < len(str); i++ {
fmt.Printf("Байт: %x\n", str[i])
}
// Итерация по символам (рунам)
for _, runeValue := range str {
fmt.Printf("Символ: %c\n", runeValue)
}
// Количество рун
fmt.Println("Количество символов:", utf8.RuneCountInString(str))
}
Так как строки неизменяемы, для извлечения подстроки используется срез (
slice
).package main
import "fmt"
func main() {
str := "Привет, Мир!"
substring := str[8:] // С 8-го байта до конца
fmt.Println(substring) // Мир!
}
Функции
strings.Split
и strings.Join
из пакета strings
позволяют разбивать строку на части или объединять части в строку.package main
import (
"fmt"
"strings"
)
func main() {
str := "apple,banana,cherry"
// Разделение строки
parts := strings.Split(str, ",")
fmt.Println(parts) // [apple banana cherry]
// Объединение строк
joined := strings.Join(parts, " | ")
fmt.Println(joined) // apple | banana | cherry
}
Для поиска подстроки можно использовать
strings.Contains
, strings.Index
или strings.HasPrefix/strings.HasSuffix
. Для замены — strings.Replace
.package main
import (
"fmt"
"strings"
)
func main() {
str := "Go — это круто!"
// Поиск подстроки
fmt.Println(strings.Contains(str, "круто")) // true
fmt.Println(strings.Index(str, "это")) // 4
// Замена подстроки
newStr := strings.Replace(str, "круто", "отлично", 1)
fmt.Println(newStr) // Go — это отлично!
}
Стандартная библиотека предоставляет функции для изменения регистра строки:
strings.ToLower
и strings.ToUpper
.package main
import (
"fmt"
"strings"
)
func main() {
str := "Hello, Go!"
fmt.Println(strings.ToUpper(str)) // HELLO, GO!
fmt.Println(strings.ToLower(str)) // hello, go!
}
Удаление пробелов или других символов с начала/конца строки выполняется с помощью
strings.Trim
, strings.TrimSpace
и других функций.package main
import (
"fmt"
"strings"
)
func main() {
str := " Hello, Go! "
fmt.Println(strings.TrimSpace(str)) // "Hello, Go!"
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Это фундаментальные концепции, используемые в операционных системах для управления выполнением программ. Хотя они тесно связаны, между ними есть ключевые различия:
Это экземпляр выполняющейся программы. Процесс имеет собственное изолированное адресное пространство памяти, что означает, что память, которую использует один процесс, не может быть напрямую доступна другому процессу. Каждый процесс предоставляет ресурсы, необходимые для выполнения программы, включая память, дескрипторы файлов, и переменные среды. Процессы изолированы друг от друга, что повышает безопасность и устойчивость системы в целом, поскольку сбой в одном процессе обычно не влияет на другие процессы. Однако эта изоляция требует больших затрат ресурсов при переключении между процессами и при их создании.
Это более легковесная единица выполнения, которая существует внутри процесса. Все потоки внутри одного процесса делят одно и то же адресное пространство памяти и системные ресурсы, такие как файловые дескрипторы. Это позволяет потокам более эффективно общаться друг с другом, поскольку они могут обмениваться данными без использования специальных механизмов межпроцессного взаимодействия. Потоки идеально подходят для выполнения задач, которые могут быть эффективно распараллелены, поскольку они позволяют программе выполнять множество операций одновременно. Однако, поскольку потоки делят память, разработчику необходимо тщательно управлять доступом к общим данным, чтобы избежать условий гонки и других проблем синхронизации.
Процессы изолированы друг от друга, в то время как потоки делят состояние и ресурсы внутри одного процесса.
Каждый процесс имеет собственное адресное пространство, в то время как все потоки внутри процесса делят его адресное пространство.
Создание нового процесса более ресурсоемко, чем создание потока внутри существующего процесса.
Взаимодействие между процессами требует использования межпроцессного взаимодействия (IPC), такого как сокеты, разделяемая память, очереди сообщений и т. д. Потоки внутри процесса могут общаться друг с другом напрямую через общую память.
Ошибка в одном процессе обычно не влияет на другие процессы, но ошибка в одном потоке может привести к сбою всего процесса.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3