Maps: criando, acessando e deletando chaves
1. Introdução aos Maps em Go
1.1. O que é um map?
Em Go, um map é uma coleção não ordenada de pares chave-valor, implementada internamente como uma tabela hash. É um tipo referência, o que significa que ao atribuir um map a outra variável ou passá-lo para uma função, ambas compartilham a mesma estrutura de dados subjacente.
package main
import "fmt"
func main() {
// Declaração e inicialização
capitais := map[string]string{
"Brasil": "Brasília",
"França": "Paris",
}
// Map é tipo referência
outroMap := capitais
outroMap["Brasil"] = "São Paulo"
fmt.Println(capitais["Brasil"]) // "São Paulo" — mesmo map!
}
1.2. Comparação com slices e structs
| Característica | Map | Slice | Struct |
|---|---|---|---|
| Acesso por chave | Sim (qualquer tipo comparável) | Não (apenas índice numérico) | Sim (apenas campo nomeado) |
| Dinâmico | Sim | Sim | Não (tamanho fixo) |
| Ordem | Não garantida | Garantida | N/A |
| Uso típico | Dicionários, cache, contagens | Listas, coleções ordenadas | Registros com campos fixos |
Use map quando precisar de associações rápidas entre chaves e valores. Use slice para sequências ordenadas. Use struct para agrupar campos relacionados de tipos diferentes.
1.3. Tipos de chave permitidos
Apenas tipos comparáveis (que suportam == e !=) podem ser usados como chave:
// Válidos
map[string]int // string
map[int]string // int
map[bool]float64 // bool
map[rune]string // rune (alias para int32)
// Inválidos (não compilam)
map[[]byte]string // slice não é comparável
map[map[int]int]int // map não é comparável
map[interface{}]int // interface{} só se o valor dinâmico for comparável
2. Criando Maps
2.1. Declaração com var e make
// Map nil — não pode receber atribuições
var m1 map[string]int
fmt.Println(m1 == nil) // true
// m1["chave"] = 1 // PANIC: assignment to entry in nil map
// Map vazio com make — pronto para uso
m2 := make(map[string]int)
m2["chave"] = 1 // OK
2.2. Uso de literal de map
// Literal completo
notas := map[string]float64{
"Alice": 9.5,
"Bob": 8.0,
"Carol": 7.5,
}
// Literal vazio (equivalente a make)
vazio := map[string]int{}
vazio["um"] = 1 // OK
2.3. Map com capacidade inicial
Quando você sabe quantos elementos serão inseridos, especificar capacidade inicial melhora performance:
// Capacidade inicial de 100 — evita realocações
populacao := make(map[string]int, 100)
for i := 0; i < 100; i++ {
populacao[fmt.Sprintf("cidade_%d", i)] = i * 1000
}
3. Acessando Valores e Verificando Existência
3.1. Acesso direto
contagem := map[string]int{"a": 1, "b": 2}
valor := contagem["a"] // 1
zero := contagem["x"] // 0 (zero value para int)
3.2. Acesso com dois valores (idiomático)
contagem := map[string]int{"a": 1}
if valor, ok := contagem["a"]; ok {
fmt.Printf("Chave 'a' existe com valor %d\n", valor) // 1
}
if _, ok := contagem["x"]; !ok {
fmt.Println("Chave 'x' não existe")
}
3.3. Diferença entre chave ausente e chave com zero value
tipo := map[string]string{"vazio": ""}
v1, ok1 := tipo["vazio"] // v1 = "", ok1 = true (chave existe)
v2, ok2 := tipo["outro"] // v2 = "", ok2 = false (chave não existe)
// Para distinguir, SEMPRE use a forma com ok
4. Inserindo e Atualizando Chaves
4.1. Sintaxe de atribuição
estoque := map[string]int{}
// Inserção
estoque["maçã"] = 10
estoque["banana"] = 5
// Atualização
estoque["maçã"] = 8 // agora maçã vale 8
4.2. Atualização condicional
estoque := map[string]int{"maçã": 10}
// Só atualiza se a chave existir
if _, ok := estoque["maçã"]; ok {
estoque["maçã"] += 5
}
4.3. Cuidado com maps nil
var m map[string]int // nil
// Leitura é segura
fmt.Println(m["chave"]) // 0
// Escrita causa PANIC
// m["chave"] = 1 // panic: assignment to entry in nil map
// Sempre inicialize com make ou literal
m = make(map[string]int)
m["chave"] = 1 // OK
5. Deletando Chaves
5.1. Função delete()
alunos := map[int]string{1: "Alice", 2: "Bob", 3: "Carol"}
delete(alunos, 2) // Remove Bob
delete(alunos, 99) // Não faz nada (seguro)
fmt.Println(alunos) // map[1:Alice 3:Carol]
5.2. Limpando um map inteiro
dados := map[string]int{"a": 1, "b": 2, "c": 3}
// Método 1: Reassign (mais eficiente)
dados = make(map[string]int)
// Método 2: Loop com delete (preserva capacidade)
for k := range dados {
delete(dados, k)
}
5.3. Efeito colateral: delete não libera memória imediatamente
grande := make(map[int]int, 1000000)
// ... preenche com 1 milhão de elementos
for k := range grande {
delete(grande, k) // map fica vazio, mas memória não é liberada
}
// O GC libera a memória apenas quando o map não for mais referenciado
6. Iterando sobre Maps
6.1. Loop for range
frutas := map[string]int{"uva": 3, "laranja": 5, "limão": 2}
for chave, valor := range frutas {
fmt.Printf("%s: %d\n", chave, valor)
}
6.2. Ordem não determinística
// A cada execução, a ordem pode ser diferente
for i := 0; i < 3; i++ {
fmt.Println("Execução", i)
for k, v := range map[string]int{"a": 1, "b": 2, "c": 3} {
fmt.Printf(" %s=%d", k, v)
}
fmt.Println()
}
6.3. Iterando apenas chaves ou apenas valores
m := map[string]int{"x": 10, "y": 20}
// Apenas chaves
for chave := range m {
fmt.Println(chave)
}
// Apenas valores
for _, valor := range m {
fmt.Println(valor)
}
7. Maps como Tipos de Dados Aninhados
7.1. Map de maps
// Criação
turmas := map[string]map[string]int{
"1A": {"Alice": 85, "Bob": 90},
"1B": {"Carol": 78},
}
// Acesso seguro
if notas, ok := turmas["1A"]; ok {
if _, ok2 := notas["Alice"]; ok2 {
fmt.Println("Alice está na turma 1A")
}
}
7.2. Map de slices
// Inicialização cuidadosa
times := map[string][]string{
"Futebol": {"Flamengo", "Palmeiras"},
}
// Adicionando a slice existente
times["Futebol"] = append(times["Futebol"], "São Paulo")
// Inicializando slice vazia para nova chave
times["Basquete"] = []string{}
times["Basquete"] = append(times["Basquete"], "Flamengo")
7.3. Verificando nil em maps aninhados
dados := map[string]map[string]int{}
// Verificação obrigatória antes de acessar
if interno, ok := dados["chave"]; ok {
if valor, ok2 := interno["subchave"]; ok2 {
fmt.Println(valor)
}
}
// Ou com inicialização automática
if dados["chave"] == nil {
dados["chave"] = make(map[string]int)
}
dados["chave"]["subchave"] = 42
8. Boas Práticas e Armadilhas Comuns
8.1. Concorrência: maps não são thread-safe
import "sync"
type SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Get(key string) int {
s.mu.RLock()
defer s.mu.RUnlock()
return s.m[key]
}
func (s *SafeMap) Set(key string, value int) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key] = value
}
8.2. Comparação de maps
import "reflect"
a := map[string]int{"a": 1}
b := map[string]int{"a": 1}
// Não funciona: fmt.Println(a == b) // compile error
// Método 1: reflect.DeepEqual
fmt.Println(reflect.DeepEqual(a, b)) // true
// Método 2: Loop manual
func equalMaps(m1, m2 map[string]int) bool {
if len(m1) != len(m2) {
return false
}
for k, v := range m1 {
if v2, ok := m2[k]; !ok || v != v2 {
return false
}
}
return true
}
8.3. Performance: escolha de chave e crescimento dinâmico
// Chaves pequenas (int, string curta) são mais rápidas
// Evite structs grandes como chave
// Capacidade inicial evita realocações
m := make(map[int]int, 1000) // boa prática para maps grandes
// Para maps muito grandes, considere:
// - sync.Map para concorrência
// - Bibliotecas especializadas como go-immutable-radix
Referências
- Go Blog: Go maps in action — Artigo oficial do time Go explicando funcionamento interno, criação, iteração e boas práticas com maps.
- Effective Go: Maps — Seção do Effective Go com exemplos idiomáticos de uso de maps.
- Documentação oficial do pacote builtin: delete — Referência direta da função
delete()e seu comportamento. - Go by Example: Maps — Tutoriais práticos com exemplos de criação, acesso, iteração e deleção em maps.
- The Go Programming Language Specification: Map types — Especificação formal da linguagem sobre tipos map, restrições de chave e operações.