散列和散列码(1)
一.前言
1.1 ==
符号
==
比较的对象内存地址,也就是两个引用指向的地址,相同即指向同一个对象则返回true。Object中的equals(Object obj)
方法默认使用的是==
方法比较两个对象:
public boolean equals(Object obj) {
return (this == obj);
}
1.2散列数据结构中键类的equals()、hashCode()
方法必须重载,否则这些数据结构便不能正确处理键;
1.3重载equals(Object obj)
原则
重载的equals
方法需要满足一下条件:
- 自反性:x.equals(x)返回true;
- 对称性:x.equals(y)与y.equals(x)返回值相同;
- 传递性:x.equals(y)为true且y.equals(z)为true,则x.equals(y)应该为true;
- 一致性:用于比较等价的信息未变则不管调用多少次x.equals(y)一直不应该改变;
- x不为null,则x.equals(null)一定为false,否则为TRUE;
1.4 instanceof关键字:object instanceof ClassZ
instanceof
关键字会先检查左侧对象是否为null,为null则返回false;
二.基本思想
2.1散列速度
如下示例SlowMap慢的原因在于对于键的查询使用的是线性查询——线性查询是最慢的查询方式。
对于键的查询,保持键的排序而且使用Collections.binarySearch()
进行查询解决速度问题的方案之一。
散列的思想是:数组是最快的数据结构将键的信息保存在数组中,键调用方法int hashCode()
返回的数字(即散列码)就是数组下标。
因为Map可以保存数量不确定的值,而数组不能调整容量,因此我们允许不同键产生相同下标,使用“外部链接”解决冲突,即数组保存list,如下图:
因此一次查询流程是:
- 使用对象计算散列码,然后O(1)时间找打数组下标;
- 如果找到了例如上图下标为11的位置,则对list使用
equals()
方法进行先行的查询,找到对象对应的确切位置,也就是我们要找的键信息;
以上,只对很少的元素进行比较,这也是HashMap速度快的原因,简单实现见第二块代码区。
package containers;
import java.util.*;
public class SlowMap<K,V> extends AbstractMap<K,V> {
private List<K> keys = new ArrayList<>();
private List<V> values=new ArrayList<>();
@Override
public V put(K key, V value) {
V oldValue=get(key);//如果key不存在则返回null;
if(!keys.contains(key)){//不存在则添加
keys.add(key);
values.add(value);
}else {//存在着覆盖
values.set(keys.indexOf(key), value);
}
return oldValue;
}
@Override
public V get(Object key){
if(!keys.contains(key)) {//不存在则返回null
return null;
}
return values.get(keys.indexOf(key));// fixme 第几个key就返回第几个key,不管key的具体内容;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K,V>> set=new HashSet<>();
Iterator<K> ki=keys.iterator();
Iterator<V> vi=values.iterator();
while (ki.hasNext()){
set.add(new SimpleEntry<K, V>(ki.next(),vi.next()));
}
return set;
}
}
HashMap的简单实现
package containers;
import java.util.*;
/**
* @author 杜艮魁
* @date 2018/1/25
*/
public class SimpleHashMap<K,V> extends AbstractMap<K,V>{
//为hash_table设置一个初始值:通常使用质数来保证散列均匀
static final int SIZE=997;
//存放键值对,但是key决定存放位置:buckets是数组,而且元素时链表
LinkedList<SimpleEntry<K,V>> buckets[]=new LinkedList[SIZE];
@SuppressWarnings("unchecked")
@Override
public V put(K key, V value) {
V oldValue=null;
int index=Math.abs(key.hashCode())%SIZE;//fixme key值hashCode信息在数组中存放位置,也就是数组下标
if(buckets[index]==null)//如果数组这个位置链表没有节点,则对这个位置头节点进行初始化
buckets[index]=new LinkedList<>();
LinkedList<SimpleEntry<K,V>> bucket=buckets[index];//bucket指向元素要存放的数组下标对应的头结点
SimpleEntry<K,V> pair=new SimpleEntry<K, V>(key,value);//要存放的键值对放进数据存放形式的元素里
boolean found=false;//数据是否已经出现在了Map中
ListIterator<SimpleEntry<K,V>> it=bucket.listIterator();//遍历键对应的数组位置链表,将其放进list中
while(it.hasNext()){//遍历这个位置的list
SimpleEntry<K,V> iPair=it.next();
if(iPair.getKey().equals(key)){//如果键equals相等则新值替代旧值,则—之前通过找数组下标的hashCode()已经相等
oldValue=iPair.getValue();
it.set(pair);//用新值代替旧值
found=true;
break;
}
}
if(!found){//如果没有找到,则添加到末尾
buckets[index].add(pair);
}
return oldValue;
}
@Override
public V get(Object key) {//注意参数不是K
int index=Math.abs(key.hashCode())%SIZE;
if(buckets[index]==null) return null;//如果定位到的链表为null,则返回null
for (SimpleEntry<K,V> iPair:buckets[index])//否则遍历这个链表,比较key值
if(iPair.getKey().equals(key))
return iPair.getValue();
return null;
}
@Override
public Set<Entry<K, V>> entrySet() {
Set<Entry<K,V>> set=new HashSet<>();
for (LinkedList<SimpleEntry<K,V>>bucket:buckets) {//定位数组位置
if(bucket==null) continue;//如果这个数组所在
for (SimpleEntry<K,V> mpair:bucket)//不为空的话则遍历里边元素放进set
set.add(mpair);
}
return set;
}
}