HashSet和HashMap的底层实现——哈希表、散列表

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值肯定不相同。

阅读更多
换一批

没有更多推荐了,返回首页