`
siyanglu
  • 浏览: 15185 次
  • 性别: Icon_minigender_2
社区版块
存档分类
最新评论

关于hash的一点理解

 
阅读更多

      一直以来对哈希表的印象仅在于的书本上,即老师提到的所谓考试重点。那时初学数据结构,被C语言中那些令人头痛的指针搞得晕头转向,初见哈希表,脑中蹦出的一句话是“哇塞!这题目超简单!”当时还是把这些当做考试题目来看待的,试卷上的习题,无异于“取模”、“放入格子里”、“解决冲突”这几个问题。以至于到后来,总觉着哈希表就那么一回事。它和二叉树、链表一样,不过只是书本上的理论罢了。直到后面自己慢慢试着用代码来实现这些结构时,才重新将他们认识了一遍。
      早之前有抄过C的哈希表的代码,只觉着自己几乎完全不能把填格子和这一串英文字联系起来。后来看到Java中的HashTable和HashMap时,一直都想不明白。“这里有两个值,咋整啊?”
      没办法只能硬着头皮看源码了,同时去网上搜索了一些资料,才慢慢有了一点理解。对于KEY和VALUE,文档中的解释是“将键映射到相应的值”。映射这个词,数学上就是x->f(x)(还是f(x)->x?悲剧了……)而在这里,则是把value当做key的一个附属,即它仅仅是一个属性,而hash方法的重点则在于KEY。而哈希结构就是实现了“键——值”之间的快速存取,通过对KEY的哈希运算快速得到对应的VALUE,而省去了遍历的时间。源码中,通过连续的两步先对键值自带的hashcode做哈希运算,之后通过indexFor的方法得到索引。这里是hashmap的源代码:

int hash = hash(k); 
	int i = indexFor(hash, table.length);   

static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

 static int indexFor(int h, int length) {
        return h & (length-1);
    }

 

 

      前者是源码中定义的哈希算法,后者则是将其转化成相应索引值。简单的两步便得到了对应的索引值,但又运用得恰到好处。接下来要说的是Entry这个内部类。要实现哈希结构,Entry占了很重要的地位。源码中的Entry继承了Map.Entry,它包含key,value,next,hash四个属性,分别代表了键、值、指向下个Entry的指针、和hash值(看了好几遍,源码中这个里面的hash值没有在这个内部类中用到,不知道它的用处在哪里……求解)。里面有几个方法,这里要说到的是setValue方法 。

 

public final V setValue(V newValue) {
	    V oldValue = value;
            value = newValue;
            return oldValue;
        }

 

     当新的值被放入时,则会覆盖旧值,并把旧值返回。这样正符合哈希表“键——值”一一对应的特性。
由于时间关系,在这里只分析了put和get方法。看了源代码,发现put方法原来是有返回值的,返回的对象是当key存在时的旧value值。顺带看了下面的putForNullKey方法,这是当key为null时,得到hashcode的方法。这也是hashmap和hashtable的一大区别,即hashmap中允许键或值为空。
      由于不同的key值hashcode有可能相同,这就导致算出来的索引值也是相同的,于是把拥有相同索引的Entry链接在前一个之后,也即是next属性所指向的。当然如果这个链表过长的话,就使搜索的时间变得更长,也就失去了哈希表的意义了。于是当超过了一定对象时,就会重新构造存取表。get的方法和put是一个道理,这里就不赘述了。于是,根据这些原理,自己写了一个简单的哈希表,仅仅实现了get和put方法,rehash也没有写,代码如下:

/**
 * 哈希表 
 * @author Administrator
 * @param <K>
 * @param <V>
 */
public class MyHashTable<K, V> {

	private int length = 10000;
	MyEntry[] table = new MyEntry[length];

	/**
	 * 添加元素
	 */
	public void put(K key, V value) {
		// 通过哈希算法的到hash值及索引值
		int code = hash(key.hashCode());
		int i = indexfor(code);
		// 当该索引位置有值时
		if (table[i] != null) {
			MyEntry<K, V> e1 = table[i];
			// 比较key值是否相同
			if (e1.getKey().equals(key)) {
				// 相同则覆盖value
				V oldvalue = (V) e1.setValue(value);
				table[i] = e1;
			} else {
				// 若不同,则链接到最后面
				MyEntry<K, V> e2 = new MyEntry<K, V>(code, key, value, null);
				while (e1.getNext() != null) {
					e1 = e1.getNext();
				}
				e1.setNext(e2);
				table[i] = e1;
			}
		} else {
			MyEntry<K, V> e = new MyEntry<K, V>(code, key, value, null);
			table[i] = e;
		}
	}

	/**
	 * 取得元素
	 * 
	 * @param key
	 */
	public V get(K key) {
		V values = null;
		// 得到索引值
		int code = hash(key.hashCode());
		int i = indexfor(code);
		if (table[i] == null) {
			System.err.println("找不到对应键值!!");
			return null;
		} else {
			MyEntry<K, V> e = table[i];
			while (true) {
				if (e.getKey().equals(key)) {
					values = e.getValue();
					break;
				}
				e = e.getNext();
				if (e == null) {
					break;
				}
			}
			return values;
		}

	}

	public int hash(int code) {
		// System.out.println(">>>>>"+code);
		code = code % 9737;

		return code;
	}

	public int indexfor(int code) {
		code = code & (length - 1);
		return code;
	}
}

 

接下来是Entry类:

package hash20120308;

/**
 * 
 * @author Administrator
 * 
 */
public class MyEntry<K, V> {
	// 四个键值
	private K key;
	V value;
	int hash;
	MyEntry<K, V> next;// 通过next创建链表解决冲突

	// 构造方法
	public MyEntry(int h, K k, V v, MyEntry<K, V> n) {
		this.hash = h;
		this.key = k;
		this.value = v;
		this.next = n;
	}

	public K getKey() {
		return key;
	}

	public V getValue() {
		return value;
	}

	// 用新的value值来覆盖旧值
	public V setValue(V newvalue) {
		V oldvalue = this.value;
		this.value = newvalue;
		return oldvalue;
	}

	public int getHash() {
		return hash;
	}

	public MyEntry<K, V> getNext() {
		return next;
	}

	public void setNext(MyEntry<K, V> next) {
		this.next = next;
		System.out.println(">>>>>接了一个");
	}
}

 

最后来测试一下:package hash20120308;

import java.util.Random;

public class Test {

 public static void main(String args[]) {

  Random rd = new Random();
  long s1 = System.currentTimeMillis();
  MyHashTable<Integer, Integer> mm = new MyHashTable<Integer, Integer>();

  int c = 0;
  // while (c < 100000) {
  // int t = rd.nextInt(10000);
  // System.out.println("d>>>" + t);
  // int r = rd.nextInt(10000);
  // mm.put(t, r);
  // c++;
  // }
  mm.put(345, 251);
  mm.put(345, 4535);
  mm.put(346, 532);
  // long s2 = System.currentTimeMillis();
  // System.out.println(s2 - s1);
  int das = mm.get(345);
  int dasw = mm.get(346);
  long s3 = System.currentTimeMillis();
  System.out.println("345对应的是" + das);
  System.out.println("346对应的是" + dasw);
  System.out.println(s3 - s1);

 }

}



 

 

结果:
>>>>替换了一个
345对应的是4535
346对应的是532
3

 

这个哈希表还很不完善,但总算对它有一定的了解了。

分享到:
评论

相关推荐

    HASH分区表增加新的分区的一点研究.doc

    在Oracle数据库中,哈希(HASH)分区是一种有效的数据存储策略,它通过计算分区键的哈希值来决定数据应存储在哪个分区中。哈希分区表在处理大量数据时可以提供良好的查询性能,因为它们能够均匀地分散数据,使得查询...

    C/C++ 一致性hash算法

    为了实现这一点,可以将每个节点哈希成一个唯一的值,并放置在环的相应位置。 3. **数据分配**:当有数据需要存储时,也对数据进行哈希,得到的哈希值定位到环上的某个位置。数据将被分配到距离其哈希值最近的节点...

    hash的sha1的verilog程序

    SHA-1(Secure Hash Algorithm 1)是一种广泛使用的哈希函数,主要应用于数据完整性校验和数字签名。在Verilog中实现SHA-1算法,是为了在硬件层面进行高速计算,常用于 FPGA 或 ASIC 设计。下面我们将深入探讨SHA-1...

    区分vue-router的hash和history模式

    Vue Router 的 hash 模式正是基于这一点来实现前端路由的。当 URL 的 hash 部分变化时,浏览器并不会发起新的 HTTP 请求,而是触发 `window.onhashchange` 事件。开发者可以监听这个事件,根据新的 hash 值更新页面...

    JDK1.8 ConcurrentHashMap的一点理解

    只是都是相通的,当我们了解了ConcurrentHashMap的实现原理以及各个方法的实现机制,我们对于其他的hash类型实现也能快速的理解,今天我们就来通过源码来一点一点的分析下ConcurrentHashMap的实现。 首先我们来看...

    google-hash-code-2021:我们对Google Hash Code 2021的参与

    5. **并行计算**:Google Hash Code 鼓励团队合作,利用多核处理器进行并行计算,Python 中的 `multiprocessing` 库可以实现这一点。 6. **测试与调试**:使用单元测试和集成测试确保代码的正确性,同时通过模拟...

    lil-hash:一个简单的共享URL缩短器

    《构建与理解:lil-hash——简易URL缩短器》 在互联网时代,长串的URL有时会给分享和传播带来不便,于是URL缩短器应运而生。本文将深入探讨一个名为“lil-hash”的简单共享URL缩短器项目,它旨在提供一种高效、便捷...

    Java常用类与基础API-String的理解与不可变性

    - **哈希码 `hash`**:缓存计算过的哈希码值,以提高性能。 在JDK 9及以后版本中,字符数组的类型从 `char[]` 改为了 `byte[]`,以优化存储空间。 了解这些细节有助于更好地掌握 `String` 类的工作原理,并能够写...

    MySql的一点学习笔记.zip

    MySQL支持多种类型的索引,如BTree索引(默认)、Hash索引、全文索引以及空间索引。创建和管理索引包括`CREATE INDEX`, `ALTER TABLE ... ADD INDEX`, `DROP INDEX`等命令。 8. **事务处理** 事务是数据库操作的...

    互联网大厂Mysql面试题精选55题

    4. **索引优化**:深入理解B-Tree、Hash、Full-text等索引类型,知道何时使用主键、唯一键和非唯一键索引。同时,要了解索引的优缺点,以及如何通过EXPLAIN分析查询执行计划。 5. **存储引擎**:MyISAM和InnoDB的...

    twoSum0.zip

    Python的for循环可以轻松实现这一点,如`for i in range(len(nums)):`。 3. **哈希表(Hash Table)**: Python的字典(Dictionary)是一种内置的哈希表数据结构,用于快速查找。在这里,我们可以创建一个空字典来...

    MD5加密器源代码(C#)

    C#的MD5加密在实际应用中要注意的一点是,由于MD5存在碰撞问题(即不同的数据可能会产生相同的哈希值),它已经不适用于安全性要求极高的场景,如密码存储。对于密码,更推荐使用更安全的哈希算法,如SHA-256或...

    esoc_latest.tar.gz

    标题"esoc_latest.tar.gz"暗示我们正在处理一个与交换机设计相关的项目,而描述中提到的内容证实了这一点。这个压缩包包含了FPGA实现的源代码和详细设计文档,这对于理解交换机的内部工作原理至关重要。 首先,FPGA...

    对COOKIE的一点小技巧

    首先,理解Cookie的基本概念: 1. **工作原理**:服务器通过HTTP响应头中的Set-Cookie字段向浏览器发送Cookie,浏览器会保存这个Cookie,并在后续的HTTP请求中通过Cookie头将Cookie信息返回给服务器。 2. **生命周期...

    必须知道的网络安全知识.docx

    准确地说,关于信息安全或者信息保障主要有两个要素:首先,正确配置系统和网络并且保持这种正确配置,因为这一点很难做到完美;第二个要素就是清楚知道进出网络的流量。这样的话,当发生严重的问题时,你就能检测出...

    Laravel开发-laravel-secure-passwords .zip

    以下是对这个主题的详细探讨。 ...Bcrypt是一种基于 Blowfish 加密算法的哈希函数,它具有很高的安全性。... ...在Laravel中,`Hash` Facade用于处理密码哈希。...理解并正确使用这些特性,将有助于构建安全的用户认证系统。

    sha1_glue.rar_glue

    描述中提到“Glue code for the SHA1 Secure Hash Algorithm assembler implementation”,进一步证实了这一点。SHA1是一种广泛使用的安全散列函数,它能够将任意长度的数据转化为固定长度的摘要,常用于数据完整性...

    SHA_Driver.zip_SHA256

    例如,他们可能有一个`sha256_hash(const void *data, size_t len, uint8_t hash[32])`这样的函数,接受输入数据、长度和一个存放结果的缓冲区,然后返回256位的哈希值。 总之,SHA256是现代密码学的重要组成部分,...

    Perl 语言常见问题集

    9. **上下文**:Perl中的操作会根据当前上下文返回不同类型的值,理解这一点对于正确使用函数至关重要。 10. **脚本命令行参数**:Perl脚本可以通过@ARGV数组接收命令行参数,这在编写可定制的脚本时非常实用。 ...

Global site tag (gtag.js) - Google Analytics