在Java编程语言中,最基本的结构就是两种,一种是数组,一种是模拟指针(引用),所有的数据结构都可以用这两个基本结构构造,HashMap也一样。当程序试图将多个 key-value 放入 HashMap 中时,以如下代码片段为例:
HashMap<String,Object> m=new HashMap<String,Object>();
m.put("a", "rrr1");
m.put("b", "tt9");
m.put("c", "tt8");
m.put("d", "g7");
m.put("e", "d6");
m.put("f", "d4");
m.put("g", "d4");
m.put("h", "d3");
m.put("i", "d2");
m.put("j", "d1");
m.put("k", "1");
m.put("o", "2");
m.put("p", "3");
m.put("q", "4");
m.put("r", "5");
m.put("s", "6");
m.put("t", "7");
m.put("u", "8");
m.put("v", "9");
HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。当程序执行 map.put(String,Obect)方法 时,系统将调用String的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。源码如下:
Java代码
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//判断当前确定的索引位置是否存在相同hashcode和相同key的元素,如果存在相同的hashcode和相同的key的元素,那么新值覆盖原来的旧值,并返回旧值。
//如果存在相同的hashcode,那么他们确定的索引位置就相同,这时判断他们的key是否相同,如果不相同,这时就是产生了hash冲突。
//Hash冲突后,那么HashMap的单个bucket里存储的不是一个 Entry,而是一个 Entry 链。
//系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),
//那系统必须循环到最后才能找到该元素。
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
上面程序中用到了一个重要的内部接口:Map.Entry,每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可.HashMap程序经过我改造,我故意的构造出了hash冲突现象,因为HashMap的初始大小16,但是我在hashmap里面放了超过16个元素,并且我屏蔽了它的resize()方法。不让它去扩容。这时HashMap的底层数组Entry[] table结构如下:
Hashmap里面的bucket出现了单链表的形式,散列表要解决的一个问题就是散列值的冲突问题,通常是两种方法:链表法和开放地址法。链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。java.util.HashMap采用的链表法的方式,链表是单向链表。形成单链表的核心代码如下:
Java代码
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
bsp;
上面方法的代码很简单,但其中包含了一个设计:系统总是将新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处——如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链),如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序代码的 e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链。
HashMap里面没有出现hash冲突时,没有形成单链表时,hashmap查找元素很快,get()方法能够直接定位到元素,但是出现单链表后,单个bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。
当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 0.75,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap 的 get() 与 put() 方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。
相关推荐
为了解决哈希碰撞问题,HashMap需要重写hashCode和equals方法,以确保两个不同的Key具有不同的哈希码和equals结果。 4. equals方法和hashCode方法的关系 equals方法和hashCode方法是两个相关的方法,equals方法...
3. 如何避免HashMap中的哈希碰撞? 答:通过良好的键的hashCode()实现减少哈希冲突,以及使用链表/红黑树处理哈希冲突。 4. HashMap与Hashtable的区别? 答:HashMap非线程安全,而Hashtable是线程安全的;HashMap...
9. ** equals() 和 hashCode()**:插入HashMap的键对象必须正确地重写`equals()`和`hashCode()`方法,以确保键的比较逻辑与哈希码的计算一致,否则可能会导致预期之外的行为。 10. **resize()**:当HashMap达到其...
在HashMap中,我们通常使用对象的`hashCode()`方法来生成哈希值。为了确保不同的键能得到不同的哈希值,哈希函数需要具有良好的分散性。 当两个键的哈希值相同时,即发生了哈希冲突。HashMap通过链表来解决这个问题...
当一个键值对被添加到HashMap中时,键的哈希码(hashCode)被计算出来,这个哈希码用于确定键值对在内部数组中的位置。如果两个键的哈希码相同,HashMap会使用equals()方法来区分它们,这就是所谓的哈希碰撞。为了...
2. **哈希函数**:HashMap使用键对象的`hashCode()`方法生成哈希码,然后通过取模运算将哈希码映射到数组的索引位置。哈希函数的质量直接影响了冲突的可能性和性能。 3. **负载因子**:HashMap有一个名为`...
哈希函数用的是java中String.hashCode()算法(经实际验证其碰撞率极低且相近的文本散列值相邻,存取的效率更高.)。可自动无限增加容量(内存允许)。3、连续10万次不同内容存取效率为,存10万次共耗时约280ms ,取10万次...
这是为了在跟数组长度一起做&与运算计算索引下标时,得到相对更加均匀分撒的结果,这样根据不同 key 得出的数组索引下标尽可能分撒的话,就不容易发生哈希碰撞,也就降低了一开始往 HashMap 中添加数据时链表的产生...
JDK8引入红黑树主要是为了解决HashMap在高哈希碰撞情况下的性能问题。当链表长度超过一定阈值(默认为8个元素)时,链表的查找效率会低于红黑树,因此将链表转换为红黑树以保证其时间复杂度。 ##### JDK8中HashMap...
- **哈希函数**:HashMap使用`hash(key.hashCode())`来计算键的哈希值,这有助于将键均匀地分布在数组中。 - **碰撞处理**:如果两个键的哈希值相等,它们会被放入数组的同一索引位置。此时,它们会在链表中按照...
- **哈希碰撞处理**:HashMap是如何处理哈希碰撞的? - **get元素**:HashMap是如何get元素的? - **HashMap和HashTable的区别**:列举HashMap和HashTable的主要区别。 - **HashMap和HashSet的区别**:解释HashMap和...
`HashMap`的内部实现会调用键对象的`hashCode()`方法,然后对其进行一定的转换操作,以便更好地分布数据并减少冲突。 #### 三、高位扩展与低位扩散 在`HashMap`中,对于计算得到的哈希码,官方文档提到一个非常...
两个对象的hashCode相同会导致它们在HashMap中位于同一个索引位置,形成哈希碰撞。HashMap使用链表处理碰撞,碰撞后的对象会被存储在链表中。 4. **重写hashCode和equals方法的原因** 为了确保键的唯一性,需要重写...
- **集合框架**:在 `HashSet`, `HashMap`, `Hashtable` 等集合中,`hashCode` 方法用于快速查找和存储对象。 - **缓存机制**:在缓存中,`hashCode` 和 `equals` 方法一起用于识别缓存项,以便在需要时快速找到它们...
- 哈希函数:HashMap使用键对象的hashCode()方法生成哈希值,再通过取模运算确定在数组中的位置。 - 链表与碰撞:当多个键的哈希值相同,它们会被链接到同一个数组槽位形成链表,解决哈希冲突。 - 负载因子:...
如果A元素的hashcode值在HashMap中不存在,则直接插入。否则,接着调用equals()方法,判断A元素在容器中是否已经存在。hashcode()的时间复杂度为O(1),equals()方法的时间复杂度为O(m),整体的时间复杂度就是:O(1) ...
在 Java 中,哈希码经常被用于实现散列表(如 `HashMap` 和 `HashSet`)。为了确保散列表的正确性,重写 `equals` 方法时,通常也需要重写 `hashCode` 方法,以保证两个相等的对象拥有相同的哈希码。 在示例代码中...
3. 一个好的hashCode函数应该将不同的对象分布到所有可能的hashCode值上,以避免哈希碰撞。 在Java中,hashCode方法的实现方式有多种,例如,使用String类的hashCode方法,使用数学表达式来计算hashCode值。同时,...