为速度而散列:

SlowMap.java说明了创建一个新的Map并不困难。但正如它的名称SlowMap所示,它不会很快,如果有更好的选择就应该放弃它。它的问题在于对键的查询,键没有按照任何特定的顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式。

散列的价值在于速度

散列使得查询得以快速进行。由于瓶颈在于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。

散列则更进一步,它将键保存在某处,以便能够很快的找到。存储一组元素的最快数据结构是数组,所以使用它来表示键的信息(请小心留意,我说的是键的信息,而不是键本身)。但是因为数组不能调整容量,因此就有了一个问题:我们希望在Map中保存的数量是不确定的值,但是如果键的数量被数组的容量限制了,该怎么办呢?

答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中的、且可能由你的类覆盖的hashCode()方法(计算机科学术语称为散列函数)生成。

为了解决数组被固定的问题,不同的键可能产生相同的下标。也就是说,可能会有冲突。因此,数组多大就不重要了,任何键总能在数组中找到它的位置。

于是查询一个值的过程首先就是计算散列码然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能)那可能就是一个完美的散列函数,但是这种情况只是特例。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的list。然后对list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速的跳到素数的某个位置,只对很少的元素进行比较。这边是HashMap快的原因。

理解了散列的原理,我们就能实现一个简单的散列Map了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class MapEntry<k, v=""> implements Map.Entry<k, v=""> {
 
    private K key;
    private V value;
 
    public MapEntry(K key, V value) {
        this.key = key;
        this.value = value;
    }
 
    public K getKey() {
        return key;
    }
 
    public V getValue() {
        return value;
    }
 
    public V setValue(V value) {
        V result = this.value;
        this.value = value;
        return result;
    }
 
    @Override
    public int hashCode() {
        return (key == null ? 0 : key.hashCode())
                ^ (value == null ? 0 : value.hashCode());
    }
 
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof MapEntry)) {
            return false;
        }
        MapEntry me = (MapEntry) o;
        return (key == null ? me.getKey() == null : key.equals(me.getKey()))
                && (value == null ? me.getValue() == null : value.equals(me
                        .getValue()));
    }
 
    @Override
    public String toString() {
        return key + " = " + value;
    }
}
 
class SimpleHashMap<k, v=""> extends AbstractMap<k, v=""> {
    static final int SIZE = 997;
 
    @SuppressWarnings("unchecked")
    LinkedList<mapentry<k, v="">>[] buckets = new LinkedList[SIZE];
 
    public V put(K key, V value) {
        V oldValue = null;
        int index = Math.abs(key.hashCode()) % SIZE;
 
        if (buckets[index] == null) {
            buckets[index] = new LinkedList<mapentry<k, v="">>();
        }
        LinkedList<mapentry<k, v="">> bucket = buckets[index];
 
        MapEntry<k, v=""> pair = new MapEntry<k, v="">(key, value);
        boolean found = false;
        ListIterator<mapentry<k, v="">> it = bucket.listIterator();
        while (it.hasNext()) {
            MapEntry<k, v=""> iPair = it.next();
            if (iPair.getKey().equals(key)) {
                oldValue = iPair.getValue();
                it.set(pair);
                found = true;
                break;
            }
        }
        if (!found) {
            buckets[index].add(pair);
        }
        return oldValue;
    }
     
    public V get(Object key) {
        int index = Math.abs(key.hashCode()) % SIZE;
         
        if (buckets[index] == null) {
            return null;
        }
         
        for (MapEntry<k, v=""> iPair : buckets[index]) {
            if (iPair.getKey().equals(key)) {
                return iPair.getValue();
            }
        }
        return null;
    }
     
 
    @Override
    public Set<java.util.map.entry<k, v="">> entrySet() {
        Set<map.entry<k, v="">> set = new HashSet<map.entry<k,v>>();
        for (LinkedList<mapentry<k, v="">> bucket : buckets) {
            if (bucket == null) {
                continue;
            }
            for (MapEntry<k, v=""> mpair : bucket) {
                set.add(mpair);
            }
        }
        return set;
    }
}
 
public class Main2 {
    public static void main(String[] args) {
        {CAPE VERDE=Praia, ANGOLA=Luanda, ETHIOPIA=Addis Ababa, BENIN=Porto-Novo, CONGO=Brazzaville, LESOTHO=Maseru, CENTRAL AFRICAN REPUBLIC=Bangui, EQUATORIAL GUINEA=Malabo, ERITREA=Asmara, COMOROS=Moroni, BURKINA FASO=Ouagadougou, GABON=Libreville, THE GAMBIA=Banjul, GUINEA=Conakry, EGYPT=Cairo, BURUNDI=Bujumbura, ALGERIA=Algiers, CAMEROON=Yaounde, GHANA=Accra, KENYA=Nairobi, COTE D'IVOIR (IVORY COAST)=Yamoussoukro, BISSAU=Bissau, DJIBOUTI=Dijibouti, CHAD=N'djamena, BOTSWANA=Gaberone}
        [CAPE VERDE = Praia, ANGOLA = Luanda, ETHIOPIA = Addis Ababa, BENIN = Porto-Novo, CONGO = Brazzaville, LESOTHO = Maseru, CENTRAL AFRICAN REPUBLIC = Bangui, EQUATORIAL GUINEA = Malabo, ERITREA = Asmara, COMOROS = Moroni, BURKINA FASO = Ouagadougou, GABON = Libreville, THE GAMBIA = Banjul, GUINEA = Conakry, EGYPT = Cairo, BURUNDI = Bujumbura, ALGERIA = Algiers, CAMEROON = Yaounde, GHANA = Accra, KENYA = Nairobi, COTE D'IVOIR (IVORY COAST) = Yamoussoukro, BISSAU = Bissau, DJIBOUTI = Dijibouti, CHAD = N'djamena, BOTSWANA = Gaberone]
        SimpleHashMap<string, string=""> m = new SimpleHashMap<string, string="">();
        m.putAll(Countries.capitals(25));
        System.out.println(m);
        System.out.println(m.entrySet());
    }
}</string,></string,></k,></mapentry<k,></map.entry<k,v></map.entry<k,></java.util.map.entry<k,></k,></k,></mapentry<k,></k,></k,></mapentry<k,></mapentry<k,></mapentry<k,></k,></k,></k,></k,>

由于散列表中的“槽位”(slot)通常称为桶位(bucket),因此我们将表示实际散列表的数组命名为bucket。

为使散列分布均匀,桶的数量通常使用质数。注意,为了能够自动处理冲突,使用了一个LinkedList的数组;

每一个新的元素只是直接添加到list末尾的某个特定的桶位中。即使Java不允许你创建泛型数组,那你也可以创建指向这种数组的引用。这里,向上转型为这种数组是很方便的,这样可以防止在后面的代码中进行额外的转型。

对于put方法,hashCode()将针对键而被调用,并且其结果被强制转换为正数。为了是产生的数组适合bucket数组的大小,取摸操作符将按照该数组的尺寸取模。如果数组的某个位置是null,这表示还没有元素被散列至此,所以,为了保存刚散列到该定位的对象需要创建爱你一个新的LinkedList。一般的过程是,查看当前位置的list中是否有相同的元素,如果有,则将旧的值付给oldValue,然后用新值取代旧值。标记found用来跟踪是否找到旧的键值对,如果没有,则将新的添加到list的末尾。

get()方法按照与put()方法相同的方式计算bucktes数组中的索引(这很重要,保证计算出相同的位置)如果此位置存在,则进行查询。

注意,这个实现并不意味着对性能进行了调优;它只是想要展示散列映射表执行的各种操作。

关于Java的散列桶, 以及附上一个案例-重写map集合的更多相关文章

  1. Java自带工具jstack故障分析的一个案例

    公司的一个web应用项目运行了很长一段时间,达半年之久,前段时间突然出现了服务不可用的情况,所有的请求都不可达,服务彻底挂了.查看tomcat进程还在,cpu使用率低,一时没找着问题,重启了服务.过了 ...

  2. Java散列和散列码的实现

    转自:https://blog.csdn.net/al_assad/article/details/52989525 散列和散列码   ※正确的equals方法应该满足的的条件: ①自反性:x.equ ...

  3. 数据结构与算法分析java——散列

    1. 散列的概念 散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h(K)(称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存 ...

  4. DotNet加密方式解析--散列加密

    没时间扯淡类,赶紧上车吧. 在现代社会中,信息安全对于每一个人都是至关重要的,例如我们的银行账户安全.支付宝和微信账户安全.以及邮箱等等,说到信息安全,那就必须得提到加密技术,至于加密的一些相关概念, ...

  5. JavaScript数据结构-12.散列碰撞(线性探测法)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. .NET加密方式解析--散列加密

    在现代社会中,信息安全对于每一个人都是至关重要的,例如我们的银行账户安全.支付宝和微信账户安全.以及邮箱等等,说到信息安全,那就必须得提到加密技术,至于加密的一些相关概念,在这里就不说了. 这一次将会 ...

  7. 非对称算法,散列(Hash)以及证书的那些事

    转载请注明出处 http://blog.csdn.net/pony_maggie/article/details/35389657 作者:小马 这几个概念在金融电子支付领域用得比較多,我忽然认为把它们 ...

  8. PAT甲级 散列题_C++题解

    散列 PAT (Advanced Level) Practice 散列题 目录 <算法笔记> 重点摘要 1002 A+B for Polynomials (25) 1009 Product ...

  9. Shiro入门学习之散列算法与凭证配置(六)

    一.散列算法概述 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5.SHA等,一般进行散列时最好提供一个salt(“盐”),什么意思?举个栗子 ...

随机推荐

  1. 一个可以拖动的自定义Gridview代码

    这个可以拖动的gridview继承于gridview,所以,用法和gridview一样, 代码如下: public class DragGridView extends GridView { priv ...

  2. 图像检索:FCTH(Fuzzy Color and Texture Histogram)算法

    模糊颜色和纹理直方图(Fuzzy Color and Texture Histogram,FCTH) 本文节选自论文<基于半监督和主动学习相结合的图像的检索研究> FCTH 特征可从 3 ...

  3. 苹果新的编程语言 Swift 语言进阶(十)--类的继承

    一.类的继承 类能够从其它类继承方法.属性以及其它特性,当一个类从另外的类继承时,继承的类称为子类,它继承的类称为超类.在Swift中,继承是类区别与其它类型(结构.枚举)的基础行为. 1.1 .类的 ...

  4. Zookeeper管理多个HBase集群

    zookeeper是hbase集群的"协调器".由于zookeeper的轻量级特性,因此我们可以将多个hbase集群共用一个zookeeper集群,以节约大量的服务器.多个hbas ...

  5. nasm汇编一些需要注意的地方

    经常用msam或tasm的童鞋一下转换到nasm下可能觉得不怎么适应,它们应该先去晓习一下gas的语法,然后就适应了-that's only a joke! :) section .data v101 ...

  6. LeetCode(51)- Count and Say

    题目: The count-and-say sequence is the sequence of integers beginning as follows: 1, 11, 21, 1211, 11 ...

  7. RecyclerView 与 Scrollview 搭配使用的两个坑

    RecyclerView & Scrollview & wrap_content RecyclerView wrap_content 用android.support.v4.widge ...

  8. 配置使用dwr完成收邮件提示

    DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJAX技术的网站.它可以允许在浏览器里的代码使用运行在 ...

  9. 使用Puppeteer抓取受限网站

    不要相信前端是安全的,今天简单验证一下,但是希望大家支持正版,支持原作者,毕竟写书不易. 安装Puppteer npm install --save puppeteer 选择目标网站 我们这里选择胡子 ...

  10. Jsp 连接 mySQL、Oracle 数据库备忘(Windows平台)

    Jsp 环境目前最流行的是 Tomcat5.0.Tomcat5.0 自己包含一个 Web 服务器,如果是测试,就没必要把 Tomcat 与 IIS 或 Apache 集成起来.在 Tomcat 自带的 ...