字节面试Java基础部分——HashMap
字节面试Java基础部分
面试管:Java应该很熟悉吧,接下来问你几个Java基础问题:
HashMap 是什么样的数据结构
JDK 7 中,HashMap 由“数组+链表”组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。
在 JDK 8 中,HashMap 由“数组+链表+红黑树”组成。
HashMap 是干什么用的
HashMap 在 Java 中主要用于以下几个方面:
键值对存储:HashMap 允许将数据以键值对的形式存储。这使得通过键快速查找、插入和删除相关值变得非常高效。
快速查找:由于其基于哈希表的实现,HashMap 可以在平均常数时间内完成插入、删除和查找操作,适用于需要频繁访问数据的场景。
实现字典功能:HashMap 可以用作字典,存储相关信息,例如将单词作为键,定义或翻译作为值。
缓存机制:可以用 HashMap 来实现简单的缓存,存储计算结果以提高性能,避免重复计算。
数据分组:可以将相同类别的数据分组,例如以学生的 ID 为键,以学生的成绩为值,快速访问某个学生的成绩。
java HashMap 对于存入的类有什么要求吗 譬如我存入一个Object 对于我存入的Object 有什么要求吗
在 Java 中,当你使用 HashMap
存储对象(无论是自定义类还是其他类型的对象)时,有几个重要的要求和注意事项:
1. hashCode() 方法
-
存入
HashMap
的对象(作为键)必须正确实现hashCode()
方法。哈希表的存储和查找依赖于哈希值。对象的哈希值应该尽可能均匀分布,以减少冲突。 -
默认情况下,
Object
类的hashCode()
方法是基于对象的内存地址生成的。若你想使用自定义类作为键,需要重写hashCode()
方法。
2. equals() 方法
- 你还需要重写
equals()
方法,以确保当两个对象被认为是相等时,它们的哈希值也相等。HashMap
会根据键的相等性来进行查找和删除操作。
3. 不可变性
- 虽然
HashMap
允许存储可变对象,但如果你将可变对象作为键,一旦这些对象的状态发生改变,可能会导致HashMap
中的查找和删除操作失败。因此,最好将不可变对象(如字符串、包装类等)或在使用后不再修改的对象作为键。
示例代码
下面是一个示例,展示了如何为自定义类实现 hashCode()
和 equals()
方法,以便可以用作 HashMap
的键:
import java.util.HashMap;
import java.util.Objects;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
}
public class HashMapExample {
public static void main(String[] args) {
HashMap<Person, String> map = new HashMap<>();
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Bob", 25);
map.put(p1, "Engineer");
map.put(p2, "Designer");
System.out.println(map.get(new Person("Alice", 30))); // 输出 "Engineer"
}
}
总结
- 重写
hashCode()
和equals()
方法:确保HashMap
正确处理键的相等性。 - 考虑不可变性:尽量使用不可变对象作为键,以避免意外的问题。
java HashMap 中的Key是用户自定义类A的对象a,Value假设为123 现在用户要取出123 一定需要拿到对象a吗
在 HashMap
中,如果键是用户自定义类 A
的对象 a
,那么要通过键取出相应的值(例如 123
),你必须提供一个与 a
相等的键对象。也就是说,不能直接用 123
来获取对应的值,而是需要用一个与 a
相同的 A
类的对象。
具体原因
-
哈希存储机制:
HashMap
使用键的哈希值来确定值的位置。当你调用get()
方法时,需要提供一个键,这个键的哈希值将用于查找。 -
equals() 方法:为了能够找到相应的值,提供的键对象必须与原来存入的键对象在逻辑上相等。也就是说,
equals()
方法必须返回true
。
示例
假设你有以下的自定义类 A
:
import java.util.HashMap;
import java.util.Objects;
class A {
private String name;
public A(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
A a = (A) obj;
return Objects.equals(name, a.name);
}
}
public class HashMapExample {
public static void main(String[] args) {
HashMap<A, Integer> map = new HashMap<>();
A keyA = new A("example");
map.put(keyA, 123);
// 要取出 123,必须提供与 keyA 相同的对象
A searchKey = new A("example");
Integer value = map.get(searchKey); // 将返回 123
System.out.println(value); // 输出 123
}
}
总结
- 需要相等的对象:要从
HashMap
中取出值,必须使用与存入时相同或逻辑上等价的键对象。 - 提供相同状态的对象:即使是不同的对象实例,只要它们的状态(字段值)相同,且正确重写了
hashCode()
和equals()
方法,就可以取出对应的值。
HashMap 是线程安全的吗
不是
你是如何定义线程安全的
线程安全是程序设计中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的公用变量,使程序功能正确完成。
HashMap为什么不是线程安全的
HashMap
不是线程安全的,主要原因如下:
1. 数据结构设计
HashMap
是基于哈希表实现的,主要使用数组和链表(或红黑树)来存储数据。当多个线程同时对同一个HashMap
进行读写操作时,可能会发生数据竞争,导致不一致性。
2. 状态变化
- 在
HashMap
中,插入、删除和更新操作可能会改变其内部状态。例如,当一个元素被插入时,可能需要重新计算哈希值并调整数组的大小或结构(如链表或树)。如果另一个线程在此期间也对HashMap
进行操作,就可能导致数据丢失或数据结构损坏。
3. 非原子性操作
HashMap
的许多操作(如put
和remove
)并不是原子的,可能由多个步骤组成。在多线程环境中,如果一个线程正在执行这些操作,另一个线程可能会同时修改数据,导致结果不可预测。
4. 并发问题
- 当多个线程同时对
HashMap
进行读和写操作时,可能会出现并发问题,例如:- 数据丢失:一个线程可能覆盖另一个线程刚插入的值。
- 抛出异常:例如,在扩容时可能会抛出
ConcurrentModificationException
。 - 读取不一致:一个线程读取数据时,另一个线程可能在修改数据,导致读取到的内容不准确。
解决方案
如果需要在多线程环境中使用 HashMap
,可以考虑以下几种替代方案:
-
使用
Collections.synchronizedMap
:
将HashMap
包装为同步的 Map。Map<K, V> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
-
使用
ConcurrentHashMap
:
ConcurrentHashMap
是 Java 提供的线程安全的哈希表实现,支持高并发访问。ConcurrentHashMap<K, V> concurrentMap = new ConcurrentHashMap<>();
-
使用显式锁:
使用ReentrantLock
等锁机制手动控制对HashMap
的访问。
总结来说,HashMap
不是线程安全的,因为它的设计没有考虑并发访问的情况。对于需要线程安全的场景,应该使用其他线程安全的集合类。
参考文献
相关回答参考ChatGPT
原创不易
转载请标明出处
如果对你有所帮助 别忘啦点赞支持哈