散列和散列码

Posted coderDu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了散列和散列码相关的知识,希望对你有一定的参考价值。

一.前言

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方法需要满足一下条件:

  1. 自反性:x.equals(x)返回true;
  2. 对称性:x.equals(y)与y.equals(x)返回值相同;
  3. 传递性:x.equals(y)为true且y.equals(z)为true,则x.equals(y)应该为true;
  4. 一致性:用于比较等价的信息未变则不管调用多少次x.equals(y)一直不应该改变;
  5. 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,如下图:
技术分享图片

因此一次查询流程是:

  1. 使用对象计算散列码,然后O(1)时间找打数组下标;
  2. 如果找到了例如上图下标为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;
    }
}


以上是关于散列和散列码的主要内容,如果未能解决你的问题,请参考以下文章

MessageDigest简介

Java集合—散列与散列码

Java集合:散列与散列码

MessageDigest简介

java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列

散列算法与散列码