Tutorial

Знакомство с картами в Go

Published on January 24, 2020
Русский
Знакомство с картами в Go

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

В Go тип данных map используется как тип dictionary в большинстве других языков программирования. Он сопоставляет ключи со значениями, создавая пары ключ-значение, представляющие собой полезный способ хранения данных в Go. Построение карты осуществляется с помощью ключевого слова map с последующим указанием типа данных ключа в квадратных скобках [ ] и типа данных значения. Пары ключ-значение заключаются в фигурные скобки { }:

map[key]value{}

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

map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

В дополнение к фигурным скобкам в карте используются двоеточия, соединяющие пары ключ-значение. Слова слева от двоеточий являются ключами. Ключи могут иметь любой сравниваемый тип в Go, в том числе строки, целые числа и т. д.

Ключи в примере карты:

  • "name"
  • "animal"
  • "color"
  • "location"

Слова справа от двоеточий являются значениями. Значения могут относиться к любому типу данных. Значения в примере карты:

  • "Sammy"
  • "shark"
  • "blue"
  • "ocean"

Как и другие типы данных, карты могут храниться в переменных и выводиться:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)

Результат будет выглядеть так:

Output
map[animal:shark color:blue location:ocean name:Sammy]

Порядок пар ключ-значение можно изменять. Тип данных карт Go является неупорядоченным. Вне зависимости от порядка, пары ключ-значение останутся без изменений, что даст возможность доступа к данным по их относительному значению.

Доступ к элементам карты

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

Если вы хотите изолировать имя пользователя Sammy, вы можете вызвать для этого переменную sammy["name"], которая содержит карту и связанный ключ. Распечатаем результат:

fmt.Println(sammy["name"])

Получим значение в качестве результата:

Output
Sammy

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

Вызывая ключ "name", вы получаете значение этого ключа, то есть "Sammy".

Также вы можете вызывать остальные значения карты sammy в том же формате:

fmt.Println(sammy["animal"])
// returns shark

fmt.Println(sammy["color"])
// returns blue

fmt.Println(sammy["location"])
// returns ocean

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

Ключи и значения

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

for key, value := range sammy {
	fmt.Printf("%q is the key for the value %q\n", key, value)
}

При ранжировании карты в Go выводится два значения. Первое значение будет ключом, а второе — значением. Go создаст эти переменные с правильным типом данных. В данном случае ключ карты представлял собой строку, поэтому ключ также будет строкой. Значение также является строкой:

Output
"animal" is the key for the value "shark" "color" is the key for the value "blue" "location" is the key for the value "ocean" "name" is the key for the value "Sammy"

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

keys := []string{}

for key := range sammy {
	keys = append(keys, key)
}
fmt.Printf("%q", keys)

Вначале программа декларирует срез для хранения ваших ключей.

В результатах будут показаны только ключи вашей карты:

Output
["color" "location" "name" "animal"]

Ключи не сортируются. Если вы хотите сортировать их, используйте функцию sort.Strings из пакета sort:

sort.Strings(keys)

С помощью этой функции вы получите следующие результаты:

Output
["animal" "color" "location" "name"]

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

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

items := make([]string, len(sammy))

var i int

for _, v := range sammy {
	items[i] = v
	i++
}
fmt.Printf("%q", items)

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

Output
["ocean" "Sammy" "shark" "blue"]

Чтобы определить количество элементов на карте, вы можете использовать встроенную функцию len:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(len(sammy))

В результатах будет показано количество элементов на вашей карте:

Output
4

Хотя в Go отсутствуют удобные функции получения ключей и значений, при необходимости их можно получить с помощью нескольких строчек кода.

Проверка существования

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

Поищем в карте заведомо несуществующее значение и посмотрим на результат:

counts := map[string]int{}
fmt.Println(counts["sammy"])

Вывод должен выглядеть так:

Output
0

Хотя ключ sammy отсутствует в карте, Go возвращает значение 0. Это связано с тем, что используется тип данных значения int, и поскольку в Go задано нулевое значение всех переменных, возвращается нулевое значение 0.

Во многих случаях такое поведение нежелательно и может вызвать ошибку в программе. При поиске значения на карте Go может вывести второе опциональное значение. Это второе значение является булевым значением true в случае обнаружения ключа или false в случае отсутствия ключа. В Go это называется идиомой ok. Хотя вы можете присвоить любое имя переменной, получающей второй аргумент, в Go всегда следует использовать имя ok:

count, ok := counts["sammy"]

Если ключ sammy существует в карте counts, ok будет иметь значение true. В противном случае ok будет иметь значение false.

Вы можете использовать переменную ok для определения действий в программе:

if ok {
	fmt.Printf("Sammy has a count of %d\n", count)
} else {
	fmt.Println("Sammy was not found")
}

Результат будет выглядеть следующим образом:

Output
Sammy was not found

В Go вы можете комбинировать декларирование переменных и условную проверку с помощью блока if/else. Это позволяет использовать для такой проверки одно выражение:

if count, ok := counts["sammy"]; ok {
	fmt.Printf("Sammy has a count of %d\n", count)
} else {
	fmt.Println("Sammy was not found")
}

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

Изменение карт

Карты — это мутируемая структура данных, и вы можете изменять их. В этом разделе мы рассмотрим добавление и удаление элементов карт.

Добавление и изменение элементов карт

Вы можете добавлять на карты пары ключ-значение, не используя методы или функции. Для этого используется имя переменной карт, за которым идет значение ключа в квадратных скобках [ ] и оператор равенства = для определения нового значения:

map[key] = value

Покажем это на примере добавления пары ключ-значение в карту с именем usernames:

usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}

usernames["Drew"] = "squidly"
fmt.Println(usernames)

В результате на карте будет выведена новая пара ключ-значение Drew:squidly:

Output
map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]

Поскольку карты выводятся в неупорядоченном виде, эта пара может появиться в карте где угодно. Если вы используете карту usernames в следующих частях вашего файла программы, она будет содержать дополнительную пару ключ-значение.

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

Рассмотрим карту с именем followers, которая отслеживает подписчиков пользователей определенной сети. У пользователя "drew" сегодня выросло число подписчиков, и поэтому нужно обновить целочисленное значение, которое передается для ключа "drew". Мы используем функцию Println() для проверки изменения карты:

followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
followers["drew"] = 342
fmt.Println(followers)

В результатах будет показано обновленное значение drew:

Output
map[cindy:918 drew:342 mary:428]

Мы видим, что количество подписчиков увеличилось с целого числа 305 до 342.

Вы можете использовать этот метод для добавления пар ключ-значение в карты с вводимыми пользователем данными. Напишем небольшую программу usernames.go, которая выполняется в командной строке и позволяет пользователю вводить дополнительные имена и связанные с ними имена пользователей:

usernames.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}

	for {
		fmt.Println("Enter a name:")

		var name string
		_, err := fmt.Scanln(&name)

		if err != nil {
			panic(err)
		}

		name = strings.TrimSpace(name)

		if u, ok := usernames[name]; ok {
			fmt.Printf("%q is the username of %q\n", u, name)
			continue
		}

		fmt.Printf("I don't have %v's username, what is it?\n", name)

		var username string
		_, err = fmt.Scanln(&username)

		if err != nil {
			panic(err)
		}

		username = strings.TrimSpace(username)

		usernames[name] = username

		fmt.Println("Data updated.")
	}
}

Вначале мы определим в файле usernames.go первоначальную карту. Затем мы зададим цикл итерации имен. Мы предлагаем пользователю ввести имя и декларируем переменную для его сохранения. Затем мы проверяем наличие ошибок, и если они есть, в программе возникает паника, и она закрывается. Поскольку Scanln получает все вводимые данные, включая символ возврата каретки, нам нужно удалить из вводимых данных все пробелы. Для этого мы используем функцию strings.TrimSpace.

Блок if проверяет наличие имени на карте и выводит обратную связь. Если имя присутствует на карте, программа возвращается в начало цикла. Если имени нет на карте, пользователю направляется обратная связь и предлагается ввести новое имя пользователя для данного имени. Затем программа снова проверяет наличие ошибок. При отсутствии ошибок программа удаляет символ возврата каретки, назначает значение имени пользователя ключу name и выводит сообщение об обновлении данных.

Запустим эту программу в командной строке:

  1. go run usernames.go

Вывод должен выглядеть так:

Output
Enter a name: Sammy "sammy-shark" is the username of "Sammy" Enter a name: Jesse I don't have Jesse's username, what is it? JOctopus Data updated. Enter a name:

После завершения тестирования нажмите CTRL + C для выхода из программы.

Это показывает возможность интерактивного изменения карт. В данной конкретной программе при выходе с помощью клавиш CTRL + C вы потеряете все данные, если не реализуете способ чтения и записи файлов.

Таким образом, мы можем добавлять элементы на карты или изменять их значения с помощью синтаксиса map[key] = value.

Удаление элементов карт

Мы можем не только добавлять пары ключ-значение и изменять значения типа данных карты, но и удалять элементы карт.

Чтобы удалить пару ключ-значение с карты, можно использовать встроенную функцию delete(). Первый аргумент — это карта, откуда мы удаляем элемент. Второй аргумент — это удаляемый ключ:

delete(map, key)

Определим карту разрешений:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}

Нам больше не нужно разрешение modify, поэтому мы удалим его с нашей карты. Затем мы выведем карту, чтобы убедиться в его удалении:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
delete(permissions, 16)
fmt.Println(permissions)

Результат подтвердит удаление:

Output
map[1:read 2:write 4:delete 8:create]

Строка delete(permissions, 16) удаляет пару ключ-значение 16:"modify" из карты permissions.

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

Удалим все элементы из карты permissions:

permissions = map[int]string{}
fmt.Println(permissions)

Результаты показывают, что мы получили пустую карту без пар ключ-значение:

Output
map[]

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

Заключение

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

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.