В информатике существует концепция линейной структуры данных, которая означает, что данные структурированы линейно, при этом порядок имеет значение. Существуют массивы и связанные списки, но сегодня я буду говорить в основном о массивах и немного о связанных списках.
Большинство объектно-ориентированных языков поставляются с массивами, тогда как большинство f функциональных языков имеют связанные списки (см. Почему в другой из моих статей, упомянутых внизу этой статьи).
Для такой дифференциации есть веская причина, о которой мы поговорим позже. А пока давайте просто кратко рассмотрим различия между двумя структурами данных. Для этого нам нужно совершить путешествие по переулку памяти.
Перемотать время назад
Объекты и функции и все, что мы знаем о компьютерах, в основном хранятся в битах и байтах в компьютере.
В таких языках, как Java и C, вы должны заранее явно объявить размер массива.
Подождите, но Ruby этого не делает?
В Ruby мы используем Array
для наших линейных структур данных. Мы можем добавлять, казалось бы, бесконечные вещи в массив Ruby, и для нас это не имеет значения.
Это здорово, не правда ли ?! Это означает, что массивы бесконечно велики, не так ли? И что Ruby - лучший язык? Нам повезло!
Но не так быстро. * всплывает пузырь *
Не существует массивов бесконечного размера; то, что вы видите в Ruby, мы называем динамическим массивом, и за это приходится платить.
Чтобы понять, что такое динамические массивы, давайте сначала посмотрим, как массивы представлены в памяти. Поскольку MRI Ruby (Matz ’Ruby Interpreter) компилируется до кода C, мы рассмотрим, как массивы представлены в C.
C-ing верит
Мы немного углубимся в код C, чтобы помочь нам немного лучше… :)
В языках более низкого уровня, таких как C, вы должны сами иметь дело с указателями и распределением памяти. Даже если вы раньше не сталкивались с C (d isclaimer - я тоже), у вас может быть C-een один из самых (не) известных примеров ниже:
Давайте разберем этот фрагмент кода:
malloc
не имеет никакого магического значения, это буквально означаетmemory allocation
malloc
возвращает указательmalloc
принимает аргумент, который представляет собой размер памяти, которую программа должна выделить для вас.100 * sizeof(int)
сообщает программе, что мы хотим сохранить 100 целых чисел, поэтому выделите нам 100 * размер того, что занимало бы каждое целое число.ptr
/ указатель хранит ссылку на адрес памяти.
Тимми хранит багаж!
Если приведенный выше пример не имеет смысла, попробуйте эту аналогию. Думайте о распределении памяти как о багажном консьерже. Работает это так:
- Тимми идет к стойке, говорит консьержу, что у него есть 2 места багажа, примерно этого большого, и что он хотел бы оставить их на складе.
- Консьерж осматривает складское помещение и говорит: «Да, у нас есть место в обозначенной
B4
зоне, и мы выделим это место для хранения вашего багажа». - Они передают Тимми карточку для получения с обозначенным местом на ней,
B4
в нашем случае. - Тимми счастлив, ходит и делает что угодно, а когда хочет забрать свой багаж, он возвращается к стойке и показывает им свою карточку получения. «Ты забрал мой багаж?»
В нашем примере багаж Тимми - это данные, а карта выдачи - указатель (в нем указано, где хранится сумка Тимми). Консьерж хранит багаж Тимми в блоке памяти, а счетчик - в программе.
Показав счетчику (программе) карточку Тимми (указатель / адрес памяти), Тимми может получить свой багаж (данные). Бонус? Поскольку они точно знают, где хранится сумка Тимми - B4
, это означает, что они могут получить весь багаж Тимми относительно быстро!
Кроме того, вы когда-нибудь задумывались, почему вы получаете доступ к элементам в массиве с помощью индекса, например?
Это связано с тем, что массив содержит ссылки на блок памяти, а индекс сообщает ему смещение.
Аналогия: если я попрошу вас искать Тимми в очереди из 20 человек, вы, по логике, должны спросить каждого из них, были ли они Тимми. Но если я скажу вам, что Тимми - шестой (указатель) от первого лица (ваш исходный указатель), вы точно знаете, где искать.
Именно поэтому получение элементов в массивах происходит быстро - программе не нужно просматривать все 100 элементов, чтобы найти то, что вы ищете. Если у вас есть индекс, ему просто нужно добавить смещение к исходному адресу памяти, и дроид, которого вы искали, будет прямо там!
Что же тогда такое динамические массивы?
Итак, я немного рассказал вам о том, как массивы представлены в памяти, но теперь пора поговорить о некоторых минусах.
Помните, как вам нужно явно указать необходимый вам объем памяти? Это означает, что в массиве найдется место, которое будет точно соответствовать вашему размеру. Нет гарантии, что он сможет вместить больше, чем у вас есть (потому что блок памяти сразу за ним может быть занят).
Вернемся к нашей аналогии с багажом: представьте, что Тимми должен хранить 2 единицы багажа, а B4
может хранить ровно 2 единицы багажа, поэтому они передают это Тимми. Теперь по какой-то причине Тимми хочет хранить другую единицу багажа, но B4
не может хранить 3 единицы, а только 2, так что они делают?
Они берут весь его существующий багаж, перемещают его на новое место, где может поместиться более трех предметов, а затем хранят их все вместе.
Это дорогостоящая операция, но именно так работает и память!
В Ruby вам не нужно объявлять конкретный размер заранее, но это потому, что Ruby обрабатывает его автоматически с помощью динамических массивов.
Что делает динамический массив, так это то, что если массив приближается к своей полной емкости, он автоматически объявляет новый, более крупный массив и перемещает в него все существующие элементы, а затем старый массив собирается сборщиком мусора. Насколько больше? Фактор роста обычно 2; удвоить размер текущего массива.
На самом деле, не верьте мне на слово.
В Ruby есть модуль ObjectSpace, который позволяет нам взаимодействовать с текущими объектами, живущими в памяти. Мы можем использовать этот модуль, чтобы взглянуть на использование памяти нашим динамическим массивом - звучит именно так, как мы хотим!
Я написал небольшой скрипт на Ruby, который вычисляет коэффициент роста динамического массива. Не стесняйтесь взглянуть на это здесь, и если вы это сделаете, вы увидите, что массивы Ruby имеют коэффициент роста в 1,5 раза (то есть они создают массив, который на 50% больше на каждой копии).
Я знаю, что такое массивы, что такое связанные списки?
Имейте в виду, что хотя массивы и связанные списки считаются линейными структурами данных, между ними есть одно большое различие.
Элементы в массиве хранятся буквально рядом друг с другом в памяти (так что мы можем иметь индекс для быстрого поиска). Но узлы в связанных списках не имеют такого ограничения (поэтому нет поиска по индексам для связанных списков) - каждый элемент может быть сохранен в любом месте блока памяти.
Это похоже на то, как будто Тимми пытается хранить 5 единиц багажа, а у консьержа нет места и он решает оставить их повсюду. Звучит неорганизованно?
Кроме того, если они хранятся в разных местах, как узнать, какие сумки принадлежат Тимми? Подсказка: просто отслеживайте следующий узел / сумку! В нашем случае консьерж хранит их отдельно, но с меткой на каждой из них, указывающей на следующую сумку.
Узел в связанном списке состоит из двух частей - части данных и указателя на следующий узел. Вот как они могут поддерживать linear
его часть - у них все еще есть концепция порядка, их просто не нужно хранить в порядке буквально!
node = [ data | pointer ]
Например, учитывая следующий пример, хранящийся в памяти:
[C | D] [A | B] [B | C] [D | nil]
Эти биты выглядят так, как будто они не в порядке, но если бы я сказал вам, что первый элемент - это A
, вы могли бы сказать мне точный порядок в списке:
list = [A -> B -> C -> D -> nil]
Есть много интересных вещей, которые вы можете сделать со связанными списками, о которых я не буду здесь углубляться (также много чего о Big O, о котором я не говорил). Но уже есть масса хороших статей о структурах данных. Если вы сделали это здесь, я предлагаю вам прочитать из блога Али здесь.
Спасибо, следующий: введение в связанные списки
В этом посте мы будем говорить о структуре данных связанного списка на языке« спасибо, следующий . 'автор: dev.to »
Вы также можете прочитать больше о List / Cons на Wiki здесь.
Заключительная записка
Изначально я написал эту статью для немного другой темы - [Эликсир | Почему именно связанные списки?] , Но обнаружил, что мне потребовалось слишком много времени, чтобы объяснить, как работают массивы, прежде чем я смог объяснить, почему Elixir использует связанные списки. Поэтому я разделил их на две статьи.
В этой статье я расскажу о том, почему функциональные языки используют связанные списки в качестве линейной структуры данных. Проверьте это!
Источники
- Https://medium.com/@rebo_dood/ruby-has-a-memory-problem-part-1-7887bbacc579 - Здесь я узнал о дополнительных
ObjectSpace
методах, потребовав их
Изначально размещено на dev.to