Java中HashMap集合在JDK1.8版本时基于 数组+链表+红黑树实现的,查询效率为O(1)
使用内部类Entry来描述键值对对象
目录
创建了StudentEntity类并重写了toString方法,创建demo进行测试:
1.HashMap的key是否可以为自定义对象?
答案:可以
public class demo {
public static void main(String[] args) {
HashMap<StudentEntity, String> hashMap = new HashMap<>();
StudentEntity s1 = new StudentEntity("Mercedes", "22");
StudentEntity s2 = new StudentEntity("Mercedes", "22");
hashMap.put(s1,"OK");
hashMap.put(s2,"OK");
for (Map.Entry<StudentEntity,String> studentEntityStringEntry:
hashMap.entrySet()) {
System.out.println("key:"+studentEntityStringEntry.getKey().toString()+
" value:"+studentEntityStringEntry.getValue());
}
}
}
程序运行结果:
2.HashMap集合存储数据时是有序还是无序?
答案:无序,其底层采用散列机制存放数据
测试时我发现一个问题,当HashMap的key值被设置为Integer类型时,HashMap的存储就时有序的。
public class demo {
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
for (int i = 0; i < 100; i++) {
hashMap.put("k"+i,"v"+i);
}
hashMap.forEach((k,v)->{
System.out.println(k+","+v);
});
}
}
以上代码采用了一种比较新颖的Java集合基于范围的for循环遍历:
hashMap.forEach((k,v)->{
System.out.println(k+","+v);
});
可替换为lambda表达式的方式:
hashMap.forEach((k,v)-> System.out.println(k+","+v));
程序运行结果:
3.HashMap key是否可以为null值?
答案:可以 null键值存放在HashMap底层数组的index为0的位置(HashTable的key不允许存放null值)
hashMap.put(null,"Mercedes");
hashMap.forEach((k,v)-> System.out.println(k+","+v));
可以为null值。
4.什么是hash冲突问题?如何解决hash冲突问题?
答:hash冲突问题是指:当要通过调用put方法存入两个对象时,这两个Entry对象的键的hashCode值相同,通过hashCode计算出的index值相同,所以会产生存入覆盖问题
public class demo {
public static void main(String[] args) {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("a","a");
hashMap.put(97,"97");
System.out.println(hashMap.get("a"));
}
}
程序运行结果:
可以看到Java提供的集合已经解决了hash冲突问题(本人想到一种解决冲突办法:将HashMap泛化出来再使用)
JDK1.7是这样解决的:
将hashCode值相同的key存放在同一张链表中
以下是自定义的HashMap
package lzx4;
public class MyHashMap<K,V> {
private Entry<K,V>[] entries = new Entry[10000]; //存放键值对的数组
class Entry<K, V> {
K k;
V v;
int hash; //当前对象的hashCode
Entry<K,V> next; //当前对象的next指针
public Entry(K k, V v,int hash) {
this.k = k;
this.v = v;
this.hash = hash;
}
}
public void put(K k,V v){
int hash = k.hashCode();
int index = hash%entries.length;
Entry oldEntry = entries[index]; //拿到该对象后判断是否为空,为空则证明该位置是第一次存对象
if(oldEntry == null){ //否则就将上一次存放的对象的next指针指向当前对象
entries[index] = new Entry<>(k,v,hash);
}else{
oldEntry.next = new Entry<>(k,v,hash);
}
}
public V get(K k){
int hash = k.hashCode();
int index = hash%entries.length;
for(Entry<K,V> entry = entries[index];entry!=null;entry = entry.next){
if(entry.hash == hash && (entry.k == k || entry.k.equals(k))){
return entry.v;
}
}
return null;
}
public static void main(String[] args) {
MyHashMap<Object, Object> myHashMap = new MyHashMap<>();
myHashMap.put("a","a");
myHashMap.put(97,"97");
System.out.println(myHashMap.get("a"));
}
}
get()方法思路:先得到传入的key的hashCode,得到index后找到该条链表所在的位置,然后通过next指针向后循环,先比较当前对象的k的hashCode和传入的key的hashCode是否相等,if中这个语句
entry.k == k || entry.k.equals(k))
前面entry.k == k方便自定义类型比较,后面的equals方法在本案例中配合hash值来比较判断该对象位于链表中的哪个位置,在这里,equals方法单纯就是比较值一不一样。