Вопросы по собеседованию о коллекциях Java

Интервью с коллекциями Java

1. Вступление

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

2. Вопросы

Q1. Опишите иерархию типов коллекций. Каковы основные интерфейсы и в чем разница между ними?

ИнтерфейсIterable представляет любую коллекцию, которая может быть повторена с помощью циклаfor-each. ИнтерфейсCollection наследуется отIterable и добавляет общие методы для проверки того, находится ли элемент в коллекции, добавления и удаления элементов из коллекции, определения его размера и т. Д.

ИнтерфейсыList,Set иQueue наследуются от интерфейсаCollection.

List - это упорядоченная коллекция, и к ее элементам можно получить доступ по их индексу в списке.

Set - это неупорядоченная коллекция с отдельными элементами, аналогичная математическому понятию набора.

Queue - это коллекция с дополнительными методами для добавления, удаления и проверки элементов, полезная для хранения элементов перед обработкой.

ИнтерфейсMap также является частью структуры сбора, но не расширяетCollection. Это сделано для того, чтобы подчеркнуть разницу между коллекциями и отображениями, которые трудно собрать под общей абстракцией. ИнтерфейсMap представляет собой структуру данных «ключ-значение» с уникальными ключами и не более одного значения для каждого ключа.

 

Q2. Описывать различные реализации интерфейса карты и различия между ними.

Одной из наиболее часто используемых реализаций интерфейсаMap являетсяHashMap. Это типичная структура данных хэш-карты, которая позволяет получать доступ к элементам за постоянное время, или O (1), ноdoes not preserve order and is not thread-safe.

Чтобы сохранить порядок вставки элементов, вы можете использовать классLinkedHashMap, который расширяетHashMap и дополнительно связывает элементы в связанный список с предсказуемыми накладными расходами.

КлассTreeMap хранит свои элементы в красно-черной древовидной структуре, которая позволяет получать доступ к элементам за логарифмическое время или O (log (n)). В большинстве случаев он медленнее, чемHashMap, но позволяет сохранять элементы в порядке в соответствии с некоторымComparator.

ConcurrentHashMap - это поточно-ориентированная реализация хэш-карты. Он обеспечивает полный параллелизм при извлечении (поскольку операцияget не влечет за собой блокировку) и ожидаемый высокий уровень параллелизма обновлений.

КлассHashtable присутствует в Java с версии 1.0. Это не считается устаревшим, но в основном считается устаревшим. Это потокобезопасная хэш-карта, но в отличие отConcurrentHashMap, все ее методы представляют собой простоsynchronized, что означает, что все операции с этим блоком карты, даже получение независимых значений.

 

Q3. Объясните разницу между Linkedlist и Arraylist.

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

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

Однако в большинстве случаевArrayList превосходитLinkedList. Даже элементы, сдвигающиеся вArrayList, хотя и являются операцией O (n), реализованы как очень быстрый вызовSystem.arraycopy(). Он может даже появиться быстрее, чем вставка O (1)LinkedList 's, которая требует создания экземпляра объектаNode и обновления нескольких ссылок под капотом. LinkedList также может иметь большие накладные расходы на память из-за создания нескольких небольших объектовNode.

 

Q4. В чем разница между Hashset и Treeset?

Оба классаHashSet иTreeSet реализуют интерфейсSet и представляют собой наборы отдельных элементов. Кроме того,TreeSet реализует интерфейсNavigableSet. Этот интерфейс определяет методы, которые используют преимущества упорядочения элементов.

HashSet внутренне основан наHashMap, аTreeSet поддерживается экземпляромTreeMap, который определяет их свойства:HashSet не сохраняет элементы в каких-либо конкретных порядок. Итерация по элементам вHashSet производит их в перемешанном порядке. TreeSet, с другой стороны, производит элементы в порядке в соответствии с некоторым предопределеннымComparator.

 

Q5. Как Hashmap реализован в Java? Как его реализация использует хэш-код и методы Equals объектов? Какова временная сложность размещения и получения элемента из такой структуры?

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

HashMap поддерживается массивом изменяемого размера, который имеет размер степени двойки. Когда элемент добавляется кHashMap, сначала вычисляется егоhashCode (значениеint). Затем определенное количество младших битов этого значения используется в качестве индекса массива. Этот индекс напрямую указывает на ячейку массива (называемую сегментом), в которую должна быть помещена эта пара ключ-значение. Доступ к элементу по индексу в массиве - это очень быстрая операция O (1), которая является основной особенностью структуры хэш-карты.

ОднакоhashCode не уникален, и даже для разныхhashCodes мы можем получить одну и ту же позицию в массиве. Это называется столкновением. Существует более одного способа разрешения коллизий в структурах данных хэш-карт. В JavaHashMap каждая корзина фактически относится не к одному объекту, а к красно-черному дереву всех объектов, попавших в эту корзину (до Java 8 это был связанный список).

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

Чтобы получить объект по его ключу,HashMap снова должен вычислитьhashCode для ключа, найти соответствующее ведро, пройти по дереву, вызватьequals для ключей в дереве и найти соответствующий.

HashMap имеет сложность O (1) или постоянную сложность размещения и получения элементов. Конечно, множество коллизий может ухудшить производительность до O (log (n)) временной сложности в худшем случае, когда все элементы попадают в одну корзину. Обычно это решается путем предоставления хорошей хэш-функции с равномерным распределением.

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

 

Q6. Какова цель параметров начальной емкости и коэффициента загрузки хэш-карты? Каковы их значения по умолчанию?

АргументinitialCapacity конструктораHashMap влияет на размер внутренней структуры данныхHashMap, но рассуждать о фактическом размере карты немного сложно. Внутренняя структура данныхHashMap представляет собой массив с размером степени двойки. Таким образом, значение аргументаinitialCapacity увеличивается до следующей степени двойки (например, если вы установите его на 10, фактический размер внутреннего массива будет равен 16).

Коэффициент загрузки aHashMap - это отношение количества элементов к количеству ковшей (т. Е. размер внутреннего массива). Например, если 16-сегментныйHashMap содержит 12 элементов, его коэффициент загрузки составляет 12/16 = 0,75. Высокий коэффициент нагрузки означает много столкновений, что, в свою очередь, означает, что размер карты должен быть изменен до следующей степени двух. Таким образом, аргументloadFactor - это максимальное значение коэффициента загрузки карты. Когда карта достигает этого коэффициента загрузки, она изменяет свой внутренний массив до следующего значения степени двойки.

initialCapacity по умолчанию равен 16, а loadFactor - 0,75 по умолчанию, поэтому вы можете поместить 12 элементов вHashMap, экземпляр которого был создан с помощью конструктора по умолчанию, и он не будет изменять размер. То же самое касаетсяHashSet, который поддерживается экземпляромHashMap внутри.

Следовательно, нетривиально найтиinitialCapacity, который удовлетворит ваши потребности. Вот почему в библиотеке Guava есть методыMaps.newHashMapWithExpectedSize() иSets.newHashSetWithExpectedSize(), которые позволяют создаватьHashMap илиHashSet, которые могут содержать ожидаемое количество элементов без изменения размера.

===

Q7. Опишите специальные коллекции для Enums. Каковы преимущества их внедрения по сравнению с обычными сборами?

EnumSet иEnumMap являются специальными реализациями интерфейсовSet иMap соответственно. Вы всегда должны использовать эти реализации, когда имеете дело с перечислениями, потому что они очень эффективны.

EnumSet - это просто битовый вектор с «единицами» в позициях, соответствующих порядковым значениям перечислений, присутствующих в наборе. Чтобы проверить, находится ли значение перечисления в наборе, реализация просто должна проверить, является ли соответствующий бит в векторе «единица», что является очень простой операцией. Точно так жеEnumMap - это массив, доступ к которому осуществляется с помощью порядкового номера enum в качестве индекса. В случаеEnumMap нет необходимости вычислять хэш-коды или разрешать конфликты.

 

Q8. В чем разница между отказоустойчивыми и отказоустойчивыми итераторами?

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

ИтераторыFail-fast (возвращаемыеHashMap,ArrayList и другими небезопасными коллекциями) перебирают внутреннюю структуру данных коллекции и бросаютConcurrentModificationException, как только они обнаруживают одновременную модификацию.

ИтераторыFail-safe (возвращаемые потокобезопасными коллекциями, такими какConcurrentHashMap,CopyOnWriteArrayList) создают копию структуры, по которой они итерируют. Они гарантируют безопасность от одновременных модификаций. Их недостатки включают в себя чрезмерное потребление памяти и итерацию, возможно, устаревших данных в случае изменения коллекции.

 

Q9. Как можно использовать интерфейсы сравнения и сравнения для сортировки коллекций?

ИнтерфейсComparable - это интерфейс для объектов, которые можно сравнивать в определенном порядке. Его единственный метод -compareTo, который работает с двумя значениями: самим объектом и объектом аргумента того же типа. Например,Integer,Long и другие числовые типы реализуют этот интерфейс. String также реализует этот интерфейс, а его методcompareTo сравнивает строки в лексикографическом порядке.

ИнтерфейсComparable позволяет сортировать списки соответствующих объектов с помощью методаCollections.sort() и поддерживать порядок итераций в коллекциях, реализующихSortedSet иSortedMap. Если ваши объекты могут быть отсортированы с использованием некоторой логики, они должны реализовывать интерфейсComparable.

ИнтерфейсComparable обычно реализуется с использованием естественного порядка элементов. Например, все числаInteger отсортированы от меньшего к большему. Но иногда вы можете захотеть реализовать другой вид упорядочения, например, для сортировки чисел в порядке убывания. Здесь может помочь интерфейсComparator.

Класс объектов, которые вы хотите отсортировать, не должен реализовывать этот интерфейс. Вы просто создаете реализующий класс и определяете методcompare, который получает два объекта и решает, как их упорядочить. Затем вы можете использовать экземпляр этого класса, чтобы переопределить естественный порядок методаCollections.sort() или экземпляровSortedSet иSortedMap.

Поскольку интерфейсComparator является функциональным интерфейсом, вы можете заменить его лямбда-выражением, как в следующем примере. Он показывает упорядочивание списка с использованием естественного упорядочения (интерфейсInteger ’sComparable) и с использованием настраиваемого итератора (интерфейсComparator<Integer>).

List list1 = Arrays.asList(5, 2, 3, 4, 1);
Collections.sort(list1);
assertEquals(new Integer(1), list1.get(0));

List list1 = Arrays.asList(5, 2, 3, 4, 1);
Collections.sort(list1, (a, b) -> b - a);
assertEquals(new Integer(5), list1.get(0));