学了这么些天的基础知识发现自己还是个门外汗,难怪自己一直混的不怎么样。但这样的恶补不知道有没有用,是不是过段时间这些知识又忘了呢?这些知识平时的工作好像都是随拿随用的,也并不是平时一点没有关注过这些基础知识,只是用完了也就忘了。所以写笔记也是个好习惯,光看一个概念不容易记住,整理写成文那就好许多,以后查起来也方便一些。

为什么要用Hash Table?

这就想到了以前工作中遇到的一个事情。多年前我还在写delphi,软件功能中有许多的批量数据运算,由于数据要拉取到内存中,然后多个数据集合间进行遍历查找对比,这样的话数据量一多就会非常的慢,而且经常会遇到内存错误,一直也找不出原因。后来有一位经验丰富的老程序员加入,他就提出了使用hashtable来解决这个问题。
 
经过测试果然大幅度的提高了性能,以下就来简单分析下:
我们的数据对象是通过对比主键字段进行定位的,而这个字段是string类型,长度为40,要在一个数据集合中找一条数据就要去遍历,然后对比主键是否相同,这就有两个问题:
1、字符串与字符串进行比较如果量少问题不大,如果数据量大的话就是个很大的问题,毕竟每次都是40个字节与40字节长度对比呀
2、由于数据是存在内存链表中的,想要定位一个数据就要搜索查找,更何况是大量的循环查找
 
要解决这两个问题分别要做什么:
1、减少主键字段对比时的时间,比如采用整形类型,这样就只有4个字节
2、优化算法,提高数据查找效率,或者提高数据定位的效率
 
我们采用的hashtable很符合这些要求,Hashtable的存储结构使用的是:数组+链表的方式。
首先,将数据存在数组中,利用数组的寻址能力不就很快吗
其次,对Key进行hash运算,这样就可以使用Int类型,这又解决了字符串比较的问题
 
看到了好处就有了继续学习下去的动力了,一步步来吧。

什么是Hash Table

对于Hash table名字应该不陌生,先看看定义吧
  1. 散列表(Hash table,也叫哈希表),是根据关键字(Key value)而直接访问在内存存储位置的数据结构。也就是说,它通过把键值通过一个函数的计算,映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
 
要理解的具体一点,就要将散列这个概念多了解一些,还是继续看维基百科吧,一点点来理解:
  • 若关键字为,则其值存放在的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系散列函数,按这个思想建立的表为散列表
  • 对不同的关键字可能得到同一散列地址,即,而,这种现象称为碰撞英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数和处理碰撞的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表散列,所得的存储位置称散列地址
这里说了几个比较重要的概念:关键字、散列函数、碰撞。应该说已经说的很明白了,没啥好难理解的。
散列函数有许多种实现方法,下面也列一下吧:

散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快定位。

  1. 直接定址法:取关键字或关键字的某个线性函数值为散列地址。即,其中为常数(这种散列函数叫做自身函数)
  2. 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
  3. 平方取中法:取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
  4. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
  5. 随机数法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
  6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即。不仅可以对关键字直接取模,也可在折叠法平方取中法等运算之后取模。对p的选择很重要,一般取素数或m,若p选择不好,容易产生碰撞。
有点悬乎,无非就是直接用某个数字或者通过计算得到一个数字作为数组的下标,就是存储的位置地址,这样存与取都可以直接定位了,简单高效。不管是哪一种方法都有一个共同的问题,就是hash计算结果可能相同,也就是碰撞问题。那么就得有办法去解决这问题,看了看资料有几种方法:
  • 开放定址法:如果发生冲突就继续找下一个空的散列地址
  • 单独链表法:即在发生冲突的位置直接使用链表保存冲突的数据
  • 再散列:即在上次散列计算发生碰撞时,用另一个散列函数计算新的散列函数地址,直到碰撞不再发生
  • 建立公共溢出区:建立基本表和溢出表,冲突的就放到溢出区
 
参考文章:
http://zh.wikipedia.org/zh/%E5%93%88%E5%B8%8C%E8%A1%A8
http://c.biancheng.net/cpp/html/1031.html

简单学习下Hash Table的机制

到这好像也差不多了解了HashTable的作用与好处,接下来就有了继续学习HashTable的动力。前面提到的那个Delphi的Hash Table类使用的存储结构是数组+链表的形式,源代码也找不到了,下面就以Java的Hash Table类作为对象来学习吧。
 
  • 存储结构

对于Java SDK中默认实现的HashTable类使用的存储结构是数组+单链表,有了前面的概念就明白了,数组即是用于存储数据的连续的地址空间,而链表是用来解决碰撞问题的。这样就利用了数组高效的寻址能力,又通过链表解决了碰撞的存储问题。
先看数组:
  1. /**
  2. * The hash table data.
  3. */
  4. private transient Entry[] table;

再看数据链表

  1. /**
  2. * Hashtable collision list.
  3. */
  4. private static class Entry<K,V> implements Map.Entry<K,V> {
  5. int hash;
  6. K key;
  7. V value;
  8. Entry<K,V> next;
  9.  
  10. protected Entry(int hash, K key, V value, Entry<K,V> next) {
  11. this.hash = hash;
  12. this.key = key;
  13. this.value = value;
  14. this.next = next;
  15. }
  16.  
  17. protected Object clone() {
  18. return new Entry<K,V>(hash, key, value,
  19. (next==null ? null : (Entry<K,V>) next.clone()));
  20. }
  21.  
  22. // Map.Entry Ops
  23.  
  24. public K getKey() {
  25. return key;
  26. }
  27.  
  28. public V getValue() {
  29. return value;
  30. }
  31.  
  32. public V setValue(V value) {
  33. if (value == null)
  34. throw new NullPointerException();
  35.  
  36. V oldValue = this.value;
  37. this.value = value;
  38. return oldValue;
  39. }
  40.  
  41. public boolean equals(Object o) {
  42. if (!(o instanceof Map.Entry))
  43. return false;
  44. Map.Entry e = (Map.Entry)o;
  45.  
  46. return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
  47. (value==null ? e.getValue()==null : value.equals(e.getValue()));
  48. }
  49.  
  50. public int hashCode() {
  51. return hash ^ (value==null ? 0 : value.hashCode());
  52. }
  53.  
  54. public String toString() {
  55. return key.toString()+"="+value.toString();
  56. }
  57. }

其实还是挺简单的,就是一个Entry数组,而Entry就是一个键值关系表,同时提供了链表的功能。

  • 数据存取

写了这么多代码,打开Hashtable的代码发现也就这那些东西吧,关键的是就是存与取。
 
先看存一个数据的方法put:
 
  1. public synchronized V put(K key, V value) {
  2. // Make sure the value is not null
  3. if (value == null) {
  4. throw new NullPointerException();
  5. }
  6.  
  7. // Makes sure the key is not already in the hashtable.
  8. Entry tab[] = table;
  9. int hash = key.hashCode();
  10. int index = (hash & 0x7FFFFFFF) % tab.length;
  11. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
  12. if ((e.hash == hash) && e.key.equals(key)) {
  13. V old = e.value;
  14. e.value = value;
  15. return old;
  16. }
  17. }
  18.  
  19. modCount++;
  20. if (count >= threshold) {
  21. // Rehash the table if the threshold is exceeded
  22. rehash();
  23.  
  24. tab = table;
  25. index = (hash & 0x7FFFFFFF) % tab.length;
  26. }
  27.  
  28. // Creates the new entry.
  29. Entry<K,V> e = tab[index];
  30. tab[index] = new Entry<K,V>(hash, key, value, e);
  31. count++;
  32. return null;
  33. }
  • 这里看到了线程同步关键字,因为整个hashtable都是线程同步的,所以在线程里用也是木有问题的
  • 注意valua是不能为null的会抛异常
  • 将要存的数据以key-value的方式放进去,先通过key计算Hash值,就是数组位置

看看关键代码:

  1. int hash = key.hashCode();
  2. int index = (hash & 0x7FFFFFFF) % tab.length;

先得到key的hashcode,再通过除留余数法得到数组索引,简单高效就得到了存储位置。

  • 然后后面的代码看看有没有相同的项目,有则替换之。最后创建一个Entry对象保存数据,如果存在碰撞Entry会自动写入链表中解决冲突。

获得数据的方法就是get:

  1. public synchronized V get(Object key) {
  2. Entry tab[] = table;
  3. int hash = key.hashCode();
  4. int index = (hash & 0x7FFFFFFF) % tab.length;
  5. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
  6. if ((e.hash == hash) && e.key.equals(key)) {
  7. return e.value;
  8. }
  9. }
  10. return null;
  11. }

计算数组下标的方法是一样的,这样的定位方式特别高效,只要计算一次就可以了,当然如果有冲突的话还要遍历链表对比hash和key再确定最终的数据项。

 

再看看HashMap

在haspMap中实现的思想其实和hashtable大体相同,存储结构也类似,只是一些小区别:
  1. key和value支持null,这种情况下总是存在数组中的第一个元素中,感觉是种特殊公共溢出区的应用
  2. 不是线程安全的,需要自己做线程同步
  3. 计算存储位置时采用了在hashcode上再次hash+indexFor的方法,使得得到的散列值更均匀
 

学习笔记:Hashtable和HashMap的更多相关文章

  1. stl源码剖析 详细学习笔记 hashtable

    //---------------------------15/03/24---------------------------- //hashtable { /* 概述: sgi采用的是开链法完成h ...

  2. HashMap源码剖析及实现原理分析(学习笔记)

    一.需求 最近开发中,总是需要使用HashMap,而为了更好的开发以及理解HashMap:因此特定重新去看HashMap的源码并写下学习笔记,以便以后查阅. 二.HashMap的学习理解 1.我们首先 ...

  3. 基于jdk1.8的HashMap源码学习笔记

    作为一种最为常用的容器,同时也是效率比较高的容器,HashMap当之无愧.所以自己这次jdk源码学习,就从HashMap开始吧,当然水平有限,有不正确的地方,欢迎指正,促进共同学习进步,就是喜欢程序员 ...

  4. java集合类学习笔记之HashMap

    1.简述 HashMap是java语言中非常典型的数据结构,也是我们平常用的最多的的集合类之一.它的底层是通过一个单向链表(Node<k,v>)数组(也称之为桶bucket,数组的长度也叫 ...

  5. HashMap源代码学习笔记

        HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是由于它是通过计算散列码来决定存储的位置. HashMap中主要是通过key的hashCode来计算hash值的 ...

  6. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  7. Redis学习笔记一:数据结构与对象

    1. String(SDS) Redis使用自定义的一种字符串结构SDS来作为字符串的表示. 127.0.0.1:6379> set name liushijie OK 在如上操作中,name( ...

  8. 20145330第五周《Java学习笔记》

    20145330第五周<Java学习笔记> 这一周又是紧张的一周. 语法与继承架构 Java中所有错误都会打包为对象可以尝试try.catch代表错误的对象后做一些处理. 使用try.ca ...

  9. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

  10. JavaSE中Map框架学习笔记

    前言:最近几天都在生病,退烧之后身体虚弱.头疼.在床上躺了几天,什么事情都干不了.接下来这段时间,要好好加快进度才好. 前面用了三篇文章的篇幅学习了Collection框架的相关内容,而Map框架相对 ...

随机推荐

  1. excel的导入导出的实现

    1.创建Book类,并编写set方法和get方法 package com.bean; public class Book { private int id; private String name; ...

  2. 使用Selector改变TextView的字体颜色textColor的方法

    先上Selector文件,名字为singer_fragment_top_text_style.xml, <?xml version="1.0" encoding=" ...

  3. 通过dll或def文件提取lib导入库文件

    很多时候第三方库或其他项目提供的库多数会以动态库的形式提供dll以及相应的lib导入库.头文件,不过也有的只是提供dll和头文件,或者也提供了def模块定义(用于导出函数)文件,此时若使用将不得不调用 ...

  4. mysql忘记root密码怎么办?

    有时候忘记mysql的root密码了,怎么办? 这个时候,我们可以修改my.cnf,添加以不检查权限的方式启动,再修改root,最后重启mysql数据库. (1)service mysql stop ...

  5. Windows7 64位系统搭建Cocos2d-x-2.2.1最新版以及Android交叉编译环境(详细教程)

    Windows7 64位系统搭建Cocos2d-x-2.2.1最新版以及Android交叉编译环境(详细教程) 声明:本教程在参考了以下博文,并经过自己的摸索后实际操作得出,本教程系本人原创,由于升级 ...

  6. Quartz.Net简单使用

    Quartz.Net为开源的作业调度框架,使用方便,实现IJob接口,及相关配置,即可实现调度. 项目包安装: install-package Quartz install-package log4n ...

  7. 在Mac中像Windows一样查看Tomcat控制台信息

    在Windows系统中,通过startup.bat启动Tomcat之后会打开一个控制台,输出日志信息,在系统调试过程中,也会随时输入日志或错误信息,对开发很有帮助. 在Mac中,通过startup.s ...

  8. LinkedHashMap源码阅读笔记(基于jdk1.8)

    LinkedHashMap是HashMap的子类,很多地方都是直接引用HashMap中的方法,所以需要注意的地方并不多.关键的点就是几个重写的方法: 1.Entry是继承与Node类,也就是Linke ...

  9. 指定eclipse启动使用的JVM

    不同eclispe对运行时要求不一样,而一台电脑只能同时使用一个运行时,当多个要求不同版本jvm的eclipse需要在一台电脑工作时,需要手动指定eclipse启动使用的jvm. [eclipse-j ...

  10. iOS Swift 3 open

    参考资料:http://stackoverflow.com/questions/38947101/what-is-the-open-keyword-in-swift