Как декларируются и формируются массивы
Перейти к содержимому

Как декларируются и формируются массивы

  • автор:

Массивы

В финальной статье этого раздела, мы познакомимся с массивами — лаконичным способом хранения списка элементов под одним именем. Мы поймём, чем они полезны, затем узнаем, как создать массив, получить, добавить и удалить элементы, хранящиеся в массиве.

Необходимые навыки: Базовая компьютерная грамотность, базовое понимание HTML и CSS, понимание о том, что такое JavaScript.
Цель: Понять, что такое массивы и как использовать их в JavaScript.

Что такое массив?

Массивы обычно описываются как «объекты, подобные спискам»; они представляют собой в основном отдельные объекты, которые содержат несколько значений, хранящихся в списке. Объекты массива могут храниться в переменных и обрабатываться во многом так же, как и любой другой тип значения, причём разница заключается в том, что мы можем получить доступ к каждому значению внутри списка отдельно и делать супер полезные и эффективные вещи со списком, а также делать то же самое для каждого из значений. Представим, что у нас есть список продуктов и их цены, хранящиеся в массиве, и мы хотим их просмотреть и распечатать на счёте-фактуре, общая сумма всех цен и распечатка общей цены внизу.

Если бы у нас не было массивов, мы должны были бы хранить каждый элемент в отдельной переменной, а затем вызывать код, выполняющий печать и добавляющий отдельно каждый элемент. Написание такого кода займёт намного больше времени, сам код будет менее эффективным и подверженным ошибкам. Если бы у нас было 10 элементов для добавления в счёт-фактуру, это ещё куда ни шло, но как насчёт 100 предметов? Или 1000? Мы вернёмся к этому примеру позже в статье.

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

Создание массива

Массивы создаются из квадратных скобок , которые содержат список элементов, разделённых запятыми.

    Допустим, мы бы хотели хранить список покупок в массиве — мы бы сделали что-то вроде этого. Введите следующие строчки в вашу консоль:

var shopping = ["bread", "milk", "cheese", "hummus", "noodles"]; shopping; 
var sequence = [1, 1, 2, 3, 5, 8, 13]; var random = ["tree", 795, [0, 1, 2]]; 

Получение и изменение элементов массива

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

    Введите следующее в вашу консоль:

[0]; // возвращает "bread" 
[0] = "tahini"; shopping; // shopping теперь возвратит [ "tahini", "milk", "cheese", "hummus", "noodles" ] 

Примечание: Мы уже упоминали это прежде, но просто как напоминание — компьютеры начинают считать с нуля!

Нахождение длины массива

Вы можете найти длину массива (количество элементов в нём) точно таким же способом, как вы находите длину строки (в символах) — используя свойство length . Попробуйте следующее:

.length; // должно возвратить 7 

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

var sequence = [1, 1, 2, 3, 5, 8, 13]; for (var i = 0; i  sequence.length; i++)  console.log(sequence[i]); > 

В будущих статьях вы узнаете о циклах, но вкратце этот код говорит:

  1. Начать цикл с номера позиции 0 в массиве.
  2. Остановить цикл на номере элемента, равном длине массива. Это будет работать для массива любой длины, но в этом случае он остановит цикл на элементе номер 7 (это хорошо, поскольку последний элемент, который мы хотим, чтобы цикл был закрыт, равен 6).
  3. Для каждого элемента вернуть его значение в консоли браузера с помощью console.log() .

Некоторые полезные методы массивов

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

Преобразование между строками и массивами

Часто у вас могут быть некоторые необработанные данные, содержащиеся в большой длинной строке, и вы можете захотеть разделить полезные пункты до более удобной и полезной формы, а затем сделать что-то для них, например отобразить их в таблице данных. Для этого мы можем использовать метод split () . В его простейшей форме он принимает единственный параметр, символ, который вы хотите отделить в строке, и возвращает подстроки между разделителем как элементы в массиве.

Примечание: Хорошо, технически это строковый метод, не метод массива, но мы поместили его в массивы, так как он хорошо подходит для них.

  1. Поиграем с этим, посмотрим как это работает. Сначала, создадим строку в вашей консоли:
var myData = "Manchester,London,Liverpool,Birmingham,Leeds,Carlisle"; 
var myArray = myData.split(","); myArray; 
.length; myArray[0]; // первый элемент в массиве myArray[1]; // второй элемент в массиве myArray[myArray.length - 1]; // последний элемент в массиве 
var myNewString = myArray.join(","); myNewString; 
var dogNames = ["Rocket", "Flash", "Bella", "Slugger"]; dogNames.toString(); //Rocket,Flash,Bella,Slugger 

Добавление и удаление элементов массива

Мы ещё не рассмотрели добавление и удаление элементов массива — давайте посмотрим на это сейчас. Мы будем использовать массив myArray , с которым мы столкнулись в предыдущем разделе. Если вы ещё не прошли этот раздел, сначала создайте массив в консоли:

var myArray = [ "Manchester", "London", "Liverpool", "Birmingham", "Leeds", "Carlisle", ]; 

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

    Давайте сначала используем метод push() — заметьте, что вам нужно указать один или более элементов, которые вы хотите добавить в конец своего массива. Попробуйте это:

.push("Cardiff"); myArray; myArray.push("Bradford", "Brighton"); myArray; 
var newLength = myArray.push("Bristol"); myArray; newLength; 
var removedItem = myArray.pop(); myArray; removedItem; 

unshift() и shift() работают точно таким же способом, за исключением того что они работают в начале массива, а не в конце.

    Сначала, попробуем метод unshift() :

.unshift("Edinburgh"); myArray; 
var removedItem = myArray.shift(); myArray; removedItem; 

Практика: Печать продуктов!

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

  1. Ниже комментария // number 1 имеется ряд строк, каждая из которых содержит название продукта и цену, разделённые двоеточием. Нужно превратить их в массив и сохранить его под названием products .
  2. На строке с комментарием // number 2 начинается цикл for. В строке цикла имеется i
  3. Под комментарием // number 3 мы хотим, чтобы вы написали строку кода, которая разбивает текущий элемент массива ( name:price ) на два отдельных элемента: один содержит только имя, а другой — содержащее только цену. Если не знаете, как это сделать, ещё раз просмотрите статью Полезные строковые методы, а лучше, посмотрите раздел Преобразование между строками и массивами этой статьи.
  4. В рамках приведённой выше строки нужно преобразовать цену из строки в число. Если не помните, как это сделать, ознакомьтесь со статьёй строки в JavaScript.
  5. В верхней части кода есть переменная с именем total , которая содержит значение 0 . Внутри цикла (под комментарием // number 4 ) нужно добавить строку, которая добавляет текущую цену товара к этой сумме на каждой итерации цикла, так чтобы в конце кода была выведена корректная сумма в счёт-фактуре. Для этого вам может понадобится оператор присваивания.
  6. Под комментарием // number 5 нужно изменить строку так, чтобы переменная itemText была равна «current item name — $current item price», например «Shoes — $23.99» для каждого случая, чтобы корректная информация для каждого элемента была напечатана в счёте-фактуре. Здесь обычная конкатенация строк, которая должна быть вам знакома.
h2>Live outputh2> div class="output" style="min-height: 150px;"> ul>ul> p>p> div> h2>Editable codeh2> p class="a11y-label"> Press Esc to move focus away from the code area (Tab inserts a tab character). p> textarea id="code" class="playable-code" style="height: 410px;width: 95%"> var list = document.querySelector('.output ul'); var totalBox = document.querySelector('.output p'); var total = 0; list.innerHTML = ''; totalBox.textContent = ''; // number 1 'Underpants:6.99' 'Socks:5.99' 'T-shirt:14.99' 'Trousers:31.99' 'Shoes:23.99'; for (var i = 0; i totalBox.textContent = 'Total: $' + total.toFixed(2); textarea> div class="playable-buttons"> input id="reset" type="button" value="Reset" /> input id="solution" type="button" value="Show solution" /> div> 
var textarea = document.getElementById("code"); var reset = document.getElementById("reset"); var solution = document.getElementById("solution"); var code = textarea.value; var userEntry = textarea.value; function updateCode()  eval(textarea.value); > reset.addEventListener("click", function ()  textarea.value = code; userEntry = textarea.value; solutionEntry = jsSolution; solution.value = "Show solution"; updateCode(); >); solution.addEventListener("click", function ()  if (solution.value === "Show solution")  textarea.value = solutionEntry; solution.value = "Hide solution"; > else  textarea.value = userEntry; solution.value = "Show solution"; > updateCode(); >); var jsSolution = "var list = document.querySelector('.output ul');\nvar totalBox = document.querySelector('.output p');\nvar total = 0;\nlist.innerHTML = '';\ntotalBox.textContent = '';\n\nvar products = ['Underpants:6.99',\n 'Socks:5.99',\n 'T-shirt:14.99',\n 'Trousers:31.99',\n 'Shoes:23.99'];\n\nfor(var i = 0; i < products.length; i++) \n\ntotalBox.textContent = 'Total: $' + total.toFixed(2);"; var solutionEntry = jsSolution; textarea.addEventListener("input", updateCode); window.addEventListener("load", updateCode); // stop tab key tabbing out of textarea and // make it write a tab at the caret position instead textarea.onkeydown = function (e)  if (e.keyCode === 9)  e.preventDefault(); insertAtCaret("\t"); > if (e.keyCode === 27)  textarea.blur(); > >; function insertAtCaret(text)  var scrollPos = textarea.scrollTop; var caretPos = textarea.selectionStart; var front = textarea.value.substring(0, caretPos); var back = textarea.value.substring( textarea.selectionEnd, textarea.value.length, ); textarea.value = front + text + back; caretPos = caretPos + text.length; textarea.selectionStart = caretPos; textarea.selectionEnd = caretPos; textarea.focus(); textarea.scrollTop = scrollPos; > // Update the saved userCode every time the user updates the text area code textarea.onkeyup = function ()  // We only want to save the state when the user code is being shown, // not the solution, so that solution is not saved over the user code if (solution.value === "Show solution")  userEntry = textarea.value; > else  solutionEntry = textarea.value; > updateCode(); >; 
html  font-family: sans-serif; > h2  font-size: 16px; > .a11y-label  margin: 0; text-align: right; font-size: 0.7rem; width: 98%; > body  margin: 10px; background-color: #f5f9fa; > 

Практика: Топ 5 поисковых запросов

Хорошим тоном, является использование методов массива, таких как push () и pop () — это когда вы ведёте запись активных элементов в веб-приложении. Например, в анимированной сцене может быть массив объектов, представляющих текущую отображаемую фоновую графику и вам может потребоваться только 50 одновременных отображений по причинам производительности или беспорядка. Когда новые объекты создаются и добавляются в массив, более старые могут быть удалены из массива для поддержания нужного числа.

В этом примере мы собираемся показать гораздо более простое использование — ниже мы даём вам поддельный поисковый сайт с полем поиска. Идея заключается в том, что когда в поле поиска вводятся запросы, в списке отображаются 5 предыдущих поисковых запросов. Когда число терминов превышает 5, последний член начинает удаляться каждый раз, когда новый член добавляется в начало, поэтому всегда отображаются 5 предыдущих терминов.

Примечание: В реальном приложении для поиска вы, вероятно, сможете щёлкнуть предыдущие условия поиска, чтобы вернуться к предыдущим поисковым запросам и отобразите фактические результаты поиска! На данный момент мы просто сохраняем его.

Чтобы завершить приложение, вам необходимо:

  1. Добавьте строку под комментарием // number 1 , которая добавляет текущее значение, введённое в ввод поиска, к началу массива. Его можно получить с помощью searchInput.value .
  2. Добавьте строку под комментарием // number 2 , которая удаляет значение, находящееся в конце массива.
h2>Live outputh2> div class="output" style="min-height: 150px;"> input type="text" />button>Searchbutton> ul>ul> div> h2>Editable codeh2> p class="a11y-label"> Press Esc to move focus away from the code area (Tab inserts a tab character). p> textarea id="code" class="playable-code" style="height: 370px; width: 95%"> var list = document.querySelector('.output ul'); var searchInput = document.querySelector('.output input'); var searchBtn = document.querySelector('.output button'); list.innerHTML = ''; var myHistory = []; searchBtn.onclick = function() < // we will only allow a term to be entered if the search input isn't empty if (searchInput.value !== '') < // number 1 // empty the list so that we don't display duplicate entries // the display is regenerated every time a search term is entered. list.innerHTML = ''; // loop through the array, and display all the search terms in the list for (var i = 0; i < myHistory.length; i++) < itemText = myHistory[i]; var listItem = document.createElement('li'); listItem.textContent = itemText; list.appendChild(listItem); >// If the array length is 5 or more, remove the oldest search term if (myHistory.length >= 5) < // number 2 >// empty the search input and focus it, ready for the next term to be entered searchInput.value = ''; searchInput.focus(); > > textarea> div class="playable-buttons"> input id="reset" type="button" value="Reset" /> input id="solution" type="button" value="Show solution" /> div> 
html  font-family: sans-serif; > h2  font-size: 16px; > .a11y-label  margin: 0; text-align: right; font-size: 0.7rem; width: 98%; > body  margin: 10px; background: #f5f9fa; > 
var textarea = document.getElementById("code"); var reset = document.getElementById("reset"); var solution = document.getElementById("solution"); var code = textarea.value; var userEntry = textarea.value; function updateCode()  eval(textarea.value); > reset.addEventListener("click", function ()  textarea.value = code; userEntry = textarea.value; solutionEntry = jsSolution; solution.value = "Show solution"; updateCode(); >); solution.addEventListener("click", function ()  if (solution.value === "Show solution")  textarea.value = solutionEntry; solution.value = "Hide solution"; > else  textarea.value = userEntry; solution.value = "Show solution"; > updateCode(); >); var jsSolution = "var list = document.querySelector('.output ul');\nvar searchInput = document.querySelector('.output input');\nvar searchBtn = document.querySelector('.output button');\n\nlist.innerHTML = '';\n\nvar myHistory= [];\n\nsearchBtn.onclick = function() \n\n if(myHistory.length >= 5) \n\n searchInput.value = '';\n searchInput.focus();\n >\n>"; var solutionEntry = jsSolution; textarea.addEventListener("input", updateCode); window.addEventListener("load", updateCode); // stop tab key tabbing out of textarea and // make it write a tab at the caret position instead textarea.onkeydown = function (e)  if (e.keyCode === 9)  e.preventDefault(); insertAtCaret("\t"); > if (e.keyCode === 27)  textarea.blur(); > >; function insertAtCaret(text)  var scrollPos = textarea.scrollTop; var caretPos = textarea.selectionStart; var front = textarea.value.substring(0, caretPos); var back = textarea.value.substring( textarea.selectionEnd, textarea.value.length, ); textarea.value = front + text + back; caretPos = caretPos + text.length; textarea.selectionStart = caretPos; textarea.selectionEnd = caretPos; textarea.focus(); textarea.scrollTop = scrollPos; > // Update the saved userCode every time the user updates the text area code textarea.onkeyup = function ()  // We only want to save the state when the user code is being shown, // not the solution, so that solution is not saved over the user code if (solution.value === "Show solution")  userEntry = textarea.value; > else  solutionEntry = textarea.value; > updateCode(); >; 

Заключение

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

Осталось только выполнить тестовую задачу, которая проверит ваше понимание статей, которые вы прочли до этого момента. Удачи!

Посмотрите также

  • Indexed collections — an advanced level guide to arrays and their cousins, typed arrays.
  • Array — the Array object reference page — for a detailed reference guide to the features discussed in this page, and many more.
  • Назад
  • Обзор: Первые шаги в JavaScript
  • Далее

В этом разделе

  • Что такое JavaScript?
  • A first splash into JavaScript
  • What went wrong? Troubleshooting JavaScript
  • Storing the information you need — Variables
  • Basic math in JavaScript — numbers and operators
  • Handling text — strings in JavaScript
  • Useful string methods
  • Массивы
  • Assessment: Silly story generator

Знаете ли Вы массивы?

Думаю, мало кто из готовящихся к своему первому интервью, при приеме на первую работу в должности (pre)junior программиста, ответит на этот вопрос отрицательно. Или хотя бы усомнится в положительном ответе. Конечно, такая простая структура данных с прямым доступом по индексу — никаких подвохов! Нет, в некоторых языках типа JavaScript или PHP массивы, конечно, реализованы очень интересно и по сути являются много большим чем просто массив. Но речь не об этом, а о «традиционной» реализации массивов в виде «сплошного участка памяти». В этом случае на основании индексов и размера одного элемента просто вычисляется адрес и осуществляется доступ к соответствующему значению. Что тут сложного?
Давайте разберемся. Например, на Java. Просим ничего не подозревающего претендента создать массив целых чисел n x n. Человек уверено пишет что-то в духе:

int g[][] = new int[n][n]; 

Отлично. Теперь просим инициализировать элементы массива чем-нибудь. Хоть единицами, хоть суммой индексов. Получаем:

for(int i = 0; i < n; i++) < for(int j = 0; j < n; j++) < g[i][j] = i + j; >> 

Даже чаще пишут

for(int i = 0; i < g.length; i++) < for(int j = 0; j < g[i].length; j++) < g[i][j] = i + j; >> 

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

for(int i = 0; i < n; i++) < g[i][i] = 2* i; for(int j = 0; j < i; j++) < g[j][i] = g[i][j] = i + j; >> 
g[i][i] = 2* i;

часто пишут

g[i][i] = i + i;
g[i][i] = i 

и это тоже повод поговорить. Но мы идем дальше и задаем ключевой вопрос: На сколько быстрее станет работать программа?. Обычные рассуждения такие: почти в 2 раза меньше вычислений индексов; почти в 2 раза меньше вычислений значений (суммирование); столько же присваиваний. Значит быстрее процентов на 30. Если у человека за плечами хорошая математическая школа, то можно даже увидеть точное количество сэкономленных операций и более аргументированную оценку эффективности оптимизации.
Теперь самое время для главного удара. Запускаем оба варианта кода на каком-нибудь достаточно большом значении n (порядка нескольких тысяч), например, так.

Код с контролем времени

class A < public static void main(String[] args) < int n = 8000; int g[][] = new int[n][n]; long st, en; // one st = System.nanoTime(); for(int i = 0; i < n; i++) < for(int j = 0; j < n; j++) < g[i][j] = i + j; >> en = System.nanoTime(); System.out.println("\nOne time " + (en - st)/1000000.d + " msc"); // two st = System.nanoTime(); for(int i = 0; i < n; i++) < g[i][i] = i + i; for(int j = 0; j < i; j++) < g[j][i] = g[i][j] = i + j; >> en = System.nanoTime(); System.out.println("\nTwo time " + (en - st)/1000000.d + " msc"); > > 

Что же мы видим? Оптимизированный вариант работает в 10-100 раз медленнее! Теперь самое время понаблюдать за реакцией претендента на должность. Какая будет реакция на необычную (точнее обычную в практике разработчика) стрессовую ситуацию. Если на лице подзащитного изобразился азарт и он стал жать на кнопочки временно забыв о Вашем существовании, то это хороший признак. До определенной степени. Вы ведь не хотите взять на работу исследователя, которому плевать на результат проекта? Тогда не задавайте ему вопрос «Почему?». Попросите переделать второй вариант так, чтобы он действительно работал быстрее первого.
Теперь можно смело заниматься некоторое время своими делами. Через пол часа у Вас будет достаточно материала, для того, чтобы оценить основные личностные и профессиональные качества претендента.
Кстати, когда я коротко описал эту задачку на своем рабочем сайте, то наиболее популярный комментарий был «Вот такая эта Ваша Java кривая». Специально для них выкладываю код на Великом и Свободном. А счастливые обладатели Free Pascal под Windows могут заглянуть

под спойлер

program Time; uses Windows; var start, finish, res: int64; n, i, j: Integer; g: Array of Array of Integer; begin n := 10000; SetLength(g, n, n); QueryPerformanceFrequency(res); QueryPerformanceCounter(start); for i:=1 to n-1 do for j:=1 to n-1 do g[i,j] := i + j; QueryPerformanceCounter(finish); writeln('Time by rows:', (finish - start) / res, ' sec' ); QueryPerformanceCounter(start); for i:=1 to n-1 do for j:=1 to n-1 do g[j,i] := i + j; QueryPerformanceCounter(finish); writeln('Time by cols:', (finish - start) / res, ' sec' ); end. 

В приведенном коде на Паскале я убрал «запутывающие» моменты и оставил только суть проблемы. Если это можно назвать проблемой.
Какие мы в итоге получаем вопросы к подзащитному?
1. Почему стало работать медленнее? И поподробнее…
2. Как сделать инициализацию быстрее?

Если есть необходимость копнуть глубже именно в реализацию Java, то просим соискателя понаблюдать за временем выполнения для небольших значений n. Например, на ideone.com для n=117 «оптимизированный» вариант работает вдвое медленнее. Но для следующего значения n=118 он оказывается уже в 100 (сто) раз быстрее не оптимизированного! Предложите поэкспериментировать на локальной машине. Пусть поиграет с настройками.
Кстати, а всем понятно, что происходит?

Несколько слов в оправдание

Хочу сказать несколько слов в оправдание такого способа собеседования при найме. Да, я не проверяю знание синтаксиса языка и владение структурами данных. Возможно, при цивилизованном рынке труда это все работает. Но в наших условиях тотальной нехватки квалифицированных кадров, приходится оценивать скорее перспективную адекватность претендента той работе с которой он столкнется. Т.е. способность научиться, прорваться, разобраться, сделать.
По духу это похоже на «собеседованию» при наборе легионеров в древнем Риме. Будущего вояку сильно пугали и смотрели краснеет он или бледнеет. Если бледнеет, то в стрессовой ситуации у претендента кровь отливает от головы и он склонен к пассивной реакции. Например, упасть в обморок. Если же соискатель краснел, то кровь у него к голове приливает. Т.е. он склонен к активным действиям, бросаться в драку. Такой считался годным.
Ну и последнее. Почему я рассказал об этой задаче всем, а не продолжаю использовать её на собеседованиях? Просто, эту задачу уже «выучили» потенциальные соискатели и приходится использовать другие.
Собственно на этот эффект я обратил внимание именно в связи с реальной задачей обработки изображений. Ситуация была несколько запутанная и я не сразу понял почему у меня так просел fps после рефакторинга. А вообще таких чуднЫх моментов наверное много накопилось у каждого.

Пока лидирует версия, что «виноват» кэш процессора. Т.е. последовательный доступ в первом варианте работает в пределах хэша, который обновляется при переходе за определенную границу. При доступе по столбцам хэш вынужден постоянно обновляться и это занимает много времени. Давайте проверим эту версию в самом чистом виде. Заведем массив и сравним, что быстрее — обработать все элементы подряд или столько же раз обработать элементы массива со случайным номером? Вот эта программа — ideone.com/tMaR2S. Для 100000 элементов массива случайный доступ обычно оказывается заметно быстрее. Что же это означает?
Тут мне совершенно справедливо указали (Big_Lebowski), что перестановка циклов меняет результаты в пользу последовательного варианта. Пришлось для чистоты эксперимента поставить цикл для разогрева. Заодно сделал несколько повторов, чтобы вывести среднее время работы как советовал leventov. Получилось так ideone.com/yN1H4g. Т.е. случайный доступ к элементам большого массива на ~10% медленнее чем последовательный. Возможно и в правду какую-то роль может сыграть кэш. Однако, в исходной ситуации производительность проседала в разы. Значит есть еще что-то.

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

Объявление массивов

Массивы объявляются так же, как и другие переменные, при помощи операторов Dim, Static, Private или Public Отличие скалярных переменных (которые не являются массивами) от переменных массивов заключается в том, что для массива, как правило, необходимо указывать размер. Массив с указанным размером является массивом фиксированного размера. Массив, размер которого можно изменить во время выполнения программы, является динамическим массивом.

Индексация массива от 0 или 1 зависит от оператора Option Base. Если не указано Option Base 1, все индексы массива будут начинается с нуля.

Объявление статического массива

В приведенном ниже примере кода массив фиксированного размера объявлен массивом целых переменных (Integer) с 11 строками и 11 столбцами:

Dim MyArray(10, 10) As Integer 

Первый аргумент определяет количество строк, второй — столбцов.

Как и в случае объявления любой другой переменной, если для объявленного массива не указать тип данных, его элементам будет присвоен тип данных Variant. Каждый числовой элемент Variant массива использует 16 байтов. Каждый строчный элемент Variant использует 22 байта. Чтобы написать как можно более компактный код, четко объявите для своих массивов тип данных, отличный от Variant.

В приведенном ниже примере кода сравниваются размеры нескольких массивов.

' Integer array uses 22 bytes (11 elements * 2 bytes). ReDim MyIntegerArray(10) As Integer ' Double-precision array uses 88 bytes (11 elements * 8 bytes). ReDim MyDoubleArray(10) As Double ' Variant array uses at least 176 bytes (11 elements * 16 bytes). ReDim MyVariantArray(10) ' Integer array uses 100 * 100 * 2 bytes (20,000 bytes). ReDim MyIntegerArray (99, 99) As Integer ' Double-precision array uses 100 * 100 * 8 bytes (80,000 bytes). ReDim MyDoubleArray (99, 99) As Double ' Variant array uses at least 160,000 bytes (100 * 100 * 16 bytes). ReDim MyVariantArray(99, 99) 

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

Объявление динамического массива

Объявив динамический массив, вы сможете менять его размер во время выполнения кода. Используйте операторы Static, Dim, Private или Public, чтобы объявить массив, не указывая значение в скобках, как показано в следующем примере:

Dim sngArray() As Single 

Используйте оператор ReDim, чтобы неявно объявить массив в процедуре. Будьте внимательны и вводите имя массива без ошибок при использовании оператора ReDim. Даже если в модуль включен оператор Option Explicit, будет создан второй массив.

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

Например, приведенный ниже оператор увеличивает массив на 10 элементов, сохраняя при этом текущие значения исходных элементов.

ReDim Preserve varArray(UBound(varArray) + 10) 

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

См. также

Поддержка и обратная связь

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

Обратная связь

Были ли сведения на этой странице полезными?

Обратная связь

Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделе https://aka.ms/ContentUserFeedback.

Отправить и просмотреть отзыв по

Исчерпывающее руководство по одномерным массивам в Java

  1. Объявление переменной массива
  2. Создание массива
  3. Тип данных массива
  4. Длина массива
  5. Инициализация массива 5.1. Сокращенная форма создания и инициализации 5.2. Ручная инициализация по индексу 5.3. Инициализация и доступ к элементам в цикле
  6. Цикл for-each
  7. Удаление элементов из массива
  8. Скорость работы массива

Введение

Для хранения данных, используемых при работе программы, применяются переменные того или иного типа. Например, если требуется сохранить имя игрока, то создается переменная String name, если целочисленное значение, то int number и т. д. Когда таких значений немного или их количество заранее известно, то для них приемлемо использовать отдельные переменные.

Но что делать, когда в программе задействовано не одно, а 10 чисел, или их количество становится известно только на этапе запуска приложения, когда пользователь вводит его с клавиатуры? Или в какой-то игре может участвовать разное количество игроков, устанавливаемое при ее старте. В таких ситуациях отдельными переменными уже не обойтись, т. к. их точное количество будет невозможно определить. Да и, если все же попытаться это сделать, код станет громоздким и не универсальным, а работа с ним будет крайне ограниченной из-за невозможности использования цикла для автоматизированной обработки данных.

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

Дадим всестороннее определение массиву.

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

На картинке ниже схематично изображен массив размерностью 10, заполненный числами, а также индексы ячеек:

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

Далее мы подробно и обстоятельно рассмотрим массив и его устройство.

1. Объявление переменной массива

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

Пример объявления таких переменных:

double[] numbers; char[] letters; int[] ids;

Такие переменные в Java объявляются точно так же, как любые другие (у них есть тип данных и имя). Отличие лишь в наличии квадратных скобок [], размещаемых после типа хранимых в массиве данных (при этом перед [] пробел не ставится). Наличие одной пары скобок является специальным маркером, указывающим на то, что переменная предназначена для одномерного массива (существуют и другие массивы: двумерные и многомерные, но о них не пойдет речь в данной статье).

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

Все возможные варианты оформления:

int[] array; — общепринятый Java-стиль int [] array; int array[]; int array [];

По историческим причинам (наследство от языка С/С++) Java позволяет размещать [] в разных местах, но это не значит, что нужно использовать какой-то иной стиль, кроме устоявшегося (или используемого в конкретной компании), даже если вы увидите подобный код у кого-то в интернете или книге. Всегда придерживайтесь одного стиля, не смешивайте разные способы написания в одной программе.

Т. к. массив всегда хранит множество значений, то для именования переменной используется:

  • существительное во множественном числе, например, cats, cars, resumes, playerAttempts
  • местоимение, например, allNumbers, myBooks
  • прилагательное, например, physicalConstants

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

Еще парочка примеров:

String[] fullNames; Player[] players; short[] multipliers;

Массив может хранить не только значения примитивных, но и ссылочных типов. И сам он тоже является ссылочным типом данных. В первой строке объявляется переменная массива строк, во второй — массива игроков, а в третьей — переменная для хранения примитивных целых значений.

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

2. Создание массива

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

int[] array = new int[10];

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

  • оператор new выделяет в памяти (heap, куче) место под хранение десяти значений типа int
  • затем он возвращает ссылку (будет храниться в стеке) на начало области памяти (совпадает с первым элементом), выделенной под массив
  • ссылка помещается в переменную array того же типа, что и массив

На картинке схематично в памяти изображен массив int размерностью 10, заполненный числами:

Следует помнить, что после создания массива его размер (длину) изменить нельзя.

Еще пример, но уже не с примитивами, а со ссылочным типом String:

String[] strings = new String[4]; // это то же самое, что и String[] strings; strings = new String[4];

Первый вариант предпочтительней, т. к. не стоит без явной причины объявлять переменную и создавать массив на разных строках — это и больше кода, и подобная запись всегда вызовет у опытного программиста недоумение: «А нафига так было делать, если можно в одну строку?».

3. Тип данных массива

Как написано в документации, массив является объектом: «An object is a class instance or an array». Но наблюдательный читатель, возможно, заметил, что хоть он и является таковым, но при этом не имеет класса, который описывал бы его. Мы просто используем массив, как есть. Но это «как есть» хранит в себе множество скрытых процессов и тайн, которые мы по возможности приоткроем.

Хоть тип массива и не является классом, например int[], но все же имеет ассоциируемый с ним класс, который автоматически создает виртуальная машина (Java Virtual Machine, JVM). При этом данный класс неявно наследуется от java.lang.Object, что делает доступным все его методы и интерфейсы Cloneable и Serializable. Но у программиста нет доступа к его коду, его нельзя посмотреть глазами.

Ассоциируемые классы создаются для каждого типа хранимой в массиве информации, например, для boolean[], или Player[], или String[] и т. д. И мы можем узнать, как эти классы называются. Они-то и являются реальными типами массивов. Чтобы увидеть их названия, необходимо воспользоваться Java-магией (рефлексией), которая позволит узнать настоящий тип массива, его суперкласс (от которого он наследуется) и список доступных методов.

Создадим массив нулевой длины (пустой) и отобразим нужную нам информацию:

import java.lang.reflect.Method; public class Test < public static void main(String[] args) < int[] array = <>; System.out.println("Тип массива - " + array.getClass()); System.out.println("Суперкласс типа массива - " + array.getClass().getSuperclass()); System.out.println("Доступные методы:"); for (Method m : array.getClass().getMethods()) < System.out.println(m.getName()); >> >
Тип массива - class [I Суперкласс типа массива - class java.lang.Object Доступные методы: equals toString hashCode getClass notify notifyAll wait wait wait

Метод clone() тоже доступен, но не отображается в списке, т. к. getMethods() возвращает только public-методы. А clone() имеет модификатор protected.

Что нам вывел код:

  • [I — это сигнатура типа (класса, который JVM создает во время выполнения) для объекта «массив с элементами типа int». Это и есть настоящий тип данных массива, как объекта
  • [ говорит, что это одномерный массив
  • I, что он содержит целые числа
  • Имя суперкласса, записанного с помощью его полного имени — java.lang.Object

Все возможные сигнатуры типов имеют следующий формат:

[B - byte [C - char [D - double [F - float [I - int [J - long [S - short [Z - boolean [L - любой объект

Можно сделать вывод, что тип массива и тип хранимых им значений — это формально разные типы. В нашем примере типом массива является тип [I, а типом хранимых значений — int.

Разберем еще одно доказательство того, что Object является суперклассом для массивов:

public class Test < public static void main(String[] args) < Object obj = new int[]; int[] array = (int[]) obj; System.out.println(obj); System.out.println(array); > >

В четвертой строке объявляется и инициализируется массив, ссылка на который присваивается переменной типа Object. И при этом никакой ошибки не возникнет. В пятой строке происходит приведение Object к int[]. В итоге обе переменные (их ссылки) указывают на один и тот же массив.

Написанный выше код отобразит следующий результат:

[I@1d81eb93 [I@1d81eb93

Как известно, если попытаться вывести, например, в println значение объекта, просто записав его имя в метод в качестве аргумента, то неявно будет вызван метод toString(). А так как массивы его не переопределяют, т. е. у них нет своей реализации этого метода, то используется реализация по умолчанию, записанная в классе Object.

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

getClass().getName() + '@' + Integer.toHexString(hashCode())

В итоге она состоит из имени класса, в нашем случае — [I, разделителя в виде @ и шестнадцатеричного представления хеш-кода объекта. Для человека эта информация не несет никакой полезной нагрузки. Решение этой проблемы обсуждается в другой статье.

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

И на закуску немного сумасбродства и неочевидных вещей — создадим пустой массив с нулевой длиной:

String[] array1 = <>; char[] array2 = new char[0];

Как вы думаете, как в памяти представляются эти массивы?

Массив с нулевым размером представляет собой объект, у которого есть заголовок объекта (Object header) и размер, но нет места в памяти, выделенного для его элементов. При этом ссылка на такой массив является рабочей и сохраняется в переменную.

Массив нулевой длины не равен массиву, ссылающемуся на null:

// это не одно и то же String[] array1 = null; String[] array2 = <>;

null — это указатель в никуда, который является маркером отсутствия указателя на объект.

4. Длина массива

В спецификации написано: «Поле length является public final (общедоступным и неизменяемым) и содержит в себе количество элементов массива (длина может быть положительной или нулевой)».

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

Вызов этого поля часто путают с вызовом метода length() получения длины String. Но length — не метод, а поле. Доступ к нему можно получить напрямую, поскольку оно является общедоступным и неизменяемым (хотя тут, возможно, что это просто плохое архитектурное решение из далекого прошлого).

Т. к. данное поле является частью объекта и не меняется, то это позволяет JVM не пересчитывать количество элементов массива всякий раз, когда в коде используется его длина — она просто берет его из заголовка объекта, когда это требуется.

Чтобы вас окончательно запутать, сошлюсь на документацию, в которой написано: «Длина массива не является частью его типа». Значит length не хранится в ассоциируемом классе!

«Да, это какой-то вынос мозга», — скажете вы: класса нет, но массив — это объект. При этом он наследуется от Object. А length — вроде бы, поле, но не совсем.

А где тогда хранится длина массива?

Она размещается в заголовке объекта. Что такое заголовок? Это часть любого объекта, содержащая метаинформацию о нем. Давайте взглянем на этот заголовок, используя утилиту Java Object Layout (JOL).

Чтобы программа ниже у вас заработала, необходимо в IDEA нажать Ctrl + Alt + Shift + S, выбрать Modules, а затем в Dependencies нажать + → Library… → New Library. → From Maven… В строке поиска ввести org.openjdk.jol.

import org.openjdk.jol.info.ClassLayout; public class Test < public static void main(String[] args) throws Exception < System.out.println(ClassLayout.parseInstance(new int[10]).toPrintable()); >>

В итоге на консоль будет выведена следующая метаинформация из заголовка объекта:

[I object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0x00002628 12 4 (array length) 10 16 40 int [I. N/A Instance size: 56 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Из всего вывода нас интересует строка: 12 4 (array length) 10.

Это и есть длина созданного массива new int[10], которая имеет размер 4 байта. Поле length является особенным в том смысле, что оно находится в заголовке объекта массива, а для получения его длины используется специальный байт-код.

Больше о заголовке объекта можно узнать в видео Алексея Шипилева.

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

public class Test < public static void main(String[] args) < int[] array = new int[5]; int len = array.length; >>

Отобразим байт-код этого класса с помощью javap:

javap -c Test.class Code: 0: iconst_5 1: newarray int 3: astore_1 4: aload_1 5: arraylength 6: istore_2 7: return

Нас в нем интересуют две строки: newarray int, которая создает массив int-ов и arraylength, которая получает длину массива. Таким образом, доступ к нему не осуществляется, как если бы это было обычное поле. Этого поля просто не существует в типе массива. Для работы с ним у JVM есть отдельная инструкция, которая берет его из заголовка объекта.

5. Инициализация массива

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

5.1 Сокращенная форма создания и инициализации

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

String[] names = new String[]; // та же запись, но короче String[] names = ;

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

Глядя на примеры выше следует отметить, что в Java в массивах хранятся не сами объекты, а ссылки на них, которые, в свою очередь, указывают на место в куче (heap), где находится соответствующий объект.

Схематично это можно изобразить так:

float[] numbers = ;
Resume[] resumes = ;

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

5.2 Ручная инициализация по индексу

Элементы массива начинают храниться с нулевого индекса: самая первая ячейка находится под индексом 0, а не 1. Это может показаться непривычным, но на то есть причина.

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

Адрес любой ячейки массива рассчитывается по следующей формуле:

адрес начала массива + индекс * количество памяти, выделенное на один элемент

Например, есть массив int[] array = ;

Предположим, что array хранит адрес 0xFF00. Тогда ячейки массива будут иметь следующие адреса (с учетом того, что на каждое значение int выделяется 4 байта):

array[0] → 0xFF00; array[1] → 0xFF04; array[2] → 0xFF08;

Более научное объяснение того, почему индексация начинается с 0, а не с 1, можно прочитать в следующей статье.

С индексацией разобрались. Скажем теперь пару слов про то, чему равно количество индексов в массиве. Оно всегда равно length - 1. Это связано с тем, что размер массива (его длина) всегда больше на единицу самого последнего индекса из-за того, что индексация начинается с 0.

Количественно размер массива и число индексов равно, но в числовом представлении оно отличается на 1 в меньшую сторону, если считать индексы по порядку. Таким образом, в приведенном ниже примере с массивом из 2 элементов индексы идут от 0 до 1, а длина массива равна 2.

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

public class Test < public static void main(String[] args) < int[] numbers = new int[2]; numbers[0] = 3; numbers[1] = 7; int a = numbers[0]; int b = numbers[1]; System.out.println(a); System.out.println(b); // или можно сразу вывести значения без создания переменных System.out.println("Под индексом 0 хранится значение " + numbers[0]); System.out.println("Под индексом 1 хранится значение " + numbers[1]); >>
3 7 Под индексом 0 хранится значение 3 Под индексом 1 хранится значение 7

В этом примере в первом случае сначала в ячейки массива под индексами 0 и 1 сохраняются числа 3 и 7. Затем они же считываются из него и размещаются в переменные a и b. Далее их значения выводятся на консоль.

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

В консоли в обоих случаях отобразятся числа 3 и 7, взятые из массива numbers по индексам 0 и 1.

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

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

Приведем для примера «странный» код:

System.out.println((new int[])[1]);

В итоге в консоли отобразится число 6, находящееся под индексом 1.

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

5.3 Инициализация и доступ к элементам в цикле

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

При использовании цикла необходимо знать следующие параметры:

  • начальное значение индекса (откуда стартует итерация)
  • значение, ограничивающее количество итераций (обычно используется длина массива или 0 — если итерация стартует с конца массива)
  • конечное значение индекса (для завершения числа итераций)

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

В качестве типа данных индекса массива можно использовать int, short, byte или char. Использование типа long приведет к ошибке компиляции, т. к. индексы массива в Java преобразуются в int-значения (4 байта). А т. к. под long выделяется больше памяти (8 байт), то преобразование к int потенциально может привести к потерям данных, о чем и сообщит компилятор.

Приведем пример такого кода:

public class Test < public static void main(String[] args) < int[] ints = new int[5]; for (long i = 0L; i < ints.length; i++) < System.out.println(ints[i]); >> >
java: incompatible types: possible lossy conversion from long to int

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

System.out.println(ints[(int) i]);

Закончив это небольшое отступление, продолжим разбираться с циклом for.

Начальное значение индекса — это значение, с которого будет начинаться доступ к ячейкам массива. Обычно начинают с самого начала, т. е. с 0 (с первой ячейки). Но не обязательно. Есть ряд задач, в которых удобнее начинать с конца (длина массива - 1).

Значение, ограничивающее количество итераций, позволяет не выйти за правую границу массива, когда индекс >= длине массива. Если цикл реализован так, что проход по массиву осуществляется с конца к началу, то тут все наоборот: длина - 1 будет началом, а условие index >= 0 будет защищать от выхода за его левую границу.

Цикл прерывается, когда конечное значение индекса совпадает с его длиной или оно < 0.

Пример работы циклов по массиву слева направо:

public class Test < public static void main(String[] args) < String[] strings = new String[4]; for (int i = 0; i < strings.length; i++) < strings[i] = "Строка по индексу " + i; >for (int i = 0; i < strings.length; i++) < System.out.println(strings[i]); >> >
Строка по индексу 0 Строка по индексу 1 Строка по индексу 2 Строка по индексу 3

В этом примере создается массив строк размерностью 4.

В первом цикле в массив сохраняется сконкатенированный (склеенный) текст с номерами индексов.

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

В обоих циклах переменная i используется в качестве счетчика для обозначения индекса. Она имеет начальное значение 0, которое в процессе работы циклов меняется на единицу (инкрементируется) после каждой итерации (одного прохода цикла). Как только условие i < strings.lengthперестает выполняться, т. е. проверка вернет false, то цикл прекращается.

Вы можете спросить, почему я использовал идентификатор «i»? Разве это не «плохое» имя для переменной? Да, обычно для имен переменных выбираются имена, глядя на которые сразу бывает понятно, что они хранят. Но в данном случае действует негласное соглашение, о котором можно прочитать тут.

Если в двух словах, то счетчики циклов принято именовать с помощью i, j и k, где i — находится во внешнем цикле, j — во вложенном в него цикле и т. д.

Считается, что этот стиль возник в языке программирования Фортран, где имена переменных, начинающиеся с букв I по Q, по умолчанию считались целыми, остальные были действительными. А еще раньше это появилось в математике, где индексы для сумм и умножений именуются как i, j, k.

Приведем еще один пример работы с массивом в цикле, но уже в обратном порядке:

public class Test < public static void main(String[] args) < char[] abc = new char[26]; int i = 0; for (char ch = 'Z'; ch >= 'A'; ch--) < abc[i++] = ch; >for (i = abc.length - 1; i >= 0; i--) < System.out.print(abc[i]); >> >

В первом цикле массив abc заполняется буквами от Z до A. Он работает до тех пор, пока выполняется условие ch >= 'А'. Во втором цикле все буквы выводятся на консоль:

ABCDEFGHIJKLMNOPQRSTUVWXYZ

Что будет, если вывести массив без инициализации?

Проверим эту идею, создав массивы размерностью 1 всех примитивных типов и String. Отобразим их значения в консоль без инициализации:

public class Test < public static void main(String[] args) < System.out.println("Значение по умолчанию для byte " + (new byte[1])[0]); System.out.println("Значение по умолчанию для short " + (new short[1])[0]); System.out.println("Значение по умолчанию для int " + (new int[1])[0]); System.out.println("Значение по умолчанию для long " + (new long[1])[0]); System.out.println("Значение по умолчанию для float " + (new float[1])[0]); System.out.println("Значение по умолчанию для double " + (new double[1])[0]); System.out.println("Значение по умолчанию для char " + (new char[1])[0]); System.out.println("Значение по умолчанию для boolean " + (new boolean[1])[0]); System.out.println("Значение по умолчанию для String " + (new String[1])[0]); >>
Значение по умолчанию для byte 0 Значение по умолчанию для short 0 Значение по умолчанию для int 0 Значение по умолчанию для long 0 Значение по умолчанию для float 0.0 Значение по умолчанию для double 0.0 Значение по умолчанию для char Значение по умолчанию для boolean false Значение по умолчанию для String null

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

Значение переменной char не отображается (не имеет никакого представления), т. к. соответствует коду '\u0000', являющимся пустым символом.

Известно, что локальные переменные по умолчанию не инициализируются, но с массивом, как мы видим, все иначе. Спецификация (1, 2) языка требует его инициализации значениями по умолчанию, что фактически мы и наблюдали в примере.

Что будет, если выйти за границы массива?

За границу массива мы можем выйти с двух сторон: слева (когда индекс отрицательный) и справа (когда он >= длине массива). В обоих случаях произойдет ошибка (исключительная ситуация) и вылетит исключение java.lang.ArrayIndexOutOfBoundsException.

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

public class Test < public static void main(String[] args) < int[] numbers = new int[4]; for (int i = 0; i > >
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4 at Test.main(Test.java:7)

Из-за того, что условие i при i = 4 вернуло true, в строке nums[i] = i + 1; возникла ошибка связанная с тем, что в массиве нет ячейки под индексом 4. Ситуации nums[4] = 4 + 1 быть не должно.

public class Test < public static void main(String[] args) < String[] letters = ; for (int i = letters.length - 1; i >= 0; i--) < System.out.println(letters[i - 1]); >> >
B A Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3 at Test.main(Test.java:7)

В коде ошибка произошла из-за того, что в какой-то момент в строке str[i — 1] индекс стал равен str[0 — 1]. Информация в консоли сообщает, что индекс не может быть -1, т. к. он вне пределов длины 3.

6. Цикл for-each

Для работы с массивами цикл for является более предпочтительным, чем while, но и он не идеален, т. к. имеет массу мест, где можно совершить ошибку: не так инициализировать счетчик, неверно написать условие работы цикла, перепутать название счетчика и т. д.

Чтобы как-то минимизировать количество ошибок, упростить работу с массивами и коллекциями (далее будут упоминаться только массивы) и сделать код более лаконичным, начиная с JDK 5, была внедрена упрощенная версия цикла for под названием for-each.

Код, записанный в традиционном стиле:

public class Test < public static void main(String[] args) < int[] array = ; for (int i = 0; i < array.length; i++) < System.out.print(array[i]); >> >

Тот же самый код, но с использованием for-each:

public class Test < public static void main(String[] args) < int[] array = ; for (int a : array) < System.out.print(a); >> >

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

Данный вид цикла является так называемым «синтаксическим сахаром», который отличается от традиционного цикла for только внешне, но для JVM в них нет никаких различий.

  • отсутствие счетчика индекса — все ячейки перебираются по порядку, начиная с нулевой
  • отсутствие условия работы цикла — это действие выполняется автоматически и скрыто от программиста

Цикл for-each позволяет получить доступ к элементам массива, но не дает возможности использовать индексы (к ним банально нет доступа) или обращаться к значениям в отличном от последовательного доступа порядке. Кроме того, он не позволяет менять или удалять значения массива. В основном, его используют для вывода элементов массива на консоль. Если вам нужно больше возможностей, то используйте обычный цикл for.

Цикл for-each используется, когда:

  • необходимо вывести значения массива по порядку
  • не нужно изменять массив
  • не нужны индексы

Еще пример с for-each:

public class Test < public static void main(String[] args) < char[] chars = ; for (char c : chars) < System.out.print(c); >> >

Цикл for-each для работы с массивами предоставляет массу преимуществ и удобств, нежели for и, тем более, while и do-while. Старайтесь его использовать везде, где это возможно.

7. Удаление элементов из массива

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

Под удалением не имеется в виду, что массив станет меньше из-за того, что ячейка со значением исчезнет или есть какая-то специальная команда, которая выполняет эту операцию. Удаление в массиве — это всего лишь перезапись ячейки любым значением, отличным от удаляемого.

public class Test < public static void main(String[] args) < float[] array = ; System.out.println(array[1]); array[1] = 3.31f; System.out.println(array[1]); > >

В этом примере создается массив из float. Затем выводится начальное значение по индексу 1. Далее по этому индексу устанавливается новое значение, которое фактически удаляет (перезаписывает) старое. И новое значение отображается в консоли.

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

Для решения этой задачи запишем следующий код:

public class Test < public static void main(String[] args) < int[] array = ; for (int i = array.length - 1; i > 0; i--) < array[i] = array[i - 1]; >for (int a : array) < System.out.print(a); >> >

В консоли отобразятся следующие значения: 00123. Число 4 было удалено значением 3, которое в цикле переместилось на его место. В этом примере все значения цикла поменяли свои позиции — сдвинулись вправо на одну ячейку.

8. Скорость работы массива

Чтобы понять, насколько массив является быстрой (но не всегда) структурой данных, необходимо определиться, хотя бы вкратце, с теоретической частью вычисления этой скорости. Для этого необходимо сказать пару слов про сложность алгоритмов (Big O Notation).

Big O Notation можно перевести как О большое. Это способ (из теории алгоритмов) обозначения сложности алгоритма.

Любой алгоритм характеризуется двумя параметрами: временем выполнения (Time Complexity) и расходом памяти (Space Complexity). Например, чем длиннее массив, тем больше нужно памяти и времени на его заполнение.

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

Например, Time Complexity для поиска числа в массиве будет обозначаться, как O(n), где n — это число ячеек в массиве. Чем их больше, тем дольше работает алгоритм (выполняется большее количество операций (итераций массива), где n — их максимальное число). Т. к. поиск происходит линейно, то ячейки перебираются одна за другой по порядку. Соответственно, такой алгоритм называется линейным.

Элементы массива в памяти размещаются в едином блоке. Это сделано для более эффективного и быстрого доступа к ним.

Если индекс ячейки известен, то алгоритм имеет константное время (постоянное, не зависящее ни от чего) и обозначается O(1), т. е. доступ к ячейкам массива происходит мгновенно и не зависит от его размера.

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

Более подробно об этой теме вы можете узнать в книге Бхаргава А. "Грокаем алгоритмы".

А дорого ли вычислять каждый раз длину массива?

Как мы выяснили, длина массива — это фиксированное значение, которое появляется в момент создания массива и хранится в его заголовке. JVM знает, что это значение никогда не изменится, а, значит, оно никогда не вычисляется повторно, а просто берется из заголовка. Этот тип операции чтения очень быстрый и выполняется за O(1), т. е. за константное время.

Заключение

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

  • java
  • массивы в java
  • сезон java one love

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *