6. Talk about List
List is an interface that represents an ordered structure of objects, which is called a list. The “trick” of this structure is that the elements contained in the List can be inserted, modified or deleted by index, that is, the internal identifier of the List . In other words, the index means: "how many elements from the beginning of the list". The first element of the List has index 0, the second element has index 1, and so on. Thus, the fifth element is four elements away from the beginning of the list. As mentioned above, the order in which items are added to a list is important. Therefore, the data structure is called a list . We list the methods unique to this structure that are aimed at working with elements by index:- get - returns the element at the specified position (by index value),
- remove - removes the element at the specified position,
- set - replaces the element at the specified position with the element specified in the method.
- push - adds the passed element to the top of the stack;
- peek - returns the element that is at the top of the stack;
- pop - also returns the element that is at the top of the stack, but removes it;
- empty - checks if the stack is empty - true , or not - false ;
- search - searches the stack for the given element. If an element is found, its ordinal relative to the top of the stack is returned. If the element is not found, -1 is returned.
7. Tell me about Map
As mentioned above, Map is a collection that has a separate structure of interfaces and their implementations. It is separate because here the values are not stored one at a time, but in a key-value pair. Map main methods :- put(K key, V value) - adding an element to the Map;
- get(Object key) - search for a value by key;
- containsKey(Object key) - check Map for the presence of the given key;
- containsValue(Object value) - check Map for the presence of the given value;
- remove(Object key) - remove a value by its key.
- HashMap - designed to store values in an arbitrary order, but allows you to quickly look up map elements. Allows you to set the key with the keyword null , but not more than once, because pairs with the same keys are written on top of each other. The main condition is the uniqueness of the keys: values can be repeated (there can be several null values).
- LinkedHashMap is similar to HashMap , which stores values in the order they were added. Accordingly, like LinkedList , it has a header - the head of a doubly linked list. When initialized, it points to itself.
LinkedHashMap also has accessOrder , which specifies how the elements will be accessed while using the iterator. If accessOrder is false, access will be done in the order in which the elements are inserted. If set to true, the elements will be in last accessed order (the last accessed element will be placed at the end).
- TreeMap is a Map that sorts elements by key values. Similar to TreeSet , but for key-value pairs. To specify sorting rules for a TreeMap, keys must implement the Comparable interface. Otherwise, there must be a Comparator oriented to keys (the one that is set in the TreeMap constructor ), TreeSet - implemented with a TreeMap object inside, in which, in fact, all the magic happens.
You can read more about sorting in TreeMap using red-black trees in the article about TreeMap features .
- Hashtable - Similar to HashMap , but does not allow nulls to be stored either as keys or as values. It is carefully synchronized in terms of multithreading, which in turn means that it is safe in terms of multithreading. But this implementation is outdated and slow, so now you will not meet Hashtable in more or less new projects.
8. ArrayList vs LinkedList. Which one is preferable to use?
This question is perhaps the most popular one on data structures and carries some pitfalls. Before answering it, let's learn more about these data structures. ArrayList implements the List interface , and operates on an internal array that expands as needed. When the internal array is completely filled, and a new element needs to be inserted, a new array is created, with a size of (oldSize * 1.5) +1. After that, all data from the old array is copied to the new + new element, the old one will be removed by the garbage collector . add methodadds an element to the last empty cell in the array. That is, if we already have 3 elements there, it will add the next one to the 4th cell. Let's walk through the performance of the basic methods:- get(int index) - taking an element in an array by index is the fastest in O(1) ;
- add(Object obj) - if there is enough space in the internal array for a new element, then O(1) time will be spent during a normal insertion , since the addition goes purposefully to the last cell.
If you need to create a new array and copy the contents into it, then the time we will have is directly proportional to the number of elements in the array O(n) ;
- remove (int index) - when removing an element, for example, from the middle, we will get O (n / 2) time, since we will need to move the elements to the right of it one cell back. Accordingly, if deleting from the beginning of the list, then O (n), from the end - O (1);
- add(int index, Object obj) - a situation similar to deletion: when adding to the middle, we will need to move the elements from the right one cell forward, so the time is O (n / 2). Of course, from the beginning - O(n), from the end - O(1);
- set(int index, Object obj) - the situation is different here, since you only need to find the desired element and write over it without moving the rest, so O (1).
- get(int index) - when searching for an element that is in the middle of the list, it starts searching through all the elements in order until the desired one is found. Logically, the search should take O(n/2) , but LinkedList also has a tail, so the search is carried out simultaneously from both sides. Accordingly, the time is reduced to O(n/4) .
If the element is not far from the beginning of the list or the end, then the time will be O(1) ;
- add(Object obj) - when a new element is added, a link to the next element will be added to the “tail” element, and the new one will receive a link to this previous element and become a new “tail”. Accordingly, the time will be O(1) ;
- remove(int index) - similar logic to the get(int index) method . To remove an element from the middle of the list, it must first be found. This is again O(n/4) , while the deletion itself actually takes nothing, since the pointer of neighboring objects only changes there (they begin to refer to each other). If the element is at the beginning or at the end, then again - O(1) ;
- add(int index, Object obj) and set(int index, Object obj) - the time complexity of the methods will be identical to get(int index) , since most of the time is spent searching for the element. Therefore, for the middle of the list - O(n/4) , for the beginning - O(1).
Operation | ArrayList | LinkedList |
---|---|---|
Get by index get(index) | O(1) | In the middle O(n/4) |
Add new element add(obj) |
O(1) If you need to copy the array then - O (n) |
O(1) |
Remove element remove(int index) |
From the beginning - O(n) From the middle - O(n/2) From the end - O(1) |
Middle - O(n/4) At the end or at the beginning - O(n) |
Add element add(int index, Object obj) |
To the beginning - O(n) To the middle - O(n/2) End - O(1) |
Middle - O(n/4) At the end or at the beginning - O(n) |
Replace element set(index, obj) | O(1) |
Middle - O(n/4) At the end or at the beginning - O(n) |
- the best choice if the most frequent operation is element search, element overwriting;
- the worst choice if the operation is insertion and deletion in the beginning-middle, because elements shift operations will take place from the right.
- the best choice if our frequent operation is insertion and deletion in the beginning-middle;
- the worst choice if the most frequent operation is a search.
9. How are elements stored in a HashMap?
The HashMap collection contains an internal array Node [] table , whose cells are also called buckets (baskets). Node contain:- key - link to the key,
- value — reference to value,
- hash - hash value,
- next - a link to the next Node .
- The key is checked against null . If it's null , then key will be stored in table[0] , because the hash code for null is always 0.
- If the key is not null , then the hashcode() method is called on the key object , which will return its hash code. This hash code is used to determine the cell in the array where the Node object will be stored.
- Next, this hashcode is placed in the internal hash() method , which calculates the hashcode, but already within the size of the table[] array .
- Further, depending on the hash value, the Node is placed in a specific cell in the table[] array .
- If the table[] cell used to store the current Node element is not empty, but already has some element, then the Node elements are iterated by the next value until the last element is reached. That is, the one whose next field is null .
During this enumeration, the key of the guarded Node object is compared with the keys of the iterated:
- if a match is found, the iteration will end and the new Node will overwrite the Node in which the match was found (only its value field will be overwritten );
- if no key matches are found, then the new Node will be the last one in this list, and the previous one will have a next link to it.
10. Tell us about the iterator
In the Collection hierarchy mapping scheme above, the Collection interface was where the whole hierarchy started, but in practice it's not exactly like that. Collection inherits from an interface with an iterator() method that returns an object that implements the Iterator<E> interface . The Iterator interface looks like this:public interface Iterator <E>{
E next();
boolean hasNext();
void remove();
}
next() - by calling this method, you can get the next element. hasNext() - makes it possible to find out if there is a next element and if the end of the collection has been reached. And when there are still elements, hasNext() will return true . Typically, hasNext() is called before the next() method , because when the end of the collection is reached, next() will throw a NoSuchElementException . remove() - removes the element that was obtained by the last call to next() . The purpose of an Iterator is to iterate over elements. For example:
Set<Integer> values = new TreeSet<>();
values.add(5);
values.add(3);
values.add(6);
values.add(8);
values.add(2);
values.add(4);
values.add(1);
values.add(7);
Iterator<Integer> iter = values.iterator();
while(iter.hasNext()){
System.out.println(iter.next());
}
Actually, the for-each loop is implemented under the hood using an iterator. You can read more about this here . List provides its own version of the iterator, but more cool and fancy - ListIterator . This interface extends Iterator and has additional methods:
- hasPrevious will return true if the collection has a previous element, false otherwise;
- previous returns the current element and jumps to the previous one; if there is none, then a NoSuchElementException is thrown;
- add will insert the passed object before the element to be returned by the next call to next() ;
- set assigns a reference to the passed object to the current element;
- nextIndex returns the index of the next element. If there is none, then the size of the list is returned;
- previousIndex returns the index of the previous element. If there is none, then -1 is returned.
GO TO FULL VERSION