Ответ 1
Является ли объект массива явно содержать индексы?
Короткий ответ: Нет.
Более длинный ответ: Обычно нет, но теоретически он может это сделать.
Полный ответ:
Ни спецификация языка Java, ни спецификация виртуальной машины Java не дают никаких гарантий относительно того, как массивы реализуются внутренне. Все, что требуется, это то, что к элементам массива обращается индекс int
, имеющий значение от 0
до length-1
. Как реализация на самом деле извлекает или сохраняет значения этих индексированных элементов, является частным, частным для реализации.
Совершенно совместимый JVM может использовать хеш-таблицу для реализации массивов. В этом случае элементы были бы не последовательными, разбросанными по памяти, и им нужно было бы записывать индексы элементов, чтобы знать, что они собой представляют. Или он мог отправлять сообщения человеку на Луне, который записывает значения массива на помеченные листы бумаги и хранит их в большом количестве маленьких шкафов. Я не понимаю, почему JVM захочет делать все это, но может.
Что будет на практике? Типичная JVM будет выделять память для элементов массива как плоский непрерывный кусок памяти. Поиск определенного элемента тривиально: умножьте фиксированный объем памяти каждого элемента на индекс искомого элемента и добавьте его в адрес памяти начала массива: (index * elementSize) + startOfArray
. Это означает, что хранение массива состоит только из значений исходного элемента, последовательно упорядоченных по индексу. Нет никакой цели также хранить значение индекса с каждым элементом, потому что адрес элемента в памяти подразумевает его индекс и наоборот. Тем не менее, я не думаю, что диаграмма, которую вы показываете, пыталась сказать, что она явно хранит индексы. Диаграмма просто маркирует элементы на диаграмме, чтобы вы знали, что они собой представляют.
Техника использования непрерывного хранения и вычисления адреса элемента по формуле проста и чрезвычайно быстра. У него также очень мало накладных расходов памяти, предполагая, что программы выделяют свои массивы настолько большими, насколько они действительно нужны. Программы зависят от ожидаемых характеристик производительности массивов, поэтому JVM, который сделал что-то странное с хранилищем массивов, вероятно, будет работать плохо и будет непопулярным. Таким образом, практические JVM будут ограничены для реализации непрерывного хранилища или того, что работает аналогично.
Я могу думать только о нескольких вариантах этой схемы, которые когда-либо были бы полезны:
-
Распределенные в стеке или распределенные регистры массивы: во время оптимизации JVM может определить через анализ утечки, что массив используется только в рамках одного метода, и если массив также является небольшим фиксированным размером, он будет идеальным объектом-кандидатом для распределения непосредственно в стеке, вычисляя адрес элементов относительно указателя стека. Если массив чрезвычайно мал (фиксированный размер может быть до 4 элементов), JVM может пойти еще дальше и сохранить элементы непосредственно в регистры процессора, при этом все обращения к элементам будут развернуты и жестко закодированы.
-
Упакованные логические массивы: наименьшая адресная единица памяти на компьютере обычно представляет собой 8-разрядный байт. Это означает, что если JVM использует байты для каждого логического элемента, тогда логические массивы отбрасывают 7 из каждых 8 бит. Он использовал бы только 1 бит на элемент, если бы булевы были собраны вместе в памяти. Эта упаковка обычно не выполняется, поскольку извлечение отдельных бит байтов происходит медленнее, и для обеспечения многопоточности требуется особое внимание. Однако упакованные булевы массивы могут иметь смысл в некоторых встроенных устройствах с ограничением памяти.
Тем не менее, ни один из этих вариантов не требует, чтобы каждый элемент хранил свой собственный индекс.
Я хочу обратиться к нескольким другим сведениям, которые вы упомянули:
массивы сохраняют указанное количество данных одного и того же типа
Правильно.
Тот факт, что все элементы массива являются одним и тем же типом, важен, поскольку он означает, что все элементы имеют одинаковый размер в памяти. Это то, что позволяет размещать элементы, просто умножая их общий размер.
Это по-прежнему технически верно, если тип элемента массива является ссылочным типом. Хотя в этом случае значение каждого элемента не является самим объектом (который может иметь разный размер), а только адресом, который относится к объекту. Кроме того, в этом случае фактический тип времени выполнения объектов, на которые ссылается каждый элемент массива, может быть любым подклассом типа элемента. Например.
Object[] a = new Object[4]; // array whose element type is Object
// element 0 is a reference to a String (which is a subclass of Object)
a[0] = "foo";
// element 1 is a reference to a Double (which is a subclass of Object)
a[1] = 123.45;
// element 2 is the value null (no object! although null is still assignable to Object type)
a[2] = null;
// element 3 is a reference to another array (all arrays classes are subclasses of Object)
a[3] = new int[] { 2, 3, 5, 7, 11 };
массивы являются последовательными ячейками памяти
Как обсуждалось выше, это не обязательно должно быть правдой, хотя на практике это почти наверняка.
Чтобы идти дальше, обратите внимание, что хотя JVM может выделять непрерывную часть памяти из операционной системы, это не означает, что она заканчивается смежным в физической ОЗУ. ОС может предоставлять программам виртуальное адресное пространство, которое ведет себя как бы смежное, но с отдельными страницами памяти, разбросанными в разных местах, включая физическую RAM, swap файлы на диске или регенерируются при необходимости, если их содержимое, как известно, пустое. Даже в той степени, в которой страницы виртуального пространства памяти находятся в физической ОЗУ, они могут быть организованы в физической ОЗУ в произвольном порядке со сложными таблицами страниц, которые определяют сопоставление от виртуальных до физических адресов. И даже если ОС думает, что имеет дело с "физической оперативной памятью", она все еще может работать в эмуляторе. Могут быть слои на слоях на слоях, это моя точка зрения, и довести их до конца все, чтобы узнать, что действительно происходит, занимает некоторое время
Часть спецификаций языка программирования заключается в том, чтобы отделить кажущееся поведение от деталей реализации. При программировании вы можете часто программировать только для спецификации, не беспокоясь о том, как это происходит внутри страны. Однако детали реализации становятся актуальными, когда вам нужно иметь дело с реальными ограничениями ограниченной скорости и памяти.
Поскольку массив является объектом, а ссылки на объекты хранятся в стеке, а фактические объекты живут в куче, ссылки на объекты указывают на фактические объекты
Это правильно, за исключением того, что вы сказали о стеке. Ссылки на объекты могут быть сохранены в стеке (как локальные переменные), но они также могут быть сохранены как статические поля или поля экземпляра или как элементы массива, как показано в примере выше.
Кроме того, как я уже упоминал ранее, умные реализации иногда могут выделять объекты непосредственно в стеке или в регистры процессора в качестве оптимизации, хотя это имеет нулевой эффект на поведение вашей программы, но только ее производительность.
Компилятор просто знает, куда идти, просматривая номер индекса предоставленного массива во время выполнения.
В Java это не компилятор, который делает это, а виртуальную машину. Массивы функция самого JVM, поэтому компилятор может перевести ваш исходный код, который использует массивы просто для байт-кода, который использует массивы. Затем это задача JVM, чтобы решить, как реализовать массивы, а компилятор не знает и не заботится о том, как они работают.