While the other answers explain what is going on, OP's comments on those answers leads me to think a different angle of explanation is required.
Simplified example
Let's say you are going to toss 10 strings into a hash map: "A", "B", "C", "Hi", "Bye", "Yo", "Yo-yo", "Z", "1", "2"
You are using HashMap
as your hash map instead of making your own hash map (good choice). Some of the stuff below will not use HashMap
implementation directly but will approach it from a more theoretical and abstract point of view.
HashMap
does not magically know that you are going to add 10 strings to it, nor does it know what strings you will be putting into it later. It has to provide places to put whatever you might give to it... for all it knows you are going to put 100,000 strings in it - perhaps every word in the dictionary.
Let's say that, because of the constructor argument you chose when making your new HashMap(n)
that your hash map has 20 buckets. We'll call them bucket[0]
through bucket[19]
.
map.put("A", value);
Let's say that the hash value for "A" is 5. The hash map can now do bucket[5] = new Entry("A", value);
map.put("B", value);
Assume hash("B") = 3. So, bucket[3] = new Entry("B", value);
map.put("C"), value);
- hash("C") = 19 - bucket[19] = new Entry("C", value);
map.put("Hi", value);
Now here's where it gets interesting. Let's say that your hash function is such that hash("Hi") = 3. So now hash map wants to do bucket[3] = new Entry("Hi", value);
We have a problem! bucket[3]
is where we put the key "B", and "Hi" is definitely a different key than "B"... but they have the same hash value. We have a collision!
Because of this possibility, the HashMap
is not actually implemented this way. A hash map needs to have buckets that can hold more than 1 entry in them. NOTE: I did not say more than 1 entry with the same key, as we cannot have that, but it needs to have buckets that can hold more than 1 entry of different keys. We need a bucket that can hold both "B" and "Hi".
So let's not do bucket[n] = new Entry(key, value);
, but instead let's have bucket
be of type Bucket[]
instead of Entry[]
. So now we do bucket[n].add( new Entry(key, value) );
So let's change to...
bucket[3].add("B", value);
and
bucket[3].add("Hi", value);
As you can see, we now have the entries for "B" and "Hi" in the same bucket. Now, when we want to get them back out, we need to loop through everything in the bucket, for example, with a for loop.
So the looping is present because of the collisions. Not collisions of key
, but collisions of hash(key)
.
Why do we use such a crazy data structure?
You might be asking at this point, "Wait, WHAT!?! Why would we do such a weird thing like that??? Why are we using such a contrived and convoluted data structure???" The answer to that question would be...
A hash map works like this because of the properties that such a peculiar setup provides to us due to the way the math works out. If you use a good hash function which minimizes conflicts, and if you size your HashMap
to have more buckets than the number of entries that you guess will be in it, then you have an optimized hash map which will be the fastest data structure for inserts and queries of complex data.
Your HashMap may be too small
Since you say you are often seeing this for-loop being iterated over with multiple elements in your debugging, that means that your HashMap
might be too small. If you have a reasonable guess as to how many things you might put into it, try to set the size to be larger than that. Notice in my example above that I was inserting 10 strings but had a hash map with 20 buckets. With a good hash function, this will yield very few collisions.
Note:
Note: the above example is a simplification of the matter and does take some shortcuts for brevity. A full explanation is even slightly more complicated, but everything you need to know to answer the question as asked is here.