86 KiB
Java Collections Framework
- Что такое «коллекция»?
- Назовите основные интерфейсы JCF и их реализации.
- Расположите в виде иерархии следующие интерфейсы:
List
,Set
,Map
,SortedSet
,SortedMap
,Collection
,Iterable
,Iterator
,NavigableSet
,NavigableMap
. - Почему
Map
— это неCollection
, в то время какList
иSet
являютсяCollection
? - В чем разница между классами
java.util.Collection
иjava.util.Collections
? - Что такое «fail-fast поведение»?
- Какая разница между fail-fast и fail-safe?
- Приведите примеры итераторов реализующих поведение fail-safe
- Чем различаются
Enumeration
иIterator
. - Как между собой связаны
Iterable
иIterator
? - Как между собой связаны
Iterable
,Iterator
и «for-each»? - Сравните
Iterator
иListIterator
. - Что произойдет при вызове
Iterator.next()
без предварительного вызоваIterator.hasNext()
? - Сколько элементов будет пропущено, если
Iterator.next()
будет вызван после 10-ти вызововIterator.hasNext()
? - Как поведёт себя коллекция, если вызвать
iterator.remove()
? - Как поведёт себя уже инстанциированный итератор для
collection
, если вызватьcollection.remove()
? - Как избежать
ConcurrentModificationException
во время перебора коллекции? - Какая коллекция реализует дисциплину обслуживания FIFO?
- Какая коллекция реализует дисциплину обслуживания FILO?
- Чем отличается
ArrayList
отVector
? - Зачем добавили
ArrayList
, если уже былVector
? - Чем отличается
ArrayList
отLinkedList
? В каких случаях лучше использовать первый, а в каких второй? - Что работает быстрее
ArrayList
илиLinkedList
? - Какое худшее время работы метода
contains()
для элемента, который есть вLinkedList
? - Какое худшее время работы метода
contains()
для элемента, который есть вArrayList
? - Какое худшее время работы метода
add()
дляLinkedList
? - Какое худшее время работы метода
add()
дляArrayList
? - Необходимо добавить 1 млн. элементов, какую структуру вы используете?
- Как происходит удаление элементов из
ArrayList
? Как меняется в этом случае размерArrayList
? - Предложите эффективный алгоритм удаления нескольких рядом стоящих элементов из середины списка, реализуемого
ArrayList
. - Сколько необходимо дополнительной памяти при вызове
ArrayList.add()
? - Сколько выделяется дополнительно памяти при вызове
LinkedList.add()
? - Оцените количество памяти на хранение одного примитива типа
byte
вLinkedList
? - Оцените количество памяти на хранение одного примитива типа
byte
вArrayList
? - Для
ArrayList
или дляLinkedList
операция добавления элемента в середину (list.add(list.size()/2, newElement)
) медленнее? - В реализации класса
ArrayList
есть следующие поля:Object[] elementData
,int size
. Объясните, зачем хранить отдельноsize
, если всегда можно взятьelementData.length
? - Сравните интерфейсы
Queue
иDeque
. - Кто кого расширяет:
Queue
расширяетDeque
, илиDeque
расширяетQueue
? - Почему
LinkedList
реализует иList
, иDeque
? LinkedList
— это односвязный, двусвязный или четырехсвязный список?- Как перебрать элементы
LinkedList
в обратном порядке, не используя медленныйget(index)
? - Что позволяет сделать
PriorityQueue
? Stack
считается «устаревшим». Чем его рекомендуют заменять? Почему?- Зачем нужен
HashMap
, если естьHashtable
? - В чем разница между
HashMap
иIdentityHashMap
? Для чего нужнаIdentityHashMap
? - В чем разница между
HashMap
иWeakHashMap
? Для чего используетсяWeakHashMap
? - В
WeakHashMap
используются WeakReferences. А почему бы не создатьSoftHashMap
на SoftReferences? - В
WeakHashMap
используются WeakReferences. А почему бы не создатьPhantomHashMap
на PhantomReferences? LinkedHashMap
- что в нем отLinkedList
, а что отHashMap
?- В чем проявляется «сортированность»
SortedMap
, кроме того, чтоtoString()
выводит все элементы по порядку? - Как устроен
HashMap
? - Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресации и на основе метода цепочек. Как реализована
HashMap
? Почему, по вашему мнению, была выбрана именно эта реализация? В чем плюсы и минусы каждого подхода? - Как работает
HashMap
при попытке сохранить в него два элемента по ключам с одинаковымhashCode()
, но для которыхequals() == false
? - Какое начальное количество корзин в
HashMap
? - Какова оценка временной сложности операций над элементами из
HashMap
? Гарантирует лиHashMap
указанную сложность выборки элемента? - Возможна ли ситуация, когда
HashMap
выродится в список даже с ключами имеющими разныеhashCode()
? - В каком случае может быть потерян элемент в
HashMap
? - Почему нельзя использовать
byte[]
в качестве ключа вHashMap
? - Какова роль
equals()
иhashCode()
вHashMap
? - Каково максимальное число значений
hashCode()
? - Какое худшее время работы метода get(key) для ключа, которого нет в
HashMap
? - Какое худшее время работы метода get(key) для ключа, который есть в
HashMap
? - Сколько переходов происходит в момент вызова
HashMap.get(key)
по ключу, который есть в таблице? - Сколько создается новых объектов, когда вы добавляете новый элемент в
HashMap
? - Как и когда происходит увеличение количества корзин в
HashMap
? - Объясните смысл параметров в конструкторе
HashMap(int initialCapacity, float loadFactor)
. - Будет ли работать
HashMap
, если все добавляемые ключи будут иметь одинаковыйhashCode()
? - Как перебрать все ключи
Map
? - Как перебрать все значения
Map
? - Как перебрать все пары «ключ-значение» в
Map
? - В чем отличия
TreeSet
иHashSet
? - Что будет, если добавлять элементы в
TreeSet
по возрастанию? - Чем
LinkedHashSet
отличается отHashSet
? - Для
Enum
есть специальный классjava.util.EnumSet
. Зачем? Чем авторов не устраивалHashSet
илиTreeSet
? - Какие существуют способы перебирать элементы списка?
- Каким образом можно получить синхронизированные объекты стандартных коллекций?
- Как получить коллекцию только для чтения?
- Напишите однопоточную программу, которая заставляет коллекцию выбросить
ConcurrentModificationException
. - Приведите пример, когда какая-либо коллекция выбрасывает
UnsupportedOperationException
. - Реализуйте симметрическую разность двух коллекций используя методы
Collection
(addAll(...)
,removeAll(...)
,retainAll(...)
). - Как, используя LinkedHashMap, сделать кэш c «invalidation policy»?
- Как одной строчкой скопировать элементы любой
collection
в массив? - Как одним вызовом из
List
получитьList
со всеми элементами, кроме первых и последних 3-х? - Как одной строчкой преобразовать
HashSet
вArrayList
? - Как одной строчкой преобразовать
ArrayList
вHashSet
? - Сделайте
HashSet
из ключейHashMap
. - Сделайте
HashMap
изHashSet<Map.Entry<K, V>>
.
Что такое «коллекция»?
«Коллекция» - это структура данных, набор каких-либо объектов. Данными (объектами в наборе) могут быть числа, строки, объекты пользовательских классов и т.п.
Назовите основные интерфейсы JCF и их реализации.
На вершине иерархии в Java Collection Framework располагаются 2 интерфейса: Collection
и Map
. Эти интерфейсы разделяют все коллекции, входящие во фреймворк на две части по типу хранения данных: простые последовательные наборы элементов и наборы пар «ключ — значение» соответственно.
Интерфейс Collection
расширяют интерфейсы:
List
(список) представляет собой коллекцию, в которой допустимы дублирующие значения. Элементы такой коллекции пронумерованы, начиная от нуля, к ним можно обратиться по индексу. Реализации:ArrayList
- инкапсулирует в себе обычный массив, длина которого автоматически увеличивается при добавлении новых элементов.LinkedList
(двунаправленный связный список) - состоит из узлов, каждый из которых содержит как собственно данные, так и две ссылки на следующий и предыдущий узел.Vector
— реализация динамического массива объектов, методы которой синхронизированы.Stack
— реализация стека LIFO (last-in-first-out).
Set
(сет) описывает неупорядоченную коллекцию, не содержащую повторяющихся элементов. Реализации:HashSet
- использует HashMap для хранения данных. В качестве ключа и значения используется добавляемый элемент. Из-за особенностей реализации порядок элементов не гарантируется при добавлении.LinkedHashSet
— гарантирует, что порядок элементов при обходе коллекции будет идентичен порядку добавления элементов.TreeSet
— предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».
Queue
(очередь) предназначена для хранения элементов с предопределённым способом вставки и извлечения FIFO (first-in-first-out):PriorityQueue
— предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».ArrayDeque
— реализация интерфейсаDeque
, который расширяет интерфейсQueue
методами, позволяющими реализовать конструкцию вида LIFO (last-in-first-out).
Интерфейс Map
реализован классами:
Hashtable
— хэш-таблица, методы которой синхронизированы. Не позволяет использоватьnull
в качестве значения или ключа и не является упорядоченной.HashMap
— хэш-таблица. Позволяет использоватьnull
в качестве значения или ключа и не является упорядоченной.LinkedHashMap
— упорядоченная реализация хэш-таблицы.TreeMap
— реализация основанная на красно-чёрных деревьях. Является упорядоченной и предоставляет возможность управлять порядком элементов в коллекции при помощи объектаComparator
, либо сохраняет элементы с использованием «natural ordering».WeakHashMap
— реализация хэш-таблицы, которая организована с использованием weak references для ключей (сборщик мусора автоматически удалит элемент из коллекции при следующей сборке мусора, если на ключ этого элемента нет жёстких ссылок).
Расположите в виде иерархии следующие интерфейсы: List
, Set
, Map
, SortedSet
, SortedMap
, Collection
, Iterable
, Iterator
, NavigableSet
, NavigableMap
.
Iterable
Collection
List
Set
SortedSet
NavigableSet
Map
SortedMap
NavigableMap
Iterator
Почему Map
— это не Collection
, в то время как List
и Set
являются Collection
?
Collection
представляет собой совокупность некоторых элементов. Map
- это совокупность пар «ключ-значение».
В чем разница между классами java.util.Collection
и java.util.Collections
?
java.util.Collections
- набор статических методов для работы с коллекциями.
java.util.Collection
- один из основных интерфейсов Java Collections Framework.
Что такое «fail-fast поведение»?
fail-fast поведение означает, что при возникновении ошибки или состояния, которое может привести к ошибке, система немедленно прекращает дальнейшую работу и уведомляет об этом. Использование fail-fast подхода позволяет избежать недетерминированного поведения программы в течение времени.
В Java Collections API некоторые итераторы ведут себя как fail-fast и выбрасывают ConcurrentModificationException
, если после его создания была произведена модификация коллекции, т.е. добавлен или удален элемент напрямую из коллекции, а не используя методы итератора.
Реализация такого поведения осуществляется за счет подсчета количества модификаций коллекции (modification count):
- при изменении коллекции счетчик модификаций так же изменяется;
- при создании итератора ему передается текущее значение счетчика;
- при каждом обращении к итератору сохраненное значение счетчика сравнивается с текущим, и, если они не совпадают, возникает исключение.
Какая разница между fail-fast и fail-safe?
В противоположность fail-fast, итераторы fail-safe не вызывают никаких исключений при изменении структуры, потому что они работают с клоном коллекции вместо оригинала.
Приведите примеры итераторов реализующих поведение fail-safe
Итератор коллекции CopyOnWriteArrayList
и итератор представления keySet
коллекции ConcurrentHashMap
являются примерами итераторов fail-safe.
Чем различаются Enumeration
и Iterator
.
Хотя оба интерфейса и предназначены для обхода коллекций между ними имеются существенные различия:
- с помощью
Enumeration
нельзя добавлять/удалять элементы; - в
Iterator
исправлены имена методов для повышения читаемости кода (Enumeration.hasMoreElements()
соответствуетIterator.hasNext()
,Enumeration.nextElement()
соответствуетIterator.next()
и т.д); Enumeration
присутствуют в устаревших классах, таких какVector
/Stack
, тогда какIterator
есть во всех современных классах-коллекциях.
Как между собой связаны Iterable
и Iterator
?
Интерфейс Iterable
имеет только один метод - iterator()
, который возвращает Iterator
.
Как между собой связаны Iterable
, Iterator
и «for-each»?
Классы, реализующие интерфейс Iterable
, могут применяться в конструкции for-each
, которая использует Iterator
.
Сравните Iterator
и ListIterator
.
ListIterator
расширяет интерфейсIterator
ListIterator
может быть использован только для перебора элементов коллекцииList
;Iterator
позволяет перебирать элементы только в одном направлении, при помощи методаnext()
. Тогда какListIterator
позволяет перебирать список в обоих направлениях, при помощи методовnext()
иprevious()
;ListIterator
не указывает на конкретный элемент: его текущая позиция располагается между элементами, которые возвращают методыprevious()
иnext()
.- При помощи
ListIterator
вы можете модифицировать список, добавляя/удаляя элементы с помощью методовadd()
иremove()
.Iterator
не поддерживает данного функционала.
Что произойдет при вызове Iterator.next()
без предварительного вызова Iterator.hasNext()
?
Если итератор указывает на последний элемент коллекции, то возникнет исключение NoSuchElementException
, иначе будет возвращен следующий элемент.
Сколько элементов будет пропущено, если Iterator.next()
будет вызван после 10-ти вызовов Iterator.hasNext()
?
Нисколько - hasNext()
осуществляет только проверку наличия следующего элемента.
Как поведёт себя коллекция, если вызвать iterator.remove()
?
Если вызову iterator.remove()
предшествовал вызов iterator.next()
, то iterator.remove()
удалит элемент коллекции, на который указывает итератор, в противном случае будет выброшено IllegalStateException()
.
Как поведёт себя уже инстанциированный итератор для collection
, если вызвать collection.remove()
?
При следующем вызове методов итератора будет выброшено ConcurrentModificationException
.
Как избежать ConcurrentModificationException
во время перебора коллекции?
- Попробовать подобрать другой итератор, работающий по принципу fail-safe. К примеру, для
List
можно использоватьListIterator
. - Использовать
ConcurrentHashMap
иCopyOnWriteArrayList
. - Преобразовать список в массив и перебирать массив.
- Блокировать изменения списка на время перебора с помощью блока
synchronized
.
Отрицательная сторона последних двух вариантов - ухудшение производительности.
Какая коллекция реализует дисциплину обслуживания FIFO?
FIFO, First-In-First-Out («первым пришел-первым ушел») - по этому принципу построена коллекция Queue
.
Какая коллекция реализует дисциплину обслуживания FILO?
FILO, First-In-Last-Out («первым пришел, последним ушел») - по этому принципу построена коллекция Stack
.
Чем отличается ArrayList
от Vector
?
Зачем добавили ArrayList
, если уже был Vector
?
- Методы класса
Vector
синхронизированы, аArrayList
- нет; - По умолчанию,
Vector
удваивает свой размер, когда заканчивается выделенная под элементы память.ArrayList
же увеличивает свой размер только на половину.
Vector
это устаревший класс и его использование не рекомендовано.
Чем отличается ArrayList
от LinkedList
? В каких случаях лучше использовать первый, а в каких второй?
ArrayList
это список, реализованный на основе массива, а LinkedList
— это классический двусвязный список, основанный на объектах с ссылками между ними.
ArrayList
:
- доступ к произвольному элементу по индексу за время O(1);
- доступ к элементам по значению за линейное время O(N);
- вставка в конец в среднем производится за время O(1);
- удаление произвольного элемента из списка занимает значительное время т.к. при этом все элементы находящиеся «правее» смещаются на одну ячейку влево (реальный размер массива (capacity) не изменяется);
- вставка элемента в произвольное место списка занимает значительное время т.к. при этом все элементы находящиеся «правее» смещаются на одну ячейку вправо;
- минимум накладных расходов при хранении.
LinkedList
:
- на получение элемента по индексу или значению потребуется линейное время O(N);
- на добавление и удаление в начало или конец списка потребуется O(1);
- вставка или удаление в/из произвольного место O(N);
- требует больше памяти для хранения такого же количества элементов, потому что кроме самого элемента хранятся еще указатели на следующий и предыдущий элементы списка.
В целом, LinkedList
в абсолютных величинах проигрывает ArrayList
и по потребляемой памяти и по скорости выполнения операций. LinkedList
предпочтительно применять, когда нужны частые операции вставки/удаления или в случаях, когда необходимо гарантированное время добавления элемента в список.
Что работает быстрее ArrayList
или LinkedList
?
Смотря какие действия будут выполняться над структурой.
см. Чем отличается ArrayList
от LinkedList
Какое худшее время работы метода contains()
для элемента, который есть в LinkedList
?
O(N). Время поиска элемента линейно пропорционально количеству элементов с списке.
Какое худшее время работы метода contains()
для элемента, который есть в ArrayList
?
O(N). Время поиска элемента линейно пропорционально количеству элементов с списке.
Какое худшее время работы метода add()
для LinkedList
?
O(N). Добавление в начало/конец списка осуществляется за время O(1).
Какое худшее время работы метода add()
для ArrayList
?
O(N). Вставка элемента в конец списка осуществляется за время O(1), но если вместимость массива недостаточна, то происходит создание нового массива с увеличенным размером и копирование всех элементов из старого массива в новый.
Необходимо добавить 1 млн. элементов, какую структуру вы используете?
Однозначный ответ можно дать только исходя из информации о том в какую часть списка происходит добавление элементов, что потом будет происходить с элементами списка, существуют ли какие-то ограничения по памяти или скорости выполнения.
см. Чем отличается ArrayList
от LinkedList
Как происходит удаление элементов из ArrayList
? Как меняется в этом случае размер ArrayList
?
При удалении произвольного элемента из списка, все элементы находящиеся «правее» смещаются на одну ячейку влево и реальный размер массива (его емкость, capacity) не изменяется никак. Механизм автоматического «расширения» массива существует, а вот автоматического «сжатия» нет, можно только явно выполнить «сжатие» командой trimToSize()
.
Предложите эффективный алгоритм удаления нескольких рядом стоящих элементов из середины списка, реализуемого ArrayList
.
Допустим нужно удалить n
элементов с позиции m
в списке. Вместо выполнения удаления одного элемента n
раз (каждый раз смещая на 1 позицию элементы, стоящие «правее» в списке), нужно выполнить смещение всех элементов, стоящих «правее» n + m
позиции на n
элементов «левее» к началу списка. Таким образом, вместо выполнения n
итераций перемещения элементов списка, все выполняется за 1 проход.
Сколько необходимо дополнительной памяти при вызове ArrayList.add()
?
Если в массиве достаточно места для размещения нового элемента, то дополнительной памяти не требуется. Иначе происходит создание нового массива размером в 1,5 раза превышающим существующий (это верно для JDK выше 1.7, в более ранних версиях размер увеличения иной).
Сколько выделяется дополнительно памяти при вызове LinkedList.add()
?
Создается один новый экземпляр вложенного класса Node
.
Оцените количество памяти на хранение одного примитива типа byte
в LinkedList
?
Каждый элемент LinkedList
хранит ссылку на предыдущий элемент, следующий элемент и ссылку на данные.
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
//...
}
Для 32-битных систем каждая ссылка занимает 32 бита (4 байта). Сам объект (заголовок) вложенного класса Node
занимает 8 байт. 4 + 4 + 4 + 8 = 20 байт, а т.к. размер каждого объекта в Java кратен 8, соответственно получаем 24 байта. Примитив типа byte
занимает 1 байт памяти, но в JCF примитивы упаковываются: объект типа Byte
занимает в памяти 16 байт (8 байт на заголовок объекта, 1 байт на поле типа byte
и 7 байт для кратности 8). Также напомню, что значения от -128 до 127 кэшируются и для них новые объекты каждый раз не создаются. Таким образом, в x32 JVM 24 байта тратятся на хранение одного элемента в списке и 16 байт - на хранение упакованного объекта типа Byte
. Итого 40 байт.
Для 64-битной JVM каждая ссылка занимает 64 бита (8 байт), размер заголовка каждого объекта составляет 16 байт (два машинных слова). Вычисления аналогичны: 8 + 8 + 8 + 16 = 40байт и 24 байта. Итого 64 байта.
Оцените количество памяти на хранение одного примитива типа byte
в ArrayList
?
ArrayList
основан на массиве, для примитивных типов данных осуществляется автоматическая упаковка значения, поэтому 16 байт тратится на хранение упакованного объекта и 4 байта (8 для x64) - на хранение ссылки на этот объект в самой структуре данных. Таким образом, в x32 JVM 4 байта используются на хранение одного элемента и 16 байт - на хранение упакованного объекта типа Byte
. Для x64 - 8 байт и 24 байта соотвтетсвенно.
Для ArrayList
или для LinkedList
операция добавления элемента в середину (list.add(list.size()/2, newElement)
) медленнее?
Для ArrayList
:
- проверка массива на вместимость. Если вместимости недостаточно, то увеличение размера массива и копирование всех элементов в новый массив (O(N));
- копирование всех элементов, расположенных правее от позиции вставки, на одну позицию вправо (O(N));
- вставка элемента (O(1)).
Для LinkedList
:
- поиск позиции вставки (O(N));
- вставка элемента (O(1)).
В худшем случае вставка в середину списка эффективнее для LinkedList
. В остальных - скорее всего, для ArrayList
, поскольку копирование элементов осуществляется за счет вызова быстрого системного метода System.arraycopy()
.
В реализации класса ArrayList
есть следующие поля: Object[] elementData
, int size
. Объясните, зачем хранить отдельно size
, если всегда можно взять elementData.length
?
Размер массива elementData
представляет собой вместимость (capacity) ArrayList
, которая всегда больше переменной size
- реального количества хранимых элементов. При необходимости вместимость автоматически возрастает.
Сравните интерфейсы Queue
и Deque
.
Кто кого расширяет: Queue
расширяет Deque
, или Deque
расширяет Queue
?
Queue
- это очередь, которая обычно (но необязательно) строится по принципу FIFO (First-In-First-Out) - соответственно извлечение элемента осуществляется с начала очереди, вставка элемента - в конец очереди. Хотя этот принцип нарушает, к примеру PriorityQueue
, использующая «natural ordering» или переданный Comparator
при вставке нового элемента.
Deque
(Double Ended Queue) расширяет Queue
и согласно документации это линейная коллекция, поддерживающая вставку/извлечение элементов с обоих концов. Помимо этого реализации интерфейса Deque
могут строится по принципу FIFO, либо LIFO.
Реализации и Deque
, и Queue
обычно не переопределяют методы equals()
и hashCode()
, вместо этого используются унаследованные методы класса Object, основанные на сравнении ссылок.
Почему LinkedList
реализует и List
, и Deque
?
LinkedList
позволяет добавлять элементы в начало и конец списка за константное время, что хорошо согласуется с поведением интерфейса Deque
.
LinkedList
— это односвязный, двусвязный или четырехсвязный список?
Двусвязный
: каждый элемент LinkedList
хранит ссылку на предыдущий и следующий элементы.
Как перебрать элементы LinkedList
в обратном порядке, не используя медленный get(index)
?
Для этого в LinkedList
есть обратный итератор, который можно получить вызва метод descendingIterator()
.
Что позволяет сделать PriorityQueue
?
Особенностью PriorityQueue
является возможность управления порядком элементов. По-умолчанию, элементы сортируются с использованием «natural ordering», но это поведение может быть переопределено при помощи объекта Comparator
, который задаётся при создании очереди. Данная коллекция не поддерживает null в качестве элементов.
Используя PriorityQueue
, можно, например, реализовать алгоритм Дейкстры для поиска кратчайшего пути от одной вершины графа к другой. Либо для хранения объектов согласно определённого свойства.
Stack
считается «устаревшим». Чем его рекомендуют заменять? Почему?
Stack
был добавлен в Java 1.0 как реализация стека LIFO (last-in-first-out) и является расширением коллекции Vector
, хотя это несколько нарушает понятие стека (например, класс Vector
предоставляет возможность обращаться к любому элементу по индексу). Является частично синхронизированной коллекцией (кроме метода добавления push()
) с вытекающими отсюда последствиями в виде негативного воздействия на производительность. После добавления в Java 1.6 интерфейса Deque
, рекомендуется использовать реализации именно этого интерфейса, например ArrayDeque
.
Зачем нужен HashMap
, если есть Hashtable
?
- Методы класса
Hashtable
синхронизированы, что приводит к снижению производительности, аHashMap
- нет; HashTable
не может содержать элементыnull
, тогда какHashMap
может содержать один ключnull
и любое количество значенийnull
;- Iterator у
HashMap
, в отличие от Enumeration уHashTable
, работает по принципу «fail-fast» (выдает исключение при любой несогласованности данных).
Hashtable
это устаревший класс и его использование не рекомендовано.
В чем разница между HashMap
и IdentityHashMap
? Для чего нужна IdentityHashMap
?
IdentityHashMap
- это структура данных, так же реализующая интерфейс Map
и использующая при сравнении ключей (значений) сравнение ссылок, а не вызов метода equals()
. Другими словами, в IdentityHashMap
два ключа k1
и k2
будут считаться равными, если они указывают на один объект, т.е. выполняется условие k1
== k2
.
IdentityHashMap
не использует метод hashCode()
, вместо которого применяется метод System.identityHashCode()
, по этой причине IdentityHashMap
по сравнению с HashMap
имеет более высокую производительность, особенно если последний хранит объекты с дорогостоящими методами equals()
и hashCode()
.
Одним из основных требований к использованию HashMap
является неизменяемость ключа, а, т.к. IdentityHashMap
не использует методы equals()
и hashCode()
, то это правило на него не распространяется.
IdentityHashMap
может применяться для реализации сериализации/клонирования. При выполнении подобных алгоритмов программе необходимо обслуживать хэш-таблицу со всеми ссылками на объекты, которые уже были обработаны. Такая структура не должна рассматривать уникальные объекты как равные, даже если метод equals()
возвращает true
.
В чем разница между HashMap
и WeakHashMap
? Для чего используется WeakHashMap
?
В Java существует 4 типа ссылок: сильные (strong reference), мягкие (SoftReference), слабые (WeakReference) и фантомные (PhantomReference). Особенности каждого типа ссылок связаны с работой Garbage Collector. Если объект можно достичь только с помощью цепочки WeakReference (то есть на него отсутствуют сильные и мягкие ссылки), то данный объект будет помечен на удаление.
WeakHashMap
- это структура данных, реализующая интерфейс Map
и основанная на использовании WeakReference для хранения ключей. Таким образом, пара «ключ-значение» будет удалена из WeakHashMap
, если на объект-ключ более не имеется сильных ссылок.
В качестве примера использования такой структуры данных можно привести следующую ситуацию: допустим имеются объекты, которые необходимо расширить дополнительной информацией, при этом изменение класса этих объектов нежелательно либо невозможно. В этом случае добавляем каждый объект в WeakHashMap
в качестве ключа, а в качестве значения - нужную информацию. Таким образом, пока на объект имеется сильная ссылка (либо мягкая), можно проверять хэш-таблицу и извлекать информацию. Как только объект будет удален, то WeakReference для этого ключа будет помещен в ReferenceQueue и затем соответствующая запись для этой слабой ссылки будет удалена из WeakHashMap
.
В WeakHashMap
используются WeakReferences. А почему бы не создать SoftHashMap
на SoftReferences?
SoftHashMap
представлена в сторонних библиотеках, например, в Apache Commons
.
В WeakHashMap
используются WeakReferences. А почему бы не создать PhantomHashMap
на PhantomReferences?
PhantomReference при вызове метода get()
возвращает всегда null
, поэтому тяжело представить назначение такой структуры данных.
LinkedHashMap
- что в нем от LinkedList
, а что от HashMap
?
Реализация LinkedHashMap
отличается от HashMap
поддержкой двухсвязанного списка, определяющего порядок итерации по элементам структуры данных. По умолчанию элементы списка упорядочены согласно их порядку добавления в LinkedHashMap
(insertion-order). Однако порядок итерации можно изменить, установив параметр конструктора accessOrder
в значение true
. В этом случае доступ осуществляется по порядку последнего обращения к элементу (access-order). Это означает, что при вызове методов get()
или put()
элемент, к которому обращаемся, перемещается в конец списка.
При добавлении элемента, который уже присутствует в LinkedHashMap
(т.е. с одинаковым ключом), порядок итерации по элементам не изменяется.
В чем проявляется «сортированность» SortedMap
, кроме того, что toString()
выводит все элементы по порядку?
Так же оно проявляется при итерации по коллекции.
Как устроен HashMap
?
HashMap
состоит из «корзин» (bucket). С технической точки зрения «корзины» — это элементы массива, которые хранят ссылки на списки элементов. При добавлении новой пары «ключ-значение», вычисляет хэш-код ключа, на основании которого вычисляется номер корзины (номер ячейки массива), в которую попадет новый элемент. Если корзина пустая, то в нее сохраняется ссылка на вновь добавляемый элемент, если же там уже есть элемент, то происходит последовательный переход по ссылкам между элементами в цепочке, в поисках последнего элемента, от которого и ставится ссылка на вновь добавленный элемент. Если в списке был найден элемент с таким же ключом, то он заменяется.
Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на основе открытой адресации и на основе метода цепочек. Как реализована HashMap
? Почему, по вашему мнению, была выбрана именно эта реализация? В чем плюсы и минусы каждого подхода?
HashMap
реализован с использованием метода цепочек, т.е. каждой ячейке массива (корзине) соответствует свой связный список и при возникновении коллизии осуществляется добавление нового элемента в этот список.
Для метода цепочек коэффициент заполнения может быть больше 1 и с увеличением числа элементов производительность убывает линейно. Такие таблицы удобно использовать, если заранее неизвестно количество хранимых элементов, либо их может быть достаточно много, что приводит к большим значениям коэффициента заполнения.
Среди методов открытой реализации различают:
- линейное пробирование;
- квадратичное пробирование;
- двойное хэширование.
Недостатки структур с методом открытой адресации:
- Количество элементов в хэш-таблице не может превышать размера массива. По мере увеличения числа элементов и повышения коэффициента заполнения производительность структуры резко падает, поэтому необходимо проводить перехэширование.
- Сложно организовать удаление элемента.
- Первые два метода открытой адресации приводят к проблеме первичной и вторичной группировок.
Преимущества хэш-таблицы с открытой адресацией:
- отсутствие затрат на создание и хранение объектов списка;
- простота организации сериализации/десериализации объекта.
Как работает HashMap
при попытке сохранить в него два элемента по ключам с одинаковым hashCode()
, но для которых equals() == false
?
По значению hashCode()
вычисляется индекс ячейки массива, в список которой этот элемент будет добавлен. Перед добавлением осуществляется проверка на наличие элементов в этой ячейке. Если элементы с таким hashCode()
уже присутствует, то осуществляется обход списка со сравнением по equals()
в поисках идентичного элемента. Т.к. equals() == false
, - элемент будет добавлен в начало списка.
Какое начальное количество корзин в HashMap
?
В конструкторе по умолчанию - 16, используя конструкторы с параметрами можно задавать произвольное начальное количество корзин.
Какова оценка временной сложности операций над элементами из HashMap
? Гарантирует ли HashMap
указанную сложность выборки элемента?
В общем случае операции добавления, поиска и удаления элементов занимают константное время.
Данная сложность не гарантируется, т.к. если хэш-функция распределяет элементы по корзинам равномерно, временная сложность станет не хуже O(lg(N)), а в случае, когда хэш-функция постоянно возвращает одно и то же значение HashMap
превратится в связный список со сложностью О(n) .
Возможна ли ситуация, когда HashMap
выродится в список даже с ключами имеющими разные hashCode()
?
Это возможно в случае, если метод, определяющий номер корзины будет возвращать одинаковые значения.
В каком случае может быть потерян элемент в HashMap
?
Допустим, в качестве ключа используется не примитив, а объект с несколькими полями. После добавления элемента в HashMap
у объекта, который выступает в качестве ключа, изменяют одно поле, которое участвует в вычислении хэш-кода. В результате при попытке найти данный элемент по исходному ключу, будет происходить обращение к правильной корзине, а вот equals
уже не найдет указанный ключ в списке элементов. Тем не менее, даже если equals
реализован таким образом, что изменение данного поля объекта не влияет на результат, то после увеличения размера корзин и пересчета хэш-кодов элементов, указанный элемент, с измененным значением поля, с большой долей вероятности попадет в совершенно другую корзину и тогда уже потеряется совсем.
Почему нельзя использовать byte[]
в качестве ключа в HashMap
?
Хэш-код массива не зависит от хранимых в нем элементов, а присваивается при создании массива (метод вычисления хэш-кода массива не переопределен и вычисляется по стандартному Object.hashCode()
на основании адреса массива). Так же у массивов не переопределен equals
и выполняется сравнение указателей. Это приводит к тому, что обратиться к сохраненному с ключом-массивом элементу не получится при использовании другого массива такого же размера и с такими же элементами, доступ можно осуществить лишь в одном случае — при использовании той же самой ссылки на массив, что использовалась для сохранения элемента.
Какова роль equals()
и hashCode()
в HashMap
?
hashCode
позволяет определить корзину для поиска элемента, а equals
используется для сравнения ключей элементов в списке корзины и искомого ключа.
Каково максимальное число значений hashCode()
?
Число значений следует из сигнатуры int hashCode()
и равно диапазону типа int
— 232.
Какое худшее время работы метода get(key) для ключа, которого нет в HashMap
?
Какое худшее время работы метода get(key) для ключа, который есть в HashMap
?
O(N). Худший случай - это поиск ключа в HashMap
, вырожденного в список по причине совпадения ключей по hashCode()
и для выяснения хранится ли элемент с определённым ключом может потребоваться перебор всего списка.
Сколько переходов происходит в момент вызова HashMap.get(key)
по ключу, который есть в таблице?
- ключ равен
null
: 1 - выполняется единственный методgetForNullKey()
. - любой ключ отличный от
null
: 4 - вычисление хэш-кода ключа; определение номера корзины; поиск значения; возврат значения.
Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap
?
Один новый объект статического вложенного класса Entry<K,V>
.
Как и когда происходит увеличение количества корзин в HashMap
?
Помимо capacity
у HashMap
есть еще поле loadFactor
, на основании которого, вычисляется предельное количество занятых корзин capacity * loadFactor
. По умолчанию loadFactor = 0.75
. По достижению предельного значения, число корзин увеличивается в 2 раза и для всех хранимых элементов вычисляется новое «местоположение» с учетом нового числа корзин.
Объясните смысл параметров в конструкторе HashMap(int initialCapacity, float loadFactor)
.
initialCapacity
- исходный размерHashMap
, количество корзин в хэш-таблице в момент её создания.loadFactor
- коэффициент заполненияHashMap
, при превышении которого происходит увеличение количества корзин и автоматическое перехэширование. Равен отношению числа уже хранимых элементов в таблице к её размеру.
Будет ли работать HashMap
, если все добавляемые ключи будут иметь одинаковый hashCode()
?
Да, будет, но в этом случае HashMap
вырождается в связный список и теряет свои преимущества.
Как перебрать все ключи Map
?
Использовать метод keySet()
, который возвращает множество Set<K>
ключей.
Как перебрать все значения Map
?
Использовать метод values()
, который возвращает коллекцию Collection<V>
значений.
Как перебрать все пары «ключ-значение» в Map
?
Использовать метод entrySet()
, который возвращает множество Set<Map.Entry<K, V>
пар «ключ-значение».
В чем отличия TreeSet
и HashSet
?
TreeSet
обеспечивает упорядоченно хранение элементов в виде красно-черного дерева. Сложность выполнения основных операций не хуже O(lg(N)).
HashSet
использует для хранения элементов такой же подход, что и HashMap
, за тем отличием, что в HashSet
в качестве ключа и значения выступает сам элемент
, кроме того HashSet
не поддерживает упорядоченное хранение элементов и обеспечивает временную сложность выполнения операций аналогично HashMap
.
Что будет, если добавлять элементы в TreeSet
по возрастанию?
В основе TreeSet
лежит красно-черное дерево, которое умеет само себя балансировать. В итоге, TreeSet
все равно в каком порядке вы добавляете в него элементы, преимущества этой структуры данных будут сохраняться.
Чем LinkedHashSet
отличается от HashSet
?
LinkedHashSet
отличается от HashSet
только тем, что в его основе лежит LinkedHashMap
вместо HashMap
. Благодаря этому порядок элементов при обходе коллекции является идентичным порядку добавления элементов (insertion-order). При добавлении элемента, который уже присутствует в LinkedHashSet
(т.е. с одинаковым ключом), порядок обхода элементов не изменяется.
Для Enum
есть специальный класс java.util.EnumSet
. Зачем? Чем авторов не устраивал HashSet
или TreeSet
?
EnumSet
- это реализация интерфейса Set
для использования с перечислениями (Enum
). В структуре данных хранятся объекты только одного типа Enum
, указываемого при создании. Для хранения значений EnumSet
использует массив битов (bit vector), - это позволяет получить высокую компактность и эффективность. Проход по EnumSet
осуществляется согласно порядку объявления элементов перечисления.
Все основные операции выполняются за O(1) и обычно (но негарантированно) быстрей аналогов из HashSet
, а пакетные операции (bulk operations), такие как containsAll()
и retainAll()
выполняются даже горазда быстрей.
Помимо всего EnumSet
предоставляет множество статических методов инициализации для упрощенного и удобного создания экземпляров.
Какие существуют способы перебирать элементы списка?
- Цикл с итератором
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
//iterator.next();
}
- Цикл
for
for (int i = 0; i < list.size(); i++) {
//list.get(i);
}
- Цикл
while
int i = 0;
while (i < list.size()) {
//list.get(i);
i++;
}
- «for-each»
for (String element : list) {
//element;
}
Каким образом можно получить синхронизированные объекты стандартных коллекций?
С помощью статических методов synchronizedMap()
и synchronizedList()
класса Collections
. Данные методы возвращают синхронизированный декоратор переданной коллекции. При этом все равно в случае обхода по коллекции требуется ручная синхронизация.
Map m = Collections.synchronizedMap(new HashMap());
List l = Collections.synchronizedList(new ArrayList());
Начиная с Java 6 JCF был расширен специальными коллекциями, поддерживающими многопоточный доступ, такими как CopyOnWriteArrayList
и ConcurrentHashMap
.
Как получить коллекцию только для чтения?
При помощи:
Collections.unmodifiableList(list)
;Collections.unmodifiableSet(set)
;Collections.unmodifiableMap(map)
.
Эти методы принимают коллекцию в качестве параметра, и возвращают коллекцию только для чтения с теми же элементами внутри.
Напишите однопоточную программу, которая заставляет коллекцию выбросить ConcurrentModificationException
.
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (Integer integer : list) {
list.remove(1);
}
}
Приведите пример, когда какая-либо коллекция выбрасывает UnsupportedOperationException
.
public static void main(String[] args) {
List<Integer> list = Collections.emptyList();
list.add(0);
}
Реализуйте симметрическую разность двух коллекций используя методы Collection
(addAll(...)
, removeAll(...)
, retainAll(...)
).
Симметрическая разность двух коллекций - это множество элементов, одновременно не принадлежащих обоим исходным коллекциям.
<T> Collection<T> symmetricDifference(Collection<T> a, Collection<T> b) {
// Объединяем коллекции.
Collection<T> result = new ArrayList<>(a);
result.addAll(b);
// Получаем пересечение коллекций.
Collection<T> intersection = new ArrayList<>(a);
intersection.retainAll(b);
// Удаляем элементы, расположенные в обоих коллекциях.
result.removeAll(intersection);
return result;
}
Как, используя LinkedHashMap, сделать кэш c «invalidation policy»?
Необходимо использовать LRU-алгоритм (Least Recently Used algorithm) и LinkedHashMap
с access-order. В этом случае при обращении к элементу он будет перемещаться в конец списка, а наименее используемые элементы будут постепенно группироваться в начале списка. Так же в стандартной реализации LinkedHashMap
есть метод removeEldestEntries()
, который возвращает true
, если текущий объект LinkedHashMap
должен удалить наименее используемый элемент из коллекции при использовании методов put()
и putAll()
.
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final int MAX_ENTRIES = 10;
public LRUCache(int initialCapacity) {
super(initialCapacity, 0.85f, true);
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > MAX_ENTRIES;
}
}
Стоит заметить, что LinkedHashMap
не позволяет полностью реализовать LRU-алгоритм, поскольку при вставке уже имеющегося в коллекции элемента порядок итерации по элементам не меняется.
Как одной строчкой скопировать элементы любой collection
в массив?
Object[] array = collection.toArray();
Как одним вызовом из List
получить List
со всеми элементами, кроме первых и последних 3-х?
List<Integer> subList = list.subList(3, list.size() - 3);
Как одной строчкой преобразовать HashSet
в ArrayList
?
ArrayList<Integer> list = new ArrayList<>(new HashSet<>());
Как одной строчкой преобразовать ArrayList
в HashSet
?
HashSet<Integer> set = new HashSet<>(new ArrayList<>());
Сделайте HashSet
из ключей HashMap
.
HashSet<Object> set = new HashSet<>(map.keySet());
Сделайте HashMap
из HashSet<Map.Entry<K, V>>
.
HashMap<K, V> map = new HashMap<>(set.size());
for (Map.Entry<K, V> entry : set) {
map.put(entry.getKey(), entry.getValue());
}