Показаны сообщения с ярлыком maps (карты) в Go. Показать все сообщения
Показаны сообщения с ярлыком maps (карты) в Go. Показать все сообщения

среда, 30 декабря 2020 г.

Go style guides: необработанные строки, инициализация ссылок на структуры и карт

Используйте необработанные строковые литералы, чтобы избежать экранирования

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

Менее удачный вариант:

wantError := "unknown name:\"test\""

Более удачный вариант:

wantError := `unknown error:"test"`

Инициализация ссылок на структуры

Используйте &T{} вместо new(T) при инициализации ссылок на структуру, чтобы это было консистентно с инициализацией структуры.

Менее удачный вариант:

sval := T{Name: "foo"}

// неконсистентно
sptr := new(T)
sptr.Name = "bar"

Более удачный вариант:

sval := T{Name: "foo"}

sptr := &T{Name: "bar"}

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

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

Менее удачный вариант:

// Объявление и инициализация визуально похожи.

var (
    // m1 безопасно читать и записывать;
    // m2 запаникует при записи.
    m1 = map[T1]T2{}
    m2 map[T1]T2
)

Более удачный вариант:

// Объявление и инициализация визуально различны.

var (
    // m1 безопасно читать и записывать;
    // m2 запаникует при записи.
    m1 = make(map[T1]T2)
    m2 map[T1]T2
)

По возможности предоставляйте подсказки емкости при инициализации карт с помощью make().

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

Менее удачный вариант:

m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3

Более удачный вариант:

m := map[T1]T2{
    k1: v1,
    k2: v2,
    k3: v3,
}

Основное практическое правило - использовать литералы карты при добавлении фиксированного набора элементов во время инициализации, в противном случае используйте make (и укажите подсказку размера, если она доступна).


Читайте также:


четверг, 10 декабря 2020 г.

Go style guides: производительность, указание емкости контейнера

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

Указание подсказок емкости карты

По возможности предоставляйте подсказки (hint) по емкости при инициализации карт с помощью make().

make(map[T1]T2, hint)

Предоставление подсказки о емкости для make() приводит к попытке подобрать правильный размер карты во время инициализации, что снижает потребность в увеличении карты и распределении по мере добавления элементов в карту.

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

Менее удачный вариант:

m := make(map[string]os.FileInfo)

files, _ := ioutil.ReadDir("./files")
for _, f := range files {
    m[f.Name()] = f
}

m создается без указания размера; во время назначения может быть больше выделений.

Более удачный вариант:

files, _ := ioutil.ReadDir("./files")

m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
    m[f.Name()] = f
}

m создается с подсказкой размера; во время назначения может быть меньше выделений.

Указание емкости среза

По возможности предоставляйте подсказки о емкости при инициализации срезов с помощью make(), особенно при планировании дальнейших добавлений в срез.

make([]T, length, capacity)

В отличие от карт, емкость среза не является подсказкой: компилятор выделит достаточно памяти для емкости среза, как это предусмотрено для make(), что означает, что последующие операции append() будут нести нулевые выделения (до тех пор, пока длина среза не будет соответствовать емкости (capacity), указанной при создании среза, после чего любые добавления потребуют изменения размера для хранения дополнительных элементов).

Менее удачный вариант:

for n := 0; n < b.N; n++ {
    data := make([]int, 0)
    for k := 0; k < size; k++{
        data = append(data, k)
    }
}

BenchmarkBad    100000000    2.48s

Более удачный вариант:

for n := 0; n < b.N; n++ {
    data := make([]int, 0, size)
    for k := 0; k < size; k++{
        data = append(data, k)
    }
}

BenchmarkGood   100000000    0.21s


Читайте также:


пятница, 6 ноября 2020 г.

Go style guides: копирование срезов и карт на границах

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

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

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

Неудачный вариант:

func (d *Driver) SetTrips(trips []Trip) {
    d.trips = trips
}

trips := ...
d1.SetTrips(trips)

// Вы хотели изменить d1.trips?
trips[0] = ...

Хороший вариант:

func (d *Driver) SetTrips(trips []Trip) {
    d.trips = make([]Trip, len(trips))
    copy(d.trips, trips)
}

trips := ...
d1.SetTrips(trips)

// Теперь мы можем изменить trips[0], 
// не затрагивая d1.trips.
trips[0] = ...

Возврат срезов и карт

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

Неудачный вариант:

type Stats struct {
    mu sync.Mutex
    counters map[string]int
}

// Snapshot возвращает текущую статистику.
func (s *Stats) Snapshot() map[string]int {
    s.mu.Lock()
    defer s.mu.Unlock()

    return s.counters
}

// snapshot больше не защищен мьютексом, поэтому любой
// доступ к snapshot повод для гонки данных.
snapshot := stats.Snapshot()

Хороший вариант:

type Stats struct {
    mu sync.Mutex
    counters map[string]int
}

func (s *Stats) Snapshot() map[string]int {
    s.mu.Lock()
    defer s.mu.Unlock()

    result := make(map[string]int, len(s.counters))
    for k, v := range s.counters {
        result[k] = v
    }
    return result
}

// Snapshot теперь является копией.
snapshot := stats.Snapshot()


Читайте также:


понедельник, 18 мая 2020 г.

Неожиданный перевод строки

Почему эта программа не компилируется?

func main() {
    fruit := []string{
        "apple",
        "banana",
        "cherry"
    }
    fmt.Println(fruit)
}

../main.go:5:11: syntax error: unexpected newline, expecting comma or }

Ответ

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

func main() {
    fruit := []string{
        "apple",
        "banana",
        "cherry", // добавлена запятая
    }
    fmt.Println(fruit) // "[apple banana cherry]"
}

Такое поведение является следствием правил вставки точек с запятой в Go.

В результате вы можете добавлять и удалять строки без изменения окружающего кода.


Читайте также:


суббота, 16 мая 2020 г.

Присвоение записи в nil карте в Golang

Почему эта программа паникует?

var m map[string]float64
m["pi"] = 3.1416

panic: assignment to entry in nil map

Ответ

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

m := make(map[string]float64)
m["pi"] = 3.1416

Использование литерала карты:

m := map[string]float64{}
m["pi"] = 3.1416


Читайте также:


понедельник, 27 апреля 2020 г.

Две базовых реализации набора (set) в Golang

Идиоматический способ реализации набора в Go - использовать карту.

set := make(map[string]bool) // Новый пустой набор
set["Foo"] = true            // Добавить
for k := range set {         // Пройти в цикле
    fmt.Println(k)
}
delete(set, "Foo")    // Удалить
size := len(set)      // Размер
exists := set["Foo"]  // Наличие элемента

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

type void struct{}
var member void

set := make(map[string]void) // Новый пустой набор
set["Foo"] = member          // Добавить
for k := range set {         // Пройти в цикле
    fmt.Println(k)
}
delete(set, "Foo")      // Удалить
size := len(set)        // Размер
_, exists := set["Foo"] // Наличие элемента


Читайте также:


четверг, 16 апреля 2020 г.

Найти элемент в срезе/массиве линейным или бинарным поиском в Golang

Линейный поиск

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

// Find возвращает наименьший индекс i, 
// при котором x == a[i],
// или len(a), если такого индекса нет.
func Find(a []string, x string) int {
    for i, n := range a {
        if x == n {
            return i
        }
    }
    return len(a)
}

// Contains указывает, содержится ли x в a.
func Contains(a []string, x string) bool {
    for _, n := range a {
        if x == n {
            return true
        }
    }
    return false
}

Бинарный поиск

"Бинарный поиск быстрее линейного, но работает, только если ваши данные в порядке. Это сортировка." - Дэн Бентли

Если массив отсортирован, вы можете использовать бинарный поиск. Это будет намного эффективнее, поскольку бинарный поиск выполняется в наихудшем логарифмическом времени, делая O(log n) сравнений, где n - размер среза.

Существует три пользовательские функции бинарного поиска: sort.SearchInts, sort.SearchStrings или sort.SearchFloat64s.

Все они имеют сигнатуру

func SearchType(a []Type, x Type) int

и возвращают

  • наименьший индекс i, при котором x <= a[i]
  • или len(a), если такого индекса нет.

Срез должен быть отсортирован в порядке возрастания.

a := []string{"A", "C", "C"}

fmt.Println(sort.SearchStrings(a, "A")) // 0
fmt.Println(sort.SearchStrings(a, "B")) // 1
fmt.Println(sort.SearchStrings(a, "C")) // 1
fmt.Println(sort.SearchStrings(a, "D")) // 3

Общий бинарный поиск

Существует также универсальная функция бинарного поиска sort.Search.

func Search(n int, f func(int) bool) int

Она возвращает:

  • наименьший индекс i, при котором f(i) истинно (равно true)
  • или n, если такого индекса нет.

Требуется, чтобы f было false для некоторого (возможно, пустого) префикса входного диапазона, а затем true для остальной части.

Этот пример отражает предыдущий, но использует общую sort.Search вместо sort.SearchInts.

a := []string{"A", "C", "C"}
x := "C"

i := sort.Search(len(a), func(i int) bool { return x <= a[i] })
if i < len(a) && a[i] == x {
    fmt.Printf("Найдено %s по индексу %d в %v.\n", x, i, a)
} else {
    fmt.Printf("Не найдено %s в %v.\n", x, a)
}
// Вывод: Найдено C по индексу 1 в [A C C].

Вариант карты

Если вы делаете повторный поиск и обновление, вы можете использовать карту (map) вместо среза. Карта обеспечивает операции поиска, вставки и удаления за O(1) ожидаемое амортизированное время.


Читайте также:


суббота, 22 февраля 2020 г.

Карта (map) в Golang: создание, добавление, получение, удаление

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

Создание новой карты

var m map[string]int                // nil карта 
                                    // string-int пар

m1 := make(map[string]float64)      // пустая карта 
                                    // string-float64 пар

m2 := make(map[string]float64, 100) // преаллоцируем место 
                                    // для 100 вхождений

m3 := map[string]float64{           // литерал карты
    "e":  2.71828,
    "pi": 3.1416,
}
fmt.Println(len(m3))                // размер карты: 2

  • Карта (или словарь) - это неупорядоченная коллекция пар ключ-значение, где каждый ключ уникален.
  • Вы создаете новую карту с оператором make или литералом карты.
  • Нулевым значением по умолчанию для карты является nil. nil карта эквивалентна пустой карте, за исключением того, что элементы не могут быть добавлены.
  • Функция len возвращает размер карты, который представляет собой количество пар ключ-значение.

Предупреждение: если вы попытаетесь добавить элемент в неинициализированную карту, вы получите загадочную ошибку времени выполнения (run-time error) Присвоение к вхождению в nil-карте (assignment to entry in nil map).

Добавление, обновление, получение, удаление ключа/значения

m := make(map[string]float64)

m["pi"] = 3.14             // Добавляем новую пару 
                           // ключ-значение
m["pi"] = 3.1416           // Обновляем значение
fmt.Println(m)             // Печатаем карту: 
                           // "map[pi:3.1416]"

v := m["pi"]               // Получаем значение: 
                           // v == 3.1416
v = m["pie"]               // Не найдено: 
                           // v == 0 (нулевое значение)

_, found := m["pi"]        // found == true
_, found = m["pie"]        // found == false

if x, found := m["pi"]; found {
    fmt.Println(x)
}                           // Печатает "3.1416"

delete(m, "pi")             // Удаляем пару ключ-значение
fmt.Println(m)              // Печатаем карту: "map[]"

  • Когда вы индексируете карту, вы получаете два возвращаемых значения; второе (необязательное) является логическим значением, указывающим, существует ли ключ.
  • Если ключ не существует, первое значение будет нулевым значением по умолчанию.

For-each range цикл

m := map[string]float64{
    "pi": 3.1416,
    "e":  2.71828,
}
fmt.Println(m) // "map[e:2.71828 pi:3.1416]"

for key, value := range m { // Порядок не определен 
    fmt.Println(key, value)
}

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

Начиная с Go 1.12, пакет fmt печатает карты в порядке сортировки ключей, чтобы упростить тестирование.

Производительность и реализация

  • Карты основаны на хэш-таблицах.
  • Операции добавления, получения и удаления выполняются в постоянное ожидаемое время. Временная сложность операции добавления амортизируется.
  • Операторы сравнения == и != должны быть определены для типа ключа.

Подробней о работе и реализации карт можно прочесть в книге Golang для профи.


Читайте также:


купить игрушку gopher

пятница, 17 января 2020 г.

Работа с картами (map) в Golang

Одной из наиболее полезных структур данных в информатике является хэш-таблица. Существует множество реализаций хэш-таблиц с различными свойствами, но в целом они предлагают быстрый поиск, добавление и удаление. Go предоставляет встроенный тип map (карта), который реализует хэш-таблицу.

Декларация и инициализация

Тип map в Go выглядит следующим образом:

map[KeyType]ValueType

где KeyType может быть любым типом, который сопоставим (comparable) (подробнее об этом позже), а ValueType может быть любым типом вообще, включая другую карту!

Эта переменная m является картой строковых ключей для значений int:

var m map[string]int

Типы карт (map) являются ссылочными типами, такими как указатели или срезы (slice), и поэтому значение m выше равно nil; оно не указывает на инициализированную карту. Нулевая карта (nil map) ведет себя как пустая карта при чтении, но попытки записи в нулевую карту вызовут панику во время выполнения (runtime panic). Чтобы инициализировать карту, используйте встроенную функцию make:

m = make(map[string]int)

Функция make выделяет и инициализирует структуру данных hash map и возвращает значение карты, которое указывает на нее. Специфика этой структуры данных является деталью реализации среды выполнения и не определяется самим языком. В этой посте мы сосредоточимся на использовании карт, а не на их реализации.

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

Go предоставляет знакомый синтаксис для работы с картами. Этот оператор устанавливает ключ "route" на значение 66:

m["route"] = 66

Этот оператор извлекает значение, хранящееся под ключом "route", и присваивает его новой переменной i:

i := m["route"]

Если запрошенный ключ не существует, мы получаем нулевое значение типа значения. В этом случае типом значения является int, поэтому нулевое значение равно 0:

j := m["root"]
// j == 0

Встроенная функция len возвращает количество элементов в карте:

n := len(m)

Встроенная функция delete удаляет запись в карте:

delete(m, "route")

Функция delete ничего не возвращает и ничего не сделает, если указанный ключ не существует.

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

i, ok := m["route"]

В этом утверждении первому значению (i) присваивается значение, хранящееся под ключом "route". Если этот ключ не существует, i является нулевым значением типа значения (0). Второе значение (ok) является логическим (bool) значением, которое имеет значение true, если ключ существует в карте, и значение false, если нет.

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

_, ok := m["route"]

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

for key, value := range m {
    fmt.Println("Key:", key, "Value:", value)
}

Чтобы инициализировать карту с некоторыми данными, используйте литерал карты:

commits := map[string]int{
    "rsc": 3711,
    "r":   2138,
    "gri": 1908,
    "adg": 912,
}

Тот же синтаксис может использоваться для инициализации пустой карты, которая функционально идентична использованию функции make:

m = map[string]int{}

Использование нулевых значений

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

Например, карта bool значений может использоваться как структура данных, подобная множеству (напомним, что нулевое значение для bool типа - false). Этот пример просматривает связанный список узлов и печатает их значения. Он использует карту указателей узлов для обнаружения циклов в списке.

type Node struct {
    Next  *Node
    Value interface{}
}
var first *Node

visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
    if visited[n] {
        fmt.Println("cycle detected")
        break
    }
    visited[n] = true
    fmt.Println(n.Value)
}

Выражение visited[n] является истинным (true), если n было посещено, или ложным (false), если n не присутствует. Нет необходимости использовать форму с двумя значениями, чтобы проверить наличие n в карте; нулевое значение по умолчанию делает это для нас.

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

type Person struct {
    Name  string
    Likes []string
}
var people []*Person

likes := make(map[string][]*Person)
for _, p := range people {
    for _, l := range p.Likes {
        likes[l] = append(likes[l], p)
    }
}

Чтобы напечатать список людей, которые любят сыр:

for _, p := range likes["cheese"] {
    fmt.Println(p.Name, "likes cheese.")
}

Чтобы напечатать количество людей, которые любят бекон:

fmt.Println(len(likes["bacon"]), "people like bacon.")

Обратите внимание, что так как range и len рассматривают нулевой срез как срез нулевой длины, эти два последних примера будут работать, даже если никто не любит сыр или бекон (хотя это маловероятно).

Типы ключей

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

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

hits := make(map[string]map[string]int)

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

n := hits["/doc/"]["au"]

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

func add(m map[string]map[string]int, path, country string) {
    mm, ok := m[path]
    if !ok {
        mm = make(map[string]int)
        m[path] = mm
    }
    mm[country]++
}
add(hits, "/doc/", "au")

С другой стороны, дизайн, в котором используется одна карта с struct ключом, устраняет всю эту сложность:

type Key struct {
    Path, Country string
}
hits := make(map[Key]int)

Когда вьетнамец посещает домашнюю страницу, увеличивая (и, возможно, создавая) соответствующий счетчик за одну строку:

hits[Key{"/", "vn"}]++

И точно так же просто увидеть, сколько швейцарцев прочитали спецификацию:

n := hits[Key{"/ref/spec", "ch"}]

Конкурентность

Карты не безопасны для конкурентного использования: не определено, что происходит, когда вы читаете и пишете в них одновременно. Если вам нужно читать и записывать на карту из конкурентно выполняемых go-процедур (goroutines), доступ должен быть обеспечен каким-то механизмом синхронизации. Один из распространенных способов защиты карт - это sync.RWMutex.

Этот оператор объявляет переменную-счетчик, которая является анонимной структурой, содержащей карту и встроенный sync.RWMutex.

var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}

Для чтения со счетчика возьмите блокировку чтения (read lock):

counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)

Для записи в счетчик возьмите блокировку записи (write lock):

counter.Lock()
counter.m["some_key"]++
counter.Unlock()

Порядок итерации

При итерации по карте с помощью range цикла порядок итераций не указывается и не гарантируется, что он будет одинаковым от одной итерации к следующей. Если вам требуется стабильный порядок итераций, вы должны поддерживать отдельную структуру данных, которая определяет этот порядок. В этом примере используется отдельный отсортированный срез ключей для печати map[int]string в порядке расположения ключей:

import "sort"

var m map[int]string
var keys []int
for k := range m {
    keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
    fmt.Println("Key:", k, "Value:", m[k])
}


Читайте также:


понедельник, 16 сентября 2019 г.

Сортировка map (карт) по ключу или значению в Golang

Map (карта) в Golang - это неупорядоченная коллекция пар ключ-значение. Порядок записи в map не имеет значения и при итерации пары ключ-значение выдаются в случайном порядке. Если вам нужен стабильный порядок итераций, вы должны поддерживать отдельную структуру данных.

В следующем примере используется отсортированный срез (slice) ключей для печати map[string]int в порядке расположения ключей.

m := map[string]int{"A": 21, "C": 3, "B": 46}

keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)

for _, k := range keys {
    fmt.Println(k, m[k])
}

Вывод:

A 21
B 46
C 3

Также, начиная с Go 1.12, пакет fmt печатает карты в порядке сортировки ключей, чтобы упростить тестирование. Например:

m := map[string]int{"A": 21, "C": 3, "B": 46}
fmt.print(m)

Вывод:

map[A:21 B:46 C:3]


Читайте также:


среда, 29 мая 2019 г.

Спецификация Go: типы карты (map types)

Карта (map) - это неупорядоченная группа элементов одного типа, называемая типом элемента (element type), индексируемая набором уникальных ключей другого типа, называемого типом ключа (key type). Значение не инициализированной карты равно nil.

MapType     = "map" "[" KeyType "]" ElementType .
KeyType     = Type .

Операторы сравнения == и != должны быть полностью определены для операндов типа ключа; таким образом, тип ключа не должен быть функцией, картой или срезом (slice). Если тип ключа является типом интерфейса, эти операторы сравнения должны быть определены для значений динамического ключа; сбой вызовет панику во время выполнения (run-time panic).

map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}

Количество элементов карты называется его длиной. Для карты m длину можно обнаружить с помощью встроенной функции len, и она может измениться во время выполнения (run-time). Элементы могут быть добавлены во время выполнения с использованием назначений и извлечены с помощью индексных выражений; они могут быть удалены с помощью встроенной функции delete.

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

make(map[string]int)
make(map[string]int, 100)

Начальная емкость не ограничивает ее размер: карты растут в соответствии с количеством элементов, хранящихся в них, за исключением нулевых карт (nil maps). Нулевая карта эквивалентна пустой карте, за исключением того, что никакие элементы не могут быть добавлены.


Читайте также:


среда, 13 февраля 2019 г.

Go FAQ: карты (maps) в Go

Почему карты в Go являются встроенным типом?

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

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

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

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

Почему карты, срезы и каналы ссылаются, в то время как массивы являются значениями?

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


Читайте также:


суббота, 9 февраля 2019 г.

Go FAQ: Почему операции с map (картой) не определены как атомарные?

После долгого обсуждения среди создателей Go было решено, что типичное использование карт не требует безопасного доступа из нескольких go-процедур (goroutines), и в тех случаях, когда это произошло, карта была вероятно, частью какой-то более крупной структуры данных или вычислений, которые уже были синхронизированы. Поэтому требование, чтобы все операции на карте захватывали мьютекс, замедляло бы отключение большинства программ, при этом добавило безопасности лишь немногим. Это было нелегкое решение, однако, поскольку такое решение принято, это означает, что неконтролируемый доступ к карте может привести к сбою программы.

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

Доступ к карте небезопасен только когда происходят обновления. Пока все программы только читают - ищут элементы на карте, в том числе итерируя с помощью цикла for range, и не изменяя карты посредством присваивания к элементам карты или выполнения удаления - такой процесс (чтение) безопасен и при одновременном доступе к карте без синхронизации.

В качестве помощи, чтобы исправить использование карты, некоторые реализации языка содержат специальную проверку, которая автоматически сообщает в runtime (окружение среды исполнения) о небезопасном изменении карты при параллельном исполнении.


Читайте также: