散列和散列码(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方法需要满足一下条件:

  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;
    }
}

原文地址:https://www.cnblogs.com/dugk/p/8900778.html

时间: 2024-10-10 12:19:56

散列和散列码(1)的相关文章

容器深入研究 --- 散列与散列码(三)

如何覆盖hashCode(): 明白了如何散列之后,编写自己的hashCode()就更有意义了. 首先,你无法控制bucket数组的下标值的产生.这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子有关.hashCode()生成的结果,经过处理后称为桶位的下标. 设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值.如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()

容器深入研究 --- 散列与散列码(一)

通常的: 当标准类库中的类被作用HashMap的键.它用的很好,因为它具备了键所需的全部性质. 当你自己创建用作HashMap的键的类,有可能会忘记在其中放置必须的方法,而这时通常会犯的一个错误. 例如:考虑一个天气系统,将Groundhog对象与Prediction对象联系起来. class Groundhog { protected int number; public Groundhog(int n) { number = n; } public String toString() { r

容器深入研究 --- 散列与散列码(二)

为速度而散列: SlowMap.java说明了创建一个新的Map并不困难.但正如它的名称SlowMap所示,它不会很快,如果有更好的选择就应该放弃它.它的问题在于对键的查询,键没有按照任何特定的顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式. 散列的价值在于速度: 散列使得查询得以快速进行.由于瓶颈在于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询. 散列则更进一步,它将键保存在某处,以便能够很快的找到.

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

package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象联系起来, * @author lenovo * */ //土拨鼠 public class Groundhog { protected int number; public Groundhog(int n) { number=n; } @Override public String toString() { return "Groundhog #" + number

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

package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象联系起来, * @author lenovo * */ //土拨鼠 public class Groundhog { protected int number; public Groundhog(int n) { number=n; } @Override public String toString() { return "Groundhog #" + number

【ThinkingInJava】51、散列与散列码

/** * 书本:<Thinking In Java> * 功能:散列与散列码 * 文件:Groundhog.java * 时间:2015年5月3日09:42:54 * 作者:cutter_point */ package Lesson17Containers; public class Groundhog { protected int number; //保护类型,继承之后还是保护类型 public Groundhog(int n) { number = n; } public Strin

Java散列和散列码的实现

转自:https://blog.csdn.net/al_assad/article/details/52989525 散列和散列码 ※正确的equals方法应该满足的的条件: ①自反性:x.equals(x) 一定返回true: ②对称性:y.euqlas(x)为true,那么x.equals(y)一定为true: ③传递性:x.equals(y)为true,y.euqlas(z)为true,则z.equals(x)为true: ④一致性:如果x,y中用于等价比较的信息没有变化,那么无论调用y.

数据结构--散列排序--散列表

散列表 散列查找,我们又回到了查找, 编译的时候,涉及变量及属性的管理: 插入:新变量的定义 查找:变量的引用 实际上是动态查找问题,查找树AVL树. 两个变量名(字符串)比较效率不高.字符串的比较要一个一个的比下去,时间会比较长, 是否可以把字符串转换成数字,再处理,就快多了.就是散列查找的思想. 已知的查找方法: 顺序查找                                          O(N) 二分查找(静态查找,不适合动态查找)   O(log2N) 二叉搜索数    

字典:散列表、散列字典、关键字列表、集合与结构体

字典 散列表和散列字典都实现了Dict的行为.Keyword模块也基本实现了,不同之处在于它支持重复键. Eunm.into可以将一种类型的收集映射转化成另一种. defmodule Sum do def values(dict) do dict |> Dict.values |> Enum.sum end end hd = [ one: 1, two: 2, three: 3 ] |> Enum.into HashDict.new IO.puts Sum.values(hd) #=&g

devexpress表格控件gridcontrol图片列,按钮列,时间列等特殊列的实现

1.项目中经常会在表格中插入按钮列,图片列,表格列一些非文本的特殊列.如何在devexpress表格控件gridcontrol中实现呢?以下列举一个实现添加图片列,按钮列,时间列,按钮列,开关列的示例,效果图如下: 2.数据代码,其中图片列使用了两种方法,大家可根据自己要求变跟. Image img = Image.FromFile(Application.StartupPath+"\\img\\11111.png");//方法1 //byte[] imgbyte=getImageBy