hashmap反转 (键值对反转)

本文介绍了一个Java示例,展示如何将一个包含重复值的HashMap反转,使得键变为原Map中的值,而值则变为原Map中的键。对于重复的键,采用连接的方式表示多个键值对应关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于HashMap中值是可以重复的,假设原Map为:

        {1=周杰伦, 2=周杰伦, 3=李健, 4=庾澄庆, 5=周杰伦, 6=谢霆锋}

我们希望得到的Map为:
        {庾澄庆=4, 周杰伦=1_2_5, 谢霆锋=6, 李健=3}

import org.junit.Test;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Created by hantao5 on 2018/9/17.
 */
public class ReverseHashMap {
	@Test
	public void test() {
		Map<Integer, String> map = new HashMap();
		map.put(1,"周杰伦");
		map.put(2,"周杰伦");
		map.put(3,"李健");
		map.put(4,"庾澄庆");
		map.put(5,"周杰伦");
		map.put(6,"谢霆锋");
		System.out.println(map);
		Map<String, String> reverseMap = new HashMap<String, String>();
		Iterator it = map.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<Integer, String> next = (Map.Entry<Integer, String>)it.next();
			if (reverseMap.containsKey(next.getValue())) {
				StringBuffer sb = new StringBuffer(reverseMap.get(next.getValue()));
				sb.append("_"+next.getKey());
				reverseMap.put(next.getValue(),sb.toString());

			} else {
				reverseMap.put(next.getValue(),next.getKey()+"");
			}
		}
		System.out.println(reverseMap);
	}
}
<think>嗯,我现在要了解一下HashMap的原理。首先,HashMap是Java中常用的数据结构,对吧?我记得它属于Map接口,用来存储键值对。那它的内部结构是怎样的呢?可能和数组、链表有关? 对了,之前学过数组和链表,数组查询快,插入删除慢;链表相反。那HashMap是不是结合了这两者的优点?比如用数组和链表结合的方式,这样既能快速查找,又能处理冲突? 然后,哈希函数的作用是什么呢?好像是把键(Key)转换成一个数组的索引。比如,当我要存一个键值对的时候,先计算键的哈希值,然后根据这个哈希值找到数组的位置,把值存进去。不过不同的键可能会有相同的哈希值,这时候就会发生哈希冲突。这时候应该怎么处理呢?可能用链表,也就是在同一个数组位置存放一个链表,把冲突的键值对都放在这里。 不过如果链表太长的话,查询效率会下降,变成O(n)。那Java是不是有办法优化这种情况?比如当链表长度超过某个阈值时,转换成红黑树?这样查询效率可以提高到O(log n)。可能是在Java 8之后引入的这个优化,称为“树化”? 另外,HashMap的容量和负载因子也很重要。容量是指数组的大小,负载因子是决定何时扩容的因子。默认负载因子是0.75,当元素数量超过容量乘以负载因子时,就会扩容。扩容的时候,数组大小会翻倍,然后重新计算所有键的位置,这个过程叫做rehash。这样做的目的是减少哈希冲突,保持效率。 那具体的put和get过程是怎样的呢?比如put时,先计算键的哈希值,找到对应的数组下标。如果该位置为空,就直接放入;如果有元素,就遍历链表或红黑树,比较键是否相等(equals方法),相等的话替换值,否则添加到链表末尾或树中。如果链表长度超过8,并且数组长度超过64,就转换为红黑树;否则只是扩容数组。 get的时候也是类似,计算哈希找到数组下标,然后遍历链表或树,用equals方法比较键,找到对应的值返回。 不过,哈希函数的好坏直接影响HashMap的性能。如果哈希函数分布不均匀,会导致很多冲突,降低效率。所以好的哈希算法很重要。Java中的String类的hashCode方法可能经过优化,减少碰撞。 还有,HashMap是非线程安全的,多线程环境下可能会引起死循环等问题,可以使用ConcurrentHashMap来代替。另外,允许null键和null值,但只能有一个null键,因为键必须唯一。 总结的话,HashMap通过数组加链表(和红黑树)的结构,使用哈希函数快速定位,处理冲突,动态扩容来保证高效性能。需要注意哈希函数、负载因子和容量设置,根据实际情况调整。</think>HashMap是Java中基于哈希表实现的键值对存储结构,下面分步骤讲解其核心原理: --- ### 一、数据结构:数组 + 链表/红黑树 HashMap底层是一个`Node<K,V>[]`数组,每个数组位置称为一个**桶(Bucket)**。 - **普通情况**:每个桶存储一个链表,用于解决哈希冲突(不同键映射到同一位置)。 - **树化优化**:当链表长度超过8且数组长度≥64时,链表转为红黑树(时间复杂度从$O(n)$优化为$O(\log n)$)。 --- ### 二、哈希函数:定位桶的位置 1. **计算键的哈希值** 通过`hashCode()`方法获得原始哈希值,再经过扰动函数(异或高16位)减少碰撞概率: ```java static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } ``` 2. **确定桶下标** 哈希值对数组长度取模: $$ \text{index} = \text{hash} \ \& \ (\text{length} - 1) $$ (当`length`是2的幂时,取模等价于按位与操作) --- ### 三、解决哈希冲突 - **链表法**:冲突的键值对以链表形式存储在同一桶中。 - **红黑树法**:链表过长时转为红黑树,提升查询效率。 --- ### 四、核心操作流程 #### 1. 插入数据(put) - **步骤**: 1. 计算键的哈希值,确定桶下标。 2. 若桶为空:直接插入新节点。 3. 若桶非空: - **链表**:遍历链表,若找到相同键则更新值,否则添加到末尾。 - **红黑树**:按树结构插入。 4. 检查是否扩容。 - **树化条件**:链表长度 > 8 且 数组长度 ≥ 64。 #### 2. 获取数据(get) 1. 计算键的哈希值,确定桶下标。 2. 遍历链表或红黑树,通过`equals()`方法匹配键。 3. 返回对应的值,未找到则返回`null`。 --- ### 五、动态扩容机制 - **负载因子(Load Factor)**:默认0.75,表示当元素数量达到容量×0.75时触发扩容。 - **扩容过程**: 1. 新建一个2倍大小的数组。 2. 重新计算所有键的哈希,分配到新桶中(避免链表反转死循环)。 - **目的**:减少哈希冲突,保证高效性。 --- ### 六、关键特性 1. **线程不安全**:多线程操作可能导致数据不一致,需用`ConcurrentHashMap`替代。 2. **允许null键/值**:但最多只能有一个null键。 3. **无序性**:遍历顺序不固定。 --- ### 七、性能优化建议 1. 设置合理的初始容量和负载因子,减少扩容次数。 2. 键对象需正确重写`hashCode()`和`equals()`方法。 3. 避免高频修改操作(如反复插入删除),优先选择不可变对象作为键。 --- 通过上述设计,HashMap在大多数场景下实现了$O(1)$时间复杂度的插入和查询操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值