Cannot understand a line in HashMap.java code - java

I was going through HashMap.java and in the function getNode(...),
I came across three things I didn't understand:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
Firstly, why are they comparing hashcode and key both? Should one comparison not be enough?
Secondly, why even store hashcode in the node when collision is possible.
Thirdly, this if condition :
(k = e.key) == key || (key != null && key.equals(k)))
seems like the part after the OR should be enough.
Can someone shed some light into this design decision please!

Related

why ConcurrentHashMap doesn't use 'CAS' but use 'synchronized' when tabAt[i] is not null

final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
When tabAt(tab, i = (n - 1) & hash)) is null, it use CAS to add/modify a node, but when is not null, it use synchronized (f). I think it can still use CAS of last node or node with key that need put of tabAt(tab, i = (n - 1) & hash)) to add/modify a node. But why not? My idea is wrong.

Sorting using collections for multiple parameter with null values

I wanted to sort list of records in my using multiple comparators.
It contains null values as well for some fields.
I used java collections, to sort it, but it works only for one filed which is places at top.
My code:
Collections.sort(userListResult, new Comparator<UserDto>()
{
#Override
public int compare(UserDto userDto1, UserDto userDto2)
{
if (userDto1.getLastLoginDate() != null && userDto2.getLastLoginDate() != null && userDto2.getLastLoginDate().compareTo(userDto1.getLastLoginDate()) != 0) {
return userDto2.getLastLoginDate().compareTo(userDto1.getLastLoginDate());
}
if (userDto1.getLastLoginDate() != null && userDto2.getLastLoginDate() == null) {
return -1;
}
if (userDto1.getLastLoginDate() == null && userDto2.getLastLoginDate() != null) {
return 1;
}
if (userDto1.getLastActionDate() != null && userDto2.getLastActionDate() != null && userDto2.getLastActionDate().compareTo(userDto1.getLastActionDate()) != 0) {
return userDto2.getLastActionDate().compareTo(userDto1.getLastActionDate());
}
if (userDto1.getLastActionDate() == null && userDto2.getLastActionDate() == null) {
return 0;
}
if (userDto1.getLastActionDate() == null && userDto2.getLastActionDate() != null) {
return 1;
}
if (userDto1.getLastActionDate() != null && userDto2.getLastActionDate() == null) {
return -1;
}
if (userDto1.getLastName() != null && userDto2.getLastName() != null && userDto1.getLastName().compareTo(userDto2.getLastName()) != 0) {
return userDto1.getLastName().compareTo(userDto2.getLastName());
}
if (userDto1.getLastName() != null && userDto2.getLastName() == null) {
return -1;
}
if (userDto1.getLastName() == null && userDto2.getLastName() != null) {
return 1;
}
if (userDto1.getFirstName().compareTo(userDto2.getFirstName()) != 0) {
return userDto1.getFirstName().compareTo(userDto2.getFirstName());
}
if (userDto1.getFirstName() != null && userDto2.getFirstName() == null) {
return -1;
}
if (userDto1.getFirstName() == null && userDto2.getFirstName() != null) {
return 1;
}
return 0;
}
});*/
I wanted to sort descending of LastLoginDate, LastActionDate. Either or both fields might be null in some cases.
And ascending of names. And to display all other null field values(LastLoginDate, LastActionDate) at last.
But when i sort, with this code it takes only the first defined method to sort.
Example, if i have 3 records having same, "LastLoginDate" then it should check sort with "LastActionDate" values. But its not sorted with this logic.
So i added another algorithm to sort, since some fields has null values it is not properly sorted.
My second logic:
if (userDto1.getLastLoginDate() != null && userDto2.getLastLoginDate() != null && userDto2.getLastLoginDate().compareTo(userDto1.getLastLoginDate()) != 0) {
compareLastLoginDate = userDto2.getLastLoginDate().compareTo(userDto1.getLastLoginDate());
}
if (userDto1.getLastActionDate() != null && userDto2.getLastActionDate() != null && userDto2.getLastActionDate().compareTo(userDto1.getLastActionDate()) != 0) {
compareLastActionDate = userDto2.getLastActionDate().compareTo(userDto1.getLastActionDate());
}
if (userDto1.getLastName() != null && userDto2.getLastName() != null && userDto1.getLastName().compareTo(userDto2.getLastName()) != 0) {
compareLastName = userDto1.getLastName().compareTo(userDto2.getLastName());
}
if (compareLastLoginDate == 0) {
return ((compareLastActionDate == 0) ? compareLastName : compareLastActionDate);
}
else {
return compareLastLoginDate;
}
Here i am getting the integer values to compare it "compareLastActionDate, compareLastName, compareLastLoginDate".
I used simple date format to filter the list.
Here is my working code,
public int compare(UserDto userDto1, UserDto userDto2) {
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
if (userDto1.getLastLoginDate() != null && userDto2.getLastLoginDate() != null && userDto2.getLastLoginDate().compareTo(userDto1.getLastLoginDate()) != 0) {
String Date1 = format.format(userDto1.getLastLoginDate());
String Date2 = format.format(userDto2.getLastLoginDate());
if (Date1.equals(Date2)) {
if (userDto1.getLastActionDate() != null && userDto2.getLastActionDate() != null && userDto2.getLastActionDate().compareTo(userDto1.getLastActionDate()) != 0) {
String Date3 = format.format(userDto1.getLastActionDate());
String Date4 = format.format(userDto2.getLastActionDate());
if (Date3.equals(Date4)) {
if (userDto1.getLastName() != null && userDto2.getLastName() != null && userDto1.getLastName().compareTo(userDto2.getLastName()) != 0) {
return userDto1.getLastName().compareTo(userDto2.getLastName());
}
} else {
return userDto2.getLastActionDate().compareTo(userDto1.getLastActionDate());
}
}
} else {
return userDto2.getLastLoginDate().compareTo(userDto1.getLastLoginDate());
}
}
if (userDto1.getLastLoginDate() != null && userDto2.getLastLoginDate() == null) {
return -1;
}
if (userDto1.getLastLoginDate() == null && userDto2.getLastLoginDate() != null) {
return 1;
}
if (userDto1.getLastLoginDate() == null && userDto2.getLastLoginDate() == null) {
return userDto1.getLastName().compareTo(userDto2.getLastName());
} }
I would recommend you to use the java8 comparators, it provides some helpful methods to compare based in multiple attributes with thenComparing().
If you don't want to / can't use, try two wrap part of that repetitive code in one or multiple custom Comparator or in a helper method.
Here, for simplicity, the following helper method is used (in java8 you can use method references):
public static <R extends Comparable<? super R>> int nullSort(R r1, R r2,
boolean ascending,
boolean nullsFirst) {
R rs1 = ascending ? r1 : r2, rs2 = ascending ? r2 : r1;
if (rs1 != null && rs2 != null) return rs1.compareTo(rs2);
else if (r1 == null && r2 == null) return 0;
else if (r1 == null) return (nullsFirst ? -1 : 1);
else return (nullsFirst ? 1 : -1);
}
In your method, you could use this nullSort() method, to check first for lastLoginDate descending and nulls last (false, false), then lastActionDate again descending and nulls last.
A difference with your comparator is that you stop evaluating here when both are null. It would be better that when lastLoginDate and lastActionDate are nulls you keep ordering by lastName and firstName. If you don't want this, you can add if (result == 0) return 0; after the second if in the method below.
Then after the dates are compared, you compare lastName and then firstName both ascending and nulls last (true, false).
#Override
public int compare(UserDto dto1, UserDto dto2) {
int result;
if ((result = nullSort(dto1.getLastLoginDate(), dto2.getLastLoginDate(),
false, false)) != 0) return result;
if ((result = nullSort(dto1.getLastActionDate(), dto2.getLastActionDate(),
false, false)) != 0) return result;
if ((result = nullSort(dto1.getLastName(), dto2.getLastName(),
true, false)) != 0) return result;
if ((result = nullSort(dto1.getFirstName(), dto2.getFirstName(),
true, false)) != 0) return result;
return 0;
}

HashMap get method in Java 6 & Java 8

I am looking at HashMap get method in Java 6 & Java 8, the implementation in Java 8 is little complex, I am not able to get it.
This is from Java 6:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
Here in Java 6, it is getting the right Entry element and trying to find the corresponding value based on given key.
This code if from Java 8:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) {
return first;
}
if ((e = first.next) != null) {
if (first instanceof TreeNode) {
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
}
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
return e;
}
} while ((e = e.next) != null);
}
}
return null;
}
I am not able to understand the logic in Java 8.
How they are taking the first element:
(first = tab[(n - 1) & hash]) != null)
and what is this extra logic:
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
Regarding:
(first = tab[(n - 1) & hash]) != null)
That comes from how the entry is added to the table, shown below:
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
AND-ing (n-1) and hash allows entries with hashCode=hash to be spread over the n entries of the table. (n-1) is used to prevent the edge case of attempting to insert into tab[n] - which could lead to ArrayIndexOutOfBoundsException since tab.length is n.
The "extra logic" that you are referring to:
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
The above returns the very first Node from the table which not only matches the hashCode of the key being searched, but also exactly "equals" that key.
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
The above returns the Node if the bucket has been "Treeified" - details about which as pointed in one of the comments is specified in the "Implementation Notes" of this class.

ConcurrentHashMap where does get method lock?

I read this excellent article about the ConcurrentHashMap by Brian Goetz. But when I was looking at the code of ConcurrentHashMap in more recent java version (1.8), I noticed a couple of differences
The next pointer in the MapEntry is not final, but rather volatile so it is possible to modify the list in the middle and not just beginning.
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Also I don't see where exactly the get method is obtaining the lock when it fails to lookup the key in the initial iteration
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
Can someone please explain how and where is the lock obtained in the get method, if at all?

How can i count comparisons are made when i try to enter a new key in a hash map?

I want to make a method to count how many comparisons are made when i want to put a new random key in my hash map . The code I used to put new keys in the map is the following :
public void put(int key, int value) {
int hash = (key % table.length);
int initialHash = -1;
int indexOfDeletedEntry = -1;
while (hash != initialHash
&& (table[hash] == DeletedEntry.getUniqueDeletedEntry()
|| table[hash] != null
&& table[hash].getKey() != key)) {
if (initialHash == -1)
initialHash = hash;
if (table[hash] == DeletedEntry.getUniqueDeletedEntry())
indexOfDeletedEntry = hash;
hash = (hash + 1) % table.length;
}
if ((table[hash] == null || hash == initialHash)
&& indexOfDeletedEntry != -1) {
table[indexOfDeletedEntry] = new HashEntry(key, value);
size++;
} else if (initialHash != hash)
if (table[hash] != DeletedEntry.getUniqueDeletedEntry()
&& table[hash] != null && table[hash].getKey() == key)
table[hash].setValue(value);
else {
table[hash] = new HashEntry(key, value);
size++;
}
if (size >= maxSize)
resize();
}
The class for the deleted entry is the following :
public class DeletedEntry extends HashEntry {
private static DeletedEntry entry = null;
private DeletedEntry() {
super(-1, -1);
}
public static DeletedEntry getUniqueDeletedEntry() {
if (entry == null)
entry = new DeletedEntry();
return entry;
}
}
Also , HashEntry class has 2 int variables , int key and int value .
Any Idea how i can count the comparisons ?
This is what I've done in my main:
Random rand = new Random();
int[] comparisons = new int[20];
int key = 0;
for (int k=0;k<20;k++){
key = rand.nextInt(1000) + 1;
}
(I'm assuming that this is a learning exercise of some kind. Hence advice to use or extend an existing Map implementation is irrelevant.)
The simple answer is that you increment a counter each time you "compare" keys. You could do that inline, or you could write yourself a little helper method like this:
private boolean compareKeys(int key1, int key2) {
count++;
return key1 == key2;
}
and then change your code to use this helper each time it compares keys; e.g.
while (hash != initialHash
&& (table[hash] == DeletedEntry.getUniqueDeletedEntry()
|| table[hash] != null
&& !compareKeys(table[hash].getKey(), key))) {
and
if (table[hash] != DeletedEntry.getUniqueDeletedEntry()
&& table[hash] != null
&& compareKeys(table[hash].getKey(), key))
There really is no clever solution to this problem.
You can write your own CustomHashMap
In this CustomHashMap you can implement a new put() method that keeps count of the comparisons and then returns that value.
public int put(int key, int value) {
int hash = (key % table.length);
int initialHash = -1;
int indexOfDeletedEntry = -1;
int numberOfComparisons = 1;
while (hash != initialHash
&& (table[hash] == DeletedEntry.getUniqueDeletedEntry()
|| table[hash] != null
&& table[hash].getKey() != key)) {
numberOfComparisons++;
if (initialHash == -1)
initialHash = hash;
if (table[hash] == DeletedEntry.getUniqueDeletedEntry())
indexOfDeletedEntry = hash;
hash = (hash + 1) % table.length;
}
if ((table[hash] == null || hash == initialHash)
&& indexOfDeletedEntry != -1) {
table[indexOfDeletedEntry] = new HashEntry(key, value);
size++;
} else if (initialHash != hash)
if (table[hash] != DeletedEntry.getUniqueDeletedEntry()
&& table[hash] != null && table[hash].getKey() == key)
table[hash].setValue(value);
else {
table[hash] = new HashEntry(key, value);
size++;
}
if (size >= maxSize)
resize();
return numberOfComparisons;
}

Categories