(四十)用自定义作为HashMap或HashTable的key需要注意哪些问题
一、HashMap=数组+链表
数组存储区间是连续的,占用内存严重,故空间复杂度很大。但数组的查找很快,为O(1)
特点:寻址容易,插入和删除困难
链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但是时间复杂度很大O(n)
特点:寻址困难,插入和删除容易
HashMap:就是将两者综合,做一种寻址容易,插入、删除也容易的数据结构
HashMap用的是链地址法来处理冲突的,可以理解为“链表的数组”
public static void main(String[] args) {
Map map = new HashMap();
map.put("What", "chenyz");
map.put("You", "chenyz");
map.put("Don't", "chenyz");
map.put("Know", "chenyz");
map.put("About", "chenyz");
map.put("Geo", "chenyz");
map.put("APIs", "chenyz");
map.put("Can't", "chenyz");
map.put("Hurt", "chenyz");
map.put("you", "chenyz");
map.put("google", "chenyz");
map.put("map", "chenyz");
map.put("hello", "chenyz");
}
当我们新添加一个元素时,首先我们通过Hash算法计算出这个元素的Hash值的hashcode,通过这个hashcode的值,我们就可以计算出这个新元素应该存放在这个hash表的哪个格子里面,如果这个格子中已经存在元素,那么就把新的元素加入到已经存在格子元素的链表中。
运行上面的程序,我们对HashMap源码进行一点修改,打印出每个key对象的hash值
What-->hash值:8
You-->hash值:3
Don't-->hash值:7
Know-->hash值:13
About-->hash值:11
Geo-->hash值:12
APIs-->hash值:1
Can't-->hash值:7
Hurt-->hash值:1
you-->hash值:10
google-->hash值:3
map-->hash值:8
hello-->hash值:0
计算出来的Hash值分别代表该key应该存放在Hash表中对应数字的格子中,如果该格子已经有元素存在,那么该key就以链表的方式依次放入格子中
二、HashMap的创建
hashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
装载因子:当实际长度与数组长度比为0.75时,数组长度会翻倍。
三、HashMap的插入
public V put(K key, V value) {
if (key == null)
return putForNullKey(value); //解释(1)
int hash = hash(key); //解释(2)
int i = indexFor(hash, table.length); //解释(3)
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}//解释(4)
modCount++;
addEntry(hash, key, value, i); //解释(5)
return null;
}
(1):在HashMap中,我们可以往其中插入为null的key,判断key是否为空,如果为空,则调用putForNullKey函数,putForNullKey的功能为是,查找原来HashMap中key为null的项,如果存在,则用当前的value,替换原来的value,并返回原来的value。如果不存在,则插入。之所以要单独处理,是因为如果不为null,我们需要对key进行hash映射,计算key的hash值,找到当前key在HashMap中的位置,而key为null的时候,直接默认key的hash值为0。
(2):根据某种hash算法,计算key的hash值。
(3):根据key的hash值与hash表的长度,计算当前这个key在hash表中的索引。
(4):这个for循环的功能是:在原来的HashMap中,查找是否存在我现在正要插入的这个key,如果存在,则把此key对应的原来的value用当前这个value替换,并返回旧的value。值得注意的是,从for循环中可以发现,HashMap虽然是基于数组的,但同时也是基于链表的,因为我们知道,Hash表有可能会起冲突,即key的hash值一样,如果我们仅采用数组,那么当hash值一样时,在一个数组元素中,我们不可能存储两个值,因此,HashMap不仅基于数组,也基于链表,即先通过key的hash值,找到key在数组中的位置,然后每个hash相同的key又采用链表存储,因此,我们在用key的hash值定位到数组的位置时,仍然还要使用for循环来查找链表里是否真的存在当前这个key,从for中可以看到,e=e.next,就是找下一个节点。
(5):前面的几个相当于,都是在做前期准备工作,判断key不是null,也确定当前这个key在HashMap中没有存储,接下来就是往这个HashMap插入这个键/值对。
Java面试笔试宝典这这样说的:
在向HashMap中添加键值对<key,value>时,需要经过如下几个步骤:
首先,调用key的hashCode()方法产生一个hash值h1
若这个h1在HashMap中不存在,则直接将<key,value>添加到HashMap中
若h1已存在,那么就要找出HashMap中所有hash值为h1的key(横向)
调用key的equals()方法判断当前添加的key值存在(true),则将新value覆盖掉旧的value值
调用key的equals()方法判断当前添加的key不存在(false),说明新增加的key在HashMap中不存在,因此会在
HashMap中创建新的映射关系。
举例一
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Iterator;
public class Student {
public static void main(String []args) {
HashMap<String,String> hm=new HashMap<String,String>();
hm.put("k1","v1");
hm.put("k1","newv1");
Iterator<Entry<String,String>> it=hm.entrySet().iterator();
while(it.hasNext())
{
Entry<String,String> e=it.next();
System.out.println(e.getKey()+"----"+e.getValue());
}
}
}
//结果: k1----newv1
解释:<k1,v1>存入,<k1,newv1>存入时,先根据key的hashCode()生成hash,存在,则横向遍历,k1.equal(k1)==true【比较的是两个字符串,String重写了equals()】,新值替换旧值。所以hash表size==1
举例二:
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Iterator;
public class Student {
String name;
String id;
public Student(String name,String id)
{
this.name=name;
this.id=id;
}
public String toString()
{
return "id="+id+"---"+"name="+name;
}
public static void main(String []args) {
HashMap<Student,String> hm=new HashMap<Student,String>();
hm.put(new Student("Spig","001"),"sp1");
hm.put(new Student("Spig","001"),"sp2");
Iterator<Entry<Student,String>> it=hm.entrySet().iterator();
while(it.hasNext())
{
Entry<Student,String> e=it.next();
System.out.println(e.getKey()+"----"+e.getValue());
}
}
结果:id=001---name=Spig----sp1
id=001---name=Spig----sp2
解释:<new Student("Spig","001"),"sp1">存入,<new Student("Spig","001"),"sp2">存入时,先根据key的hashCode()生成hash,不存在,存入新位置。所以hash表size==2
因此,若要根据自己的逻辑判定是否为同一个key,则要重写hashCode()和equal方法
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Iterator;
public class Student {
String name;
String id;
public Student(String name,String id)
{
this.name=name;
this.id=id;
}
public String toString()
{
return "id="+id+"---"+"name="+name;
}
public int hashCode(){
System.out.println(id.hashCode());
return id.hashCode();
}
public boolean equals(Object obj){
Student s=(Student)obj;
if(s.id.equals(this.id))
return true;
else
return false;
}
public static void main(String []args) {
HashMap<Student,String> hm=new HashMap<Student,String>();
hm.put(new Student("Spig","001"),"sp1");
hm.put(new Student("Spig","001"),"sp2");
Iterator<Entry<Student,String>> it=hm.entrySet().iterator();
while(it.hasNext())
{
Entry<Student,String> e=it.next();
System.out.println(e.getKey()+"----"+e.getValue());
}
}
结果:47665
47665
id=001---name=Spig----sp2
HashSet:元素唯一性hashCode、equals,元素唯一性执行过程解释:https://blog.csdn.net/jiangshangchunjiezi/article/details/75212185
参考:https://blog.csdn.net/strongyoung88/article/details/47429559
http://www.blogjava.net/dongbule/archive/2011/02/15/344387.html