Карты и Go

Карты в Go — это встроенный тип данных, предоставляющий возможность хранения пар ключ-значение. Они похожи на словари или ассоциативные массивы в других языках программирования.

Особенности карт в Go:

  • Динамический размер: Карты могут расти и уменьшаться по мере добавления и удаления элементов, что делает их гибкими для использования в ситуациях, когда количество элементов заранее неизвестно.

  • Типизированные ключи и значения: В картах Go и ключи, и значения могут быть почти любого типа, за исключением тех, которые содержат срезы, карты или другие несравнимые типы. Тип ключа и тип значения для карты указываются при её объявлении.

  • Быстрый доступ: Карты предоставляют быстрый доступ к значениям по ключам. Время доступа к элементу карты не зависит от размера карты, что делает их эффективным выбором для поиска данных.

Работа с картами:

package main

import (

"fmt"

)

func main() {

// Объявление карты

var myMap map[int]string

fmt.Println(myMap) // map[]

// Инициализация карты

myMap = make(map[int]string)

fmt.Println(myMap) // map[]

// Или можно объявить и инициализировать карту одновременно

myMap = make(map[int]string)

fmt.Println(myMap) // map[]

/*

Или можно объявить и инициализировать карту одновременно с емкостью


В отличие от слайсов, у карт нет понятия внешней емкости, доступной для проверки с помощью функции cap().

Это означает, что вызов cap(myMap) будет ошибочным, так как функция cap() не применима к картам.


Начальная емкость карты в Go служит лишь подсказкой для реализации о том, сколько элементов ожидается в карте.

Это может помочь оптимизировать ее внутреннюю структуру и уменьшить количество аллокаций памяти в

начальной фазе использования карты.

*/

myMap = make(map[int]string, 5)

fmt.Println(myMap, len(myMap)) // map[] 0


// Или можно объявить и инициализировать карту одновременно значениями

myMap = map[int]string{1: "a", 2: "b", 3: "c"}

fmt.Println(myMap) // map[1:a 2:b 3:c]

key := 5

// Добавить или изменить значение для ключа "apple"

myMap[key] = "apple"

fmt.Println(myMap) // map[1:a 2:b 3:c 5:apple]

// Получить значение. Если ключ существует, `ok` будет true, иначе false.

if _, ok := myMap[key]; !ok {

panic(fmt.Sprintf("myMap: key %d not found", key)) // panic: myMap: key 6 not found, если ключа 6 не окажется в карте

}

// Удалить элемент с ключом "apple"

// Ну и ни чего не происходит если такого ключа нет

delete(myMap, 6)

fmt.Println(myMap) // map[1:a 2:b 3:c 5:apple]

// Итерация по карте

for k, v := range myMap {

fmt.Println(k, v)

}

}

Подводные камни

1. Изменение карты во время итерации

Модификация карты во время итерации по ней может привести к непредсказуемому поведению. Например, вы можете пропустить некоторые элементы или столкнуться с паникой в рантайме.

Это допустимо в Go и не приведет к непосредственной ошибке или панике.

В случае с Go, поведение при удалении элементов из карты во время итерации по ней довольно определенное и безопасное. Итератор карты в Go спроектирован так, чтобы обрабатывать изменения карты во время итерации, но это может привести к тому, что некоторые элементы будут пропущены в процессе итерации, если вы удаляете элементы впереди текущего элемента итерации.

package main

func main() {

m := map[int]string{1: "a", 2: "b", 3: "c"}

for k := range m {

if k == 2 {

delete(m, 3) // Попытка удалить ключ во время итерации

}

}

// Поведение не определено. Возможно пропуск элементов или другие непредвиденные результаты.

}

Решение.

Для безопасного удаления элементов из карты во время итераций: сначала соберите ключи элементов, которые вы хотите удалить, в отдельный слайс, а затем удалите их в отдельном цикле после завершения итерации по карте. Это гарантирует, что итерация по карте не будет нарушена изменениями карты.

package main

import "fmt"

func main() {

m := map[int]string{1: "a", 2: "b", 3: "c"}

var keysToDelete []int

for k := range m {

if k == 2 {


// добавляем ключи для удаления

keysToDelete = append(keysToDelete, 3)

}

}

// Удаляем элементы после итерации

for _, k := range keysToDelete {

delete(m, k)

}

fmt.Println(m) // Вывод: map[1:a 2:b], элемент с ключом 3 удален

}

2. Неинициализированная карта

Попытка использования неинициализированной карты вызовет панику во время выполнения.

package main

func main() {

// Попытка использования неинициализированной карты вызовет панику во время выполнения.

var m map[int]string

m[1] = "apple" // panic: assignment to entry in nil map

// Чтобы избежать этого, карта должна быть инициализирована перед использованием

m = make(map[int]string)


m[1] = "apple" // OK

}

3. Проверка наличия ключа

При получении значения из карты, если ключ не существует, возвращаемое значение будет нулевым для типа значения карты. Это может ввести в заблуждение, если не проверять, действительно ли ключ существует.

package main

import (

"fmt"

)

func main() {

m := make(map[int]int)

k := 123

val := m[1] // val == 0, ключ 123 не существует, но 0 также может быть допустимым значением

fmt.Println(val)

// Лучше использовать двухзначную форму получения элемента:

if _, ok := m[k]; !ok {

fmt.Printf("map key: %d, not found\n", k)

}

}

4. Сортировка

В Go карты не имеют внутреннего порядка, и их элементы хранятся в случайном порядке. Однако вы можете отсортировать ключи (или значения) карты, используя дополнительные шаги. Вот как можно отсортировать карту по ключам:

package main

import (

"fmt"

"sort"

)

func main() {

m := map[string]int{

"banana": 3,

"apple":  5,

"pear":  2,

"orange": 4,

}

// Создаем слайс для хранения ключей

var keys []string

// Добавляем ключи в слайс

for k := range m {

keys = append(keys, k)

}

// Сортируем ключи

sort.Strings(keys)


// Выводим отсортированную карту

for _, k := range keys {

fmt.Println(k, m[k])

}

}

5. Конкурентная модификация

Карты в Go не являются потокобезопасными, и одновременная модификация карты из разных горутин может привести к состоянию гонки и панике.

package main

import "time"

func main() {

m := make(map[int]int)

// Имитация конкурентной модификации

go func() {

for i := 0; i < 10000; i++ {

m[i] = i

}

}()

go func() {

for i := 0; i < 10000; i++ {

delete(m, i)

}

}()

go func() {

for i := 0; i < 10000; i++ {

delete(m, i)

}

}()

// Возможна паника или другое непредсказуемое поведение

time.Sleep(time.Second * 100)

}

Решение.

Для предотвращения конкурентной модификации карты и обеспечения потокобезопасности, можно использовать мьютекс из пакета sync. Мьютекс позволяет блокировать доступ к ресурсу (в данном случае к карте) для всех горутин, кроме одной, которая в данный момент владеет мьютексом. Это гарантирует, что только одна горутина может изменять карту в любой момент времени.

Пример решения с использованием мьютекса:

package main

import (

"sync"

"time"

)

func main() {

var m = make(map[int]int) // Создаем карту

var mutex sync.Mutex // Создаем мьютекс

// Горутина для добавления элементов в карту

go func() {

for i := 0; i < 10000; i++ {

mutex.Lock() // Блокируем мьютекс перед модификацией карты

m[i] = i

mutex.Unlock() // Разблокируем мьютекс после модификации карты

}

}()

// Горутина для удаления элементов из карты

go func() {

for i := 0; i < 10000; i++ {

mutex.Lock() // Блокируем мьютекс перед модификацией карты

delete(m, i)

mutex.Unlock() // Разблокируем мьютекс после модификации карты

}

}()

// Даем горутинам время на выполнение

time.Sleep(time.Second)

}

В данном примере перед каждым изменением карты мьютекс блокируется вызовом mutex.Lock(), и разблокируется вызовом mutex.Unlock() после изменения. Это гарантирует, что в любой момент времени карта может быть изменена только одной горутиной, предотвращая конкурентную модификацию и потенциальные ошибки в программе.

Использование каналов: Каналы в Go могут использоваться не только для обмена данными между горутинами, но и как средство синхронизации. Вы можете создать канал, через который будут передаваться операции чтения и записи карты, а одна горутина будет отвечать за выполнение этих операций. Это гарантирует, что все операции с картой выполняются последовательно.

package main

import (

"sync"

)

type command struct {

key  int

value int

op  string // "set", "delete", "get"

}

func main() {

var opChan = make(chan command)

var wg sync.WaitGroup

// Горутина для управления картой

go func() {

m := make(map[int]int)

for op := range opChan {

switch op.op {

case "set":

m[op.key] = op.value

case "delete":

delete(m, op.key)

}

}

}()

// Горутина для добавления элементов

wg.Add(1)

go func() {

defer wg.Done()

for i := 0; i < 100; i++ {

opChan <- command{key: i, value: i, op: "set"}

}

}()

// Горутина для удаления элементов

wg.Add(1)

go func() {

defer wg.Done()

for i := 0; i < 100; i++ {

opChan <- command{key: i, op: "delete"}

}

}()

wg.Wait()

close(opChan)

}

sync.Map: В пакете sync есть специальная структура Map, предназначенная для использования в многопоточных средах без явного использования мьютексов для синхронизации. sync.Map имеет встроенные методы для безопасной работы с данными в конкурентных ситуациях.

package main

import (

"fmt"

"sync"

)

func main() {

var m sync.Map

// Установка значения

m.Store(1, "a")

// Получение значения

if val, ok := m.Load(1); ok {

fmt.Println("Value:", val)

}

// Удаление значения

m.Delete(1)

}

Оба подхода имеют свои преимущества и недостатки. Использование каналов может быть более наглядным и понятным, но потребует больше кода для управления операциями. sync.Map более прост в использовании для простых случаев, но может быть менее гибким, чем полное управление синхронизацией через мьютексы или каналы.

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества