HashSet和HashMap的底层实现——哈希表、散列表
2016-06-27 22:55
309 查看
java.lang
类 Object
java.lang.Object
int | hashCode() 返回该对象的哈希码值。 |
boolean | equals(Object obj) 指示某个其他对象是否与此对象“相等”。 |
Java最基本的两种数据结构:数组和链表的区别:
数组易于快速读取(通过for循环),不便存储(数组长度有限制);链表易于存储,不易于快速读取。
哈希表的出现是为了解决链表访问不快速的弱点,哈希表也称散列表。
HashSet是通过HasMap来实现的,HashMap的输入参数有Key、Value两个组成,在实现HashSet的时候,保持HashMap的Value为常量,相当于在HashMap中只对Key对象进行处理。
HashMap的底层是一个数组结构,数组中的每一项对应了一个链表,这种结构称“链表散列”的数据结构,即数组和链表的结合体;也叫散列表、哈希表。
HahMap存储对象的过程如下:
1、对HahMap的Key调用hashCode()方法,返回int值,即对应的hashCode;
2、把此hashCode作为哈希表的索引,查找哈希表的相应位置,若当前位置内容为NULL,则把hashMap的Key、Value包装成Entry数组,放入当前位置;
3、若当前位置内容不为空,则继续查找当前索引处存放的链表,利用equals方法,找到Key相同的Entry数组,则用当前Value去替换旧的Value;
4、若未找到与当前Key值相同的对象,则把当前位置的链表后移(Entry数组持有一个指向下一个元素的引用),把新的Entry数组放到链表表头;
HashMap的内存实现结构如下:
注意:在哈希表的数组结构中Entry对象已经超出0.75的容量,因此需要重新申请内存了。
二、使用示例
利用HashMap往散列表中存放<Key, Value>,并根据存入的Key,返回Value值。
①Key为基本数据类型
import java.util.*;
class MapDemo
{
public static void main(String[] args)
{
Map map = new HashMap();
map.put("a",new Integer(1));
map.put("b",new Integer(2));
map.put("c",new Integer(3));
Set set= map.keySet();
for(Iterator iter = set.iterator();iter.hasNext();)
{
String key = (String)iter.next();
Integer value = (Integer)map.get(key);
System.out.print(key+", "+value);
System.out.println();
}
System.out.println(map.get("a"));
System.out.println(map.get("b"));
System.out.println(map.get("c"));
}
}
结果:
b, 2
c, 3
a, 1
1
2
3
②Key为对象引用类型
import java.util.* ;
class Person
{
private String name ;
private int age ;
Person(String name,int age)
{
this.name = name ;
this.age = age ;
}
public String toString()
{
return "姓名:"+this.name+",年龄:"+this.age ;
}
}
public class HashCodeDemo
{
public static void main(String args[])
{
HashMap hm = new HashMap() ;
hm.put(new Person("张三",20),"张三") ;
System.out.println(hm.get(new Person("张三",20))) ;
}
}
结果:null
原因:两次new Person("张三",20)实例化的引用不同,因此在查找哈希表的时候,index也不同。
修改成如下:
public class HashCodeDemo
{
public static void main(String args[])
{
HashMap hm = new HashMap() ;
Person p1 = new Person("张三",20);
hm.put(p1,"张三") ;
System.out.println(hm.get(p1)) ;
}
}
结果:张三
或者进行强制修改hashCode()方法和equals()方法,另hashCode()返回固定的值(即,每次对Key进行哈希编码的值都是一样的),另equals()方法返回true(即在哈希表中找到了与当前Key对应的Value)。
为什么重写了hashCode()方法后,必须要重写equals()方法呢?
原因是,哈希码相同仅代表Key相同,不代表Entry数组里面的Value值相同。
class Person
{
private String name ;
private int age ;
Person(String name,int age)
{
this.name = name ;
this.age = age ;
}
public String toString()
{
return "姓名:"+this.name+",年龄:"+this.age ;
}
public boolean equals(Object obj)
{
return true ;
}
public int hashCode()
{
return 20 ;//返回任意一个int值都可以
}
}
public class HashCodeDemo
{
public static void main(String args[])
{
HashMap hm = new HashMap() ;
hm.put(new Person("张三",20),"张三") ;
System.out.println(hm.get(new Person("张三",20))) ;
}
}
结果:张三
分析:上例中利用重写hashCode()方法,返回相同的哈希码,这时如果把System.out.println(hm.get(new Person("张三",20))) ;修改成System.out.println(hm.get(new Person("李四",20))) ;后返回的哈希码也是相同的。在当前哈希码对应index上查找Entry数组组成的链表,因为equals()方法返回的是true,所以结果就找到了hm.put(new Person("张三",20),"张三") ;中的Value值——张三。
若再放入一个Entry数组 hm.put(new Person("张三",20),"王五") ;结果会变成怎么样呢?
public class HashCodeDemo
{
public static void main(String args[])
{
HashMap hm = new HashMap() ;
hm.put(new Person("张三",20),"张三") ;
hm.put(new Person("张三",20),"王五") ;
System.out.println(hm.get(new Person("张三",20))) ;
}
}
结果:王五
为什么不是 张三?
在数据库结构中,程序默认的对最近一次放入的数据具有较高的优先级,因为hm.put(new Person("张三",20),"王五") ;是后放入的,所以返回
王五。
若调换一下顺序:
public class HashCodeDemo
{
public static void main(String args[])
{
HashMap hm = new HashMap() ;
hm.put(new Person("张三",20),"王五") ;
hm.put(new Person("张三",20),"张三") ;
System.out.println(hm.get(new Person("张三",20))) ;
}
}
结果:张三
在复写equals()方法时,返回设定为false:
public boolean equals(Object obj)
{
return false ;
}
结果:null
原因:虽然hashCode()返回值相同,即找到了数组index,但在当前索引的链表中,没有找到Key相同的Entry包,所以最后返回null。
总结:在查找哈希表中的对象时,首先根据Key确定哈希码,以哈希码作为数组的索引查找对应位置的链表,从链表头开始查找有相同的Key值的Entry包,找到后返回映射对象。链表上多个Entry包的哈希码可以相同,但Value值肯定不相同。
相关文章推荐
- json,jsonObject , jsonArray 详解
- VIJOS 1516 N连环
- android消息通知Noticication使用详解
- Hadoop(hadoop,HBase)组件import到eclipse
- Android 使用Socket实现服务器与手机客户端的长连接五:使用队列封装请求
- JavaScript强化教程 - 六步实现贪食蛇
- 设置Intent.FLAG_ACTIVITY_NEW_TASK和Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED对Activity Stack的影响
- mysql 主从配置,实现读写分离
- Gesture 通过手势实现翻书效果
- 进程和线程的区别
- java.lang.IllegalStateException:Web app root system property already set to different value 错误原因及解决 Log4j
- 无缝轮播图
- 关闭TV的效果
- Mysql 主从复制(AB复制)
- What are the advantages of semi-supervised learning over supervised and unsupervised learning?
- java基础篇(七)super关键字
- eclipse.ini相关配置
- ubuntu 11.10 安装apache2 tomcat6
- JavaScript强化教程——AJAX
- Unbound classpath container: 'JRE System Library [jdk17060]' in project ***