Go включает собственный тип, реализующий хеш-таблицы, называемый картой. Это тип данных, состоящий из набора уникальных ключей и набора значений для каждого из этих ключей.
Его можно сравнить, например, со словарем на других языках, в котором хранятся пары ключ-значение. Доступ к этим значениям осуществляется с помощью ключей точно так же, как к массивам и срезам, как мы видели в предыдущем посте.
Индексы не ограничены числом, как в массивах или срезах, и элементы не упорядочены, поэтому, если мы напечатаем карту, она вернет случайный порядок, если мы не сделаем ничего, чтобы переопределить ее печать и установить желаемый порядок.
Чтобы объявить карту, это делается с помощью значения карты[key], где ключ будет того типа, который мы хотим, чтобы наш ключ был (он должен быть сопоставимого типа https://go.dev/ref/spec#Comparison_operators) ), а значение будет типом, который мы хотим, чтобы карта хранилась в каждом из ключей, независимо от типа, от int до структуры или другой карты, независимо от того, что мы хотим.
Как и срезы, карты являются ссылочными типами, а это означает, что нулевое значение карты будет равно нулю.
Это происходит потому, что под ним находится хеш-таблица, в которой хранятся ключи и значения, и они представляют собой просто их оболочку, абстракцию.
Если мы объявим это как:
var m map[int]int
его значение будет равно нулю.
Если мы хотим, чтобы оно имело нулевое значение, мы можем использовать объявление:
m := map[int]int{}
И мы даже можем инициализировать его, как и срезы, используя функцию make.
m := make(map[string]string)
При этом хеш-карта будет инициализирована с соответствующим пулом памяти, возвращая таким образом карту, указывающую на эту структуру данных.
Добавление значений на карту осуществляется с помощью фигурных скобок [] и фигурной скобки, как и в случае с массивами или срезами. В этом примере мы создадим карту, в которой ключи будут строками, а значения — целыми числами, для хранения имен и возраста.
ages := make(map[string]int) ages["John"] = 33 ages["Charly"] = 27 ages["Jenny"] = 45 ages["Lisa"] = 19
Если мы хотим добавить к нему значения при объявлении карты, мы можем использовать короткое объявление и сделать все это за один шаг:
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
Чтобы прочитать значения, нам просто нужно указать ключ к нашей карте, и она вернет это значение. Например, чтобы узнать возраст Лизы, мы можем сделать:
fmt.Println(ages["Lisa"]) // 19
Если мы попытаемся получить доступ к несуществующему ключу, полученное значение будет нулевым значением типа, в данном случае это будет "", поскольку это строка.
Чтобы проверить, существует ли элемент на карте, мы можем проверить, является ли тип типом по умолчанию, но это не очень надежно, поскольку, возможно, он существует, но его значение представляет собой пустую строку или 0 в случае int , что соответствует его нулевому значению, поэтому Go помогает нам в следующем:
val, ok := ages["Randy"]
Если мы приравняем карту к двум значениям, первое будет значением этого элемента, доступ к которому осуществляется через ключ, в данном случае «Randy», которого не существует, а второе будет логическим значением, которое будет указывать, является ли оно существует или нет.
Если нас не интересует значение и мы просто хотим проверить существование ключа, мы можем использовать _, чтобы игнорировать значение следующим образом:
_, ok := ages["Randy"]
Как и в случае с массивами и срезами, мы можем использовать функцию len, чтобы узнать, сколько элементов имеется на карте.
fmt.Println(len(ages)) // 4
Если мы хотим изменить значение, это так же просто, как получить доступ к этому значению с помощью ключа и сопоставить его с другим, и оно будет изменено.
Если мы объявим вторую карту, указывающую на первую, если мы изменим значение второй, поскольку это ссылочный тип, мы будем изменять значение первой, потому что обе используют одну и ту же хеш-таблицу.
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19} agesNew := ages agesNew["Bryan"] = 77 fmt.Println(agesNew) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19] fmt.Println(ages) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]
Чтобы удалить элементы с карты, Go предоставляет нам функцию удаления со следующей сигнатурой delete(m map[Type]Type1, key Type), которая получает карту и ключ для удаления.
В предыдущем случае, если бы мы хотели исключить «Лизу», мы бы сделали это:
delete(ages, "Lisa")
Если мы хотим просмотреть содержимое карты, мы можем сделать это, используя for с вариацией диапазона, который мы уже видели в посте о массивах и срезах.
Как и тогда, первый элемент будет индексом, следовательно, ключом, а второй — значением.
for key, value := range ages { fmt.Printf("%s: %d\n", key, value) } // Output: // Jenny: 45 // Lisa: 19 // John: 33 // Charly: 27
Как и в случае с массивами и срезами, если нас интересует только значение без ключа, мы можем опустить его, используя _.
for _, value := range ages { fmt.Println(value) } // Output: // 19 // 33 // 27 // 45
И если нас интересует просто ключ, мы можем присвоить диапазон одной переменной, чтобы получить его:
for key := range ages { fmt.Println(key) } // Output: // John // Charly // Jenny // Lisa
Как я упоминал во введении, информация на карте не упорядочена, поэтому при ее циклическом прохождении мы не можем указать, в каком порядке она следует, а также не может Go гарантировать, что порядок между выполнениями будет одинаковым.
Как мы видели на примере массивов и срезов, в стандартной библиотеке есть пакет сортировки, который помогает нам сортировать элементы: https://pkg.go.dev/sort
Следуя нашему примеру с возрастом и использованием сортировки, мы можем отсортировать ключи карты перед ее обходом и, таким образом, гарантировать, что доступ к ней будет осуществляться по порядку.
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19} keys := make([]string, 0, len(ages)) for k := range ages { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Println(k, ages[k]) } // Output: // Charly 27 // Jenny 45 // John 33 // Lisa 19
Мы объявляем нашу возрастную карту с помощью короткого объявления, как мы видели раньше.
Мы создаем фрагменты строки для хранения ключей и используем метод make с длиной 0, поскольку на данный момент у нас нет ключей, но мы резервируем емкость, которую они будут иметь, с помощью метода len для длины нашей карты.
Мы просматриваем карту возрастов, чтобы сохранить ее ключи, и добавляем их в созданный срез.
Мы сортируем ключи по алфавиту с помощью функции sort.Strings.
Мы просматриваем уже заказанный фрагмент ключей и получаем доступ к карте с соответствующим ключом.
Таким образом, мы получим упорядоченный доступ к карте и сможем реализовать логику, необходимую нашей программе.
При использовании карт следует помнить, что их одновременное использование небезопасно. Если это одновременное чтение, либо доступ к значению, либо через for с диапазоном, нет проблем с одновременным доступом к нему нескольких горутин.
Проблемный случай — это когда вы хотите одновременно обновить значение карты, добавляя или удаляя из нее элементы, и в то же время вы читаете ее, например, с другой стороны.
Для решения этой ситуации есть несколько возможных решений, которые я не буду вдаваться в подробности, просто упомяну и оставлю на ваше усмотрение копаться в них глубже.
Если мы используем пакет синхронизации: https://pkg.go.dev/sync из стандартной библиотеки, мы можем контролировать синхронизацию между различными горутинами.
Возможное использование — тип RWMutex, который позволяет нам блокировать и разблокировать чтение и запись в тип. Итак, если у нас есть тип, содержащий sync.RWMutex и карту, мы можем контролировать, когда к ней можно получить доступ.
Еще один интересный тип для исследования в том же пакете синхронизации — это Map, который уже предлагает нам ряд функций, которые помогут нам работать с нашей картой, с которой в конечном итоге мы не сможем работать изначально, как с предыдущим решением.
В зависимости от варианта использования, который мы реализуем, тот или иной будет для нас полезнее, и нет никого лучше другого, это всегда будет зависеть от того, что нам нужно.
Я надеюсь, что все, что я пытался объяснить в этом посте, было ясно, и, пожалуйста, если есть какая-то часть, которая не была полностью ясна, или есть части, которые я не осветил и которые вы хотели бы, чтобы я сделал, оставьте оставьте мне комментарий прямо здесь или через мои социальные сети, которые есть в моем профиле, и я буду рад ответить.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3