Зачем использовать массивы вместо слайсов?

Я читал на Go, и получил тупой размышления об этом фундаментальном вопросе.

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

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

Так почему же создатели разрешили массивы в первую очередь? Когда будут использоваться массивы вместо слайсов? Была ли когда-нибудь ситуация, когда использование массивов поверх срезов будет убедительным?

Когда я ознакомился с официальной документацией ( http://golang.org/doc/effective_go.html), единственная полезная часть, которую я нашел, была:

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

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

Но даже этот стиль не идиоматичен. Вместо этого используйте ломтики.

Итак, каковы реальные примеры "планирования детального размещения памяти" или "помощи в избежании выделения", для которых срезы не подходят?

4 ответа

Решение

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

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

Они могут быть сериализованы путем кодирования / двоичного кода.

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

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


Хэшируемые / сериализуемые все приятно иметь, но я просто не уверен, действительно ли они настолько привлекательны, чтобы иметь

Что бы вы сделали, если бы хотели иметь карту хэшей md5? Невозможно использовать байтовый фрагмент, поэтому вам нужно сделать что-то вроде этого, чтобы обойти систему типов:

// 16 bytes
type hashableMd5 struct {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p byte}

Затем создайте для него функцию сериализации. Хэш-массивы означают, что вы можете просто назвать его [16] байтом.

Похоже, ближе к malloc C, sizeof

Нет, это не имеет ничего общего с malloc или sizeof. Они предназначены для выделения памяти и получения размера переменной.

Тем не менее, CGo является еще одним вариантом использования для этого. Команда cgo создает типы, которые имеют ту же структуру памяти, что и их соответствующие типы C. Для этого иногда требуется вставить безымянные массивы для заполнения.

Если проблемы могут быть решены с... ноль / незначительное снижение производительности с использованием срезов...

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

В дополнение к ответу Стивена Вайнберга:

Итак, каковы реальные примеры "планирования детального размещения памяти" или "помощи в избежании выделения", для которых срезы не подходят?

Вот пример для "планирования детального расположения памяти". Существует много форматов файлов. Обычно формат файла такой: он начинается с "магического числа", затем следует информационный заголовок, структура которого обычно фиксирована. Этот заголовок содержит информацию о содержимом, например, в случае файла изображения он содержит информацию, такую ​​как размер изображения (ширина, высота), формат пикселя, используемое сжатие, размер заголовка, смещение данных изображения и т. Д. (В основном описывает остальную часть файла и как это интерпретировать / обработать).

Если вы хотите реализовать формат файла в Go, простой и удобный способ - создать struct содержащий поля заголовка формата. Если вы хотите прочитать файл такого формата, вы можете использовать binary.Read() способ прочитать весь заголовок struct в переменную, и аналогично, когда вы хотите записать файл этого формата, вы можете использовать binary.Write() записать полный заголовок за один шаг в файл (или куда бы вы ни отправляли данные).

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

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

И где массивы входят в картину?

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

Так что же делать, если у вас есть спецификация заголовка со многими полями, но вам нужно только несколько из них? Вы можете определить структуру, которая будет содержать нужные вам поля, а между полями вы можете использовать массивы с размером полей, которые вам просто не нужны / не нужны. Это гарантирует, что вы все равно сможете прочитать весь заголовок одним вызовом функции, и массивы будут в основном заполнителями неиспользуемых данных в файле. Вы также можете использовать пустой идентификатор в качестве имени поля в заголовке struct определение, если вы не будете использовать данные.

Теоретический пример

Для простого примера, давайте реализуем формат, в котором магия - "TGI" (Теоретическое изображение Go), а заголовок содержит поля наподобие этого: 2 зарезервированных слова (16 бит каждое), 1 ширина изображения dword, 1 высота изображения dword, теперь пришло 15 "безразличных" слов, тогда изображение экономит время в виде 8-байтовых наносекунд с 1 января 1970 года по Гринвичу.

Это можно смоделировать с помощью такой структуры (исключая магическое число):

type TGIHeader struct {
    _        uint16 // Reserved
    _        uint16 // Reserved
    Width    uint32
    Height   uint32
    _        [15]uint32 // 15 "don't care" dwords
    SaveTime int64
}

Чтобы прочитать файл TGI и распечатать полезную информацию:

func ShowInfo(name string) error {
    f, err := os.Open(name)
    if err != nil {
        return err
    }
    defer f.Close()

    magic := make([]byte, 3)
    if _, err = f.Read(magic); err != nil {
        return err
    }
    if !bytes.Equal(magic, []byte("TGI")) {
        return errors.New("Not a TGI file")
    }

    th := TGIHeader{}
    if err = binary.Read(f, binary.LittleEndian, &th); err != nil {
        return err
    }

    fmt.Printf("%s is a TGI file,\n\timage size: %dx%d\n\tsaved at: %v",
        name, th.Width, th.Height, time.Unix(0, th.SaveTime))

    return nil
}

Одно практическое отличие состоит в том, что arrays хаси, а slices не.

Чтобы расширить это

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

Массивы могут быть более эффективными при рассмотрении накладных расходов на выделение кучи. Подумайте о сборщике мусора, управлении кучей, фрагментации и т. Д.

Например, если у вас есть переменная локального массива, как var x [8]int который не используется после возврата функции, скорее всего, он будет размещен в стеке. И распределение стека намного дешевле, чем распределение кучи.

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

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

Другие вопросы по тегам