HashMap get method in Java 6 & Java 8 - java

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.

Related

Cannot understand a line in HashMap.java code

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!

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.

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?

Insert in Binary Search Tree with duplicate Keys?

Right now when I'm inserting Keys/Values into the BST and then searching them I get null values. I would like to get some assistance on how to handle with duplicate keys.
private Node put(Node root, final Key key, final Value value) {
if (root == null)
return new Node(key, value, 1);
final int result = key.compareTo(root.key);
if (result > 0)
root.right = put(root.right, key, value);
else if (result <= 0) {
root.left = put(root.left, key, value);
}
root.size = size(root.left) + size(root.right) + 1;
return root;
}
private Value get(final Node root, final Key key) {
if (root == null)
return null;
final int result = key.compareTo(root.key);
if (result > 0)
return get(root.right, key);
else if (result <= 0)
return get(root.left, key);
return root.value;
}
Your get code needs an == check. Your <= 0 check is grabbing the node to the left where you want < 0. Your == check should return the root.value.
Something like:
private Value get(final Node root, final Key key) {
if (root == null)
return null;
final int result = key.compareTo(root.key);
if (result > 0)
return get(root.right, key);
else if (result < 0)
return get(root.left, key);
else if (result == 0)
return root.value;
else
return null; //key not found
}

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