Подтвердить что ты не робот

Является ли объект массива явно содержать индексы?

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

Поскольку массив является объектом, а ссылки на объекты хранятся в стеке, а фактические объекты живут в куче, ссылки на объекты указывают на фактические объекты.

Но когда я сталкивался с примерами создания массивов в памяти, они всегда показывают что-то вроде этого:

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

Внутренняя работа объектов, преподаваемых различными веб-сайтами и преподавателями

Но недавно я наткнулся на онлайн-заметки Java, в которых они заявили, что явные индексы массивов не указаны в памяти. Компилятор просто знает, куда идти, глядя на номер индекса предоставленного массива во время выполнения.

Точно так же:

Введите описание изображения здесь

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

Мне нужно больше разъяснений по этому вопросу. Являются ли индексы объектов массива явно отображаемыми в памяти или нет? Если нет, то как Java управляет командами для перехода в определенное место в массиве во время выполнения?

4b9b3361

Ответ 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, чтобы решить, как реализовать массивы, а компилятор не знает и не заботится о том, как они работают.

Ответ 2

В Java массивы - это объекты. См. JLS - Глава 10. Массивы:

В языке программирования Java массивы являются объектами (§4.3.1), динамически создаются и могут быть назначены переменным типа Object (§4.3.2). Все методы класса Object могут быть вызваны в массиве.

Если вы посмотрите 10.7. Array Members, вы увидите, что индекс является не частью элемента массива:

Элементы типа массива являются следующими:

Поле public final length, которое содержит количество компонентов массива. длина может быть положительной или нулевой.

  • Метод public clone, который переопределяет метод с тем же именем в классе Object и не выбрасывает исключенные исключения. Тип возврата метод clone типа массива T[] равен T[].

  • Все члены, унаследованные от класса Object; единственный метод объекта который не унаследован, является его методом клонирования.

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

Сложность доступа к элементу - это O (1), поскольку ему нужно только вычислить смещение адреса. Стоит отметить, что это поведение не предполагается для всех языков программирования.

Ответ 3

Массив, как вы говорите, будет хранить объекты только одного типа. Каждый тип будет иметь соответствующий размер в байтах. Например, в int[] каждый элемент будет занимать 4 байта, каждый byte в byte[] будет занимать 1 байт, каждый Object в Object[] будет занимать 1 слово (потому что это действительно указатель на кучу ) и т.д.

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

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

Если ваш массив начинается с некоторой позиции памяти N, вы можете использовать указанный индекс я и размер элемента S для вычисления того, что память, которую вы ищете, будет находиться в адресе памяти N + (S * I).

Так Java находит позиции памяти для индексов во время выполнения, не сохраняя их.

Ответ 4

На вашем первом снимке arr[0] до arr[4] нет ссылок на элементы массива. Это просто иллюстративные ярлыки для местоположения.

Ответ 5

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

То есть, на первой диаграмме метки arr[0], arr[1] и т.д. не являются частью массива. Они просто существуют для иллюстративных целей, указывая, как элементы массива выложены в памяти.

Что вам сказали, а именно, что массивы хранятся в смежных местах в памяти (по крайней мере, в том, что касается виртуальных адресов, на современных аппаратных архитектурах они не должны отображаться в смежных физических адресах), а элементы массива расположены на основе их размер и индекс. (По крайней мере, в... ну, это определенно верно в C/С++. Это почти наверняка правильно в большинстве, если не во всех, реализациях Java. Но это, вероятно, неверно в языках, допускающих разреженные массивы или массивы, которые могут расти или динамически сжимаются.)

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

Ответ 6

Ссылка на массив не всегда находится в стеке. Он также может храниться в куче, если он является членом класса.

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

См:
* Спецификация языка Java, Java SE 8 Edition - Массивы
* Спецификация виртуальной машины Java, Java SE 8 Edition - Типы ссылок и значения

Ответ 7

Критическая часть для понимания состоит в том, что память, выделенная для массива, смежна. Поэтому, учитывая адрес исходного элемента массива, т.е. Arr [0], эта непрерывная схема распределения памяти помогает среде выполнения определять адрес элемента массива с учетом его индекса.

Скажем, мы объявили int [] arr = new int [5], а его начальный элемент массива arr [0] находится по адресу 100. Чтобы достичь третьего элемента в массиве, все, что должно выполнить выполнение, следуя математике 100 + ((3-1)*32) = 164 (предполагая, что 32 - это целое число). Таким образом, все, что требуется для выполнения, это адрес исходного элемента этого массива. Он может выводить все остальные адреса элементов массива на основе индекса и размера типа данных, хранящегося в массиве.

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

Ответ 8

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

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

Ответ 9

"Последовательные ячейки памяти" являются деталями реализации и могут быть неправильными. Например, переменные массивы Objective-C не используют последовательные ячейки памяти.

Для вас это в основном не имеет значения. Все, что вам нужно знать, это доступ к элементу массива путем подачи массива и индекса, а неизвестный вам механизм использует массив и индекс для создания элемента массива.

Очевидно, нет необходимости в том, чтобы массив хранил индексы, так как каждый массив в мире с пятью элементами массива имеет индексы 0, 1, 2, 3 и 4. Мы знаем, что это индексы, нет необходимости для их хранения.