`
zhuyifeng
  • 浏览: 45045 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

Hash表的建立及增删改查相关操作

 
阅读更多

1、什么是hash?

 

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

也许这么听大家肯定会对hash不知所云,晕晕乎乎。说实话,我也是这样。所以根据我自己的理解呢,hash就是把一段数据通过某种方法映射成另外一个值(相当于x映射为f(x)),也就是一个单向加密的过程。这个方法可以自己定义,也可以去网上找现成的,比如MD5的加密算法就用到了hash。

 

 

 

2、为什么要用hash?

 

当然是用来加密数据了~~~不错,不过这却不是我要说的。hash还可以用来干许多事比如说快速建立索引及查找。举个最现实的例子:QQ同时在线人数平均在几千万~近两亿(极端情况不考虑),每个用户都是一个User的类保存在腾讯的服务器上,存储这些数据也许可以用数组,也可以用链表,不过我们很快就会发现这种方法的缺陷。假如说这时,有一个用户上线了,腾讯的服务器必须先检测是否该QQ号是否已经登录了。若是,则向原来登录的客户机发送离线消息,然后再让新的用户上线;若不是,则直接让该用户上线。不管是哪种情况,都需要进行查找遍历。如果用数组或链表,那么都得从头开始排查,最差的情况是里头有多少元素就要排查多少次,这已经大大降低了查询效率,况且一个时刻上线下线的人数少说也得数以百计,并且之后还伴随着添加和删除过程(相对来说,数组是查询快,增删操作慢,而链表正好相反),那么这么看来,单纯的用数组或是链表是行不通的了。

那么我们应该怎么解决呢?用hash,当然!不过具体要怎么实行?首先我们建立一个模型:一个长度为n的数组,每一个数组元素存的是一段链表的头节点,链表长度为m(每个链表的m值相互无关),而链表的每个节点存储的则是一个User类的对象,这样无形中就形成了一个二维的数据存储结构,我们称之为数组加链表模型。假如有0~99这100个数需要存到这种模型中,我们可以让数组的第一个元素专门存储第一位为0的数(即一位数),链表长度为10,存储的数据分别为0、1、2、3、4、5、6、7、8、9;第二个元素存储第一位为1的数……以此类推,直到全部存进。现在我们要查找65,假设数组名为a,我们只需要找到a[6],然后再循环查找链表的下一个节点,直到找到65,只需要5次。而用链表查找,则需要查找65次。当然如果用数组定义的话直接a[65]就能得到,但是如果需要在65与66之间再插入一个值就比较麻烦了,而上述数组加链表模型依然能轻松的解决这一问题。

现在我们回到QQ用户的问题,若是对于不同的用户能够尽可能的把他们分散到数组加链表模型的各处的话,查找和增删等操作就迎刃而解了,采取一种什么样的机制让他们分散呢?这就是hash方法了。我们知道每个用户的QQ号是唯一的,所以我们可以用该数值来进行hash运算。假如QQ同时在线人数为1亿,我们采取取模的hash算法,我们建立一个数组名为a,长度为100000的数组加链表模型,然后用QQ号的值对100000取模,比如说QQ号为123456789,取模后为56789,那么我们就把他存入a[56789]的链表中。当然不止一个QQ号取模后结果为56789,对于这些QQ号,新来一个就把他加到链表的最后一个节点的后面并设置链表相邻元素间的指向关系。全部遍历一遍建立好了之后再找某个QQ号就可以先取模,然后到指定的数组链表上去找了,在这里最多只需要10000次即可找到(针对九位数的QQ)或确定不存在。

 

 

 

3、需要注意的方面:

 

首先是hash方法的确定,在这方面每个人有自己的想法,根据hash算法和初始容量然后初步确定数组长度n的大小,要使得数据尽量散列开来(所以hash函数一般翻译为“散列”嘛)。

其次就是随着用户的增多会导致链表的越来越长,当数组长度不变之后每个元素的链表长度却在一个劲地增加,长此以往,这个模型就会变得越来越像纯链表,也就慢慢偏向无效率了。所以在适时的时候,即链表长度和数组长度达到某一关系后我们就应该扩充数组长度了,当数组长度一变,取模的值也就变了,就是说原来的hash结果已经改变了,无法再适用到新的长度的数组上,所以我们需要对原来的每个元素重新hash一次,称之为rehash过程。可以想象,rehash的代价是巨大的,但是这也是必须的。

 

 

 

4、拓展方面:

 

其实既然有了数组加链表模型,那么我们可以继续设置数组加数组加链表模型,或者在前面套n个数组,但是前提条件是该用户类必须有n个字段值是唯一的,若QQ设置用户昵称也不能一样,那么我们在第二个数组里可以对用户昵称再进行hash一下,建立一个索引,然后加入链表中。

 

 

 

以下是简单实现的一个数组加链表模型:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;

public class HashTest {

	int prime;

	public HashTest() {
		Scanner sc = new Scanner(System.in);
		int count = 0;
		System.out.println("请输入需要添加的个数(值由系统随机生成):");
		count = sc.nextInt();
		long start = System.currentTimeMillis();
		MyThread th = new MyThread();
		th.start();
		int size = 2 * (int) Math.sqrt(count);
		prime = Prime(size);
		User DataArray[] = new User[size];
		h: for (int i = 0; i < count; i++) {
			System.out.println(i);
			int value = random();
			int addr = hash(value);
			// 排查重复
			User checkuser = new User();
			try {
				checkuser = DataArray[addr];
			} catch (ArrayIndexOutOfBoundsException aioobe) {
				aioobe.printStackTrace();
				System.out.println(DataArray.length);
			}
			while (checkuser != null) {
				if (checkuser.getid() == value) {
					continue h;
				}
				checkuser = checkuser.getnext();
			}
			User u = new User(value);
			DataArray = addto(addr, u, DataArray);
		}
		th.setflag(false);
		System.out.println("共用了"
				+ ((System.currentTimeMillis() - start) / 1000) + "秒");
		System.out.println("添加成功。\t\n是否继续操作:\t\n1:继续\t0:退出");
		int choose = new Scanner(System.in).nextInt();
		while (choose != 0 && choose != 1) {
			System.out.println("输入错误,请重新输入");
			sc = new Scanner(System.in);
			choose = sc.nextInt();
		}
		if (choose == 0) {
			System.exit(0);
		} else {
			System.out
					.println("请输入需要进行的操作:\t\n1:增加\t2:删除\t\n3:修改\t4:查询\t\n0:退出");
			int result = new Scanner(System.in).nextInt();
			while (result != 1 && result != 2 && result != 3 && result != 4
					&& result != 0) {
				System.out.println("输入错误,请重新输入");
				sc = new Scanner(System.in);
				result = sc.nextInt();
			}
			switch (result) {
			case 1:
				add(DataArray);
				break;
			case 2:
				del(DataArray);
				try {
					Thread.sleep(2000);
					query(DataArray);
				} catch (Exception e) {
				}
				break;
			case 3:
				fix(DataArray);
				try {
					Thread.sleep(2000);
					query(DataArray);
				} catch (Exception e) {
				}
				break;
			case 4:
				query(DataArray);
				break;
			case 0:
				break;
			default:
			}
			System.exit(0);
		}
	}

	/**
	 * 获取质数
	 * 
	 * @param len
	 *            数组长度
	 * @return 离len数值最近的最大的质数
	 */
	int Prime(int len) {
		int sqr = (int) Math.sqrt(len);
		while (len > 2) {
			for (int i = 2; i < sqr; i++) {
				if (len % i == 0) {
					break;
				}
				if (i == sqr - 1) {
					return len;
				}
			}
			len--;
		}
		return len;
	}

	/**
	 * 生成随机数
	 * 
	 * @return 生成的随机数
	 */
	int random() {
		return new Random().nextInt(900000000) + 100000000;
	}

	/**
	 * 哈希算法
	 * 
	 * @param value
	 *            传入的值
	 * @param prime
	 *            计算数组长度所得的质数
	 * @return 哈希算法后的结果
	 */
	int hash(int value) {
		int sum = 0;
		while (value / 10 != 0) {
			sum += Math.pow(8, value % 10);
			value /= 10;
		}
		return sum % prime;
	}

	/**
	 * 添加元素
	 * 
	 * @param addr
	 *            数组的第几号元素
	 * @param u
	 *            加入的User对象
	 * @param Array
	 *            加至的数组
	 */
	User[] addto(int addr, User u, User[] Array) {
		if (addr > Array.length) {
			throw new RuntimeException("地址超出数组长度,添加失败");
		}
		if (Array[addr] == null) {
			Array[addr] = u;
		} else {
			User user = Array[addr];
			// 链表节点数
			int amount = 0;
			while (user.getnext() != null) {
				amount++;
				user = user.getnext();
			}
			user.setnext(u);
			u.setlast(user);
			if (amount > Array.length * 0.8) {
				// 链表节点过多,扩展数组
				System.out.println("链表节点过多,扩展数组");
				Array = rehash(Array);
			}
		}
		return Array;
	}

	/**
	 * 当数组需要扩张时的重新散列方法
	 * 
	 * @param Array
	 */
	User[] rehash(User[] Array) {
		User[] reArray = new User[(int) (Array.length * 1.5 + 100)];
		List<User> list = new ArrayList<User>();
		prime = Prime(reArray.length);
		for (int i = 0; i < Array.length; i++) {
			for (User u = Array[i]; u != null; u = u.getnext()) {
				list.add(u);
				try {
					// 把原本的上下链关系全部断开
					u.getlast().setnext(null);
					u.setlast(null);
				} catch (NullPointerException npe) {
					System.out.println("空指针");
				}
			}
		}
		for (int i = 0; i < list.size(); i++) {
			// 逐个添加
			User u = list.get(i);
			int addr = hash(u.getid());
			addto(addr, u, reArray);
		}
		Array = reArray;
		return Array;
	}

	void add(User[] DataArray) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入需要添加的个数(值由系统随机生成):");
		int count = sc.nextInt();
		long start = System.currentTimeMillis();
		MyThread th = new MyThread();
		th.start();
		h: for (int i = 0; i < count; i++) {
			int value = random();
			int addr = hash(value);
			User checkuser = DataArray[addr];
			while (checkuser != null) {
				if (checkuser.getid() == value) {
					continue h;
				}
				checkuser = checkuser.getnext();
			}
			User u = new User(value);
			DataArray = addto(addr, u, DataArray);
		}
		th.setflag(false);
		System.out.println("花费了"
				+ ((System.currentTimeMillis() - start) / 1000)
				+ "秒后增加成功,即将显示结果……");
		try {
			Thread.sleep(2000);
		} catch (Exception e) {
		}
		query(DataArray);
	}

	void del(User[] DataArray) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入需要删除的用户的id值:");
		int id = sc.nextInt();
		int addr = hash(id);
		User u = DataArray[addr];
		if (u != null) {
			if (u.getid() == id) {
				DataArray[addr] = u.getnext();
				System.out.println("元素已成功删除,即将显示结果……");
				return;
			} else {
				while (u.getnext() != null) {
					u = u.getnext();
					if (u.getid() == id) {
						if (u.getnext() == null) {
							u.getlast().setnext(null);
							System.out.println("元素已成功删除,即将显示结果……");
							return;
						} else {
							u.getlast().setnext(u.getnext());
							u.getnext().setlast(u.getlast());
							System.out.println("元素已成功删除,即将显示结果……");
							return;
						}
					}
				}
			}
		}
		System.out.println("未找到相应元素,即将显示结果……");
	}

	void fix(User[] DataArray) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入需要修改的用户的id值");
		int id = sc.nextInt();
		int addr = hash(id);
		User u = DataArray[addr];
		while (u != null) {
			if (u.getid() == id) {
				System.out.println("请设置其名字:");
				Scanner scan = new Scanner(System.in);
				String name = scan.next();
				u.setname(name);
				System.out.println("已成功更新数据,即将显示结果……");
				return;
			}
			u = u.getnext();
		}
		System.out.println("未找到相应元素,即将显示结果……");
	}

	void query(User[] DataArray) {
		int all = 0;
		int mount = DataArray.length;
		System.out.println("共有" + mount + "个元素");
		for (int i = 0; i < mount; i++) {
			int num = 1;
			User u = DataArray[i];
			if (u != null) {
				while (u.getnext() != null) {
					num++;
					u = u.getnext();
				}
			} else {
				num = 0;
			}
			all += num;
			System.out.println("第" + i + "个元素的链表中共有" + num + "个对象");
		}
		System.out.println("共有" + all + "个对象");
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new HashTest();
	}

	class User {
		private int id;
		private User nextuser, lastuser;
		private String name;

		public User() {
		}

		public User(int id) {
			this.id = id;
		}

		public int getid() {
			return id;
		}

		public void setnext(User u) {
			this.nextuser = u;
		}

		public User getnext() {
			return nextuser;
		}

		public void setlast(User u) {
			this.lastuser = u;
		}

		public User getlast() {
			return lastuser;
		}

		public void setname(String name) {
			this.name = name;
		}

		public String getname() {
			return name;
		}
	}

	class MyThread extends Thread {
		public boolean flag = true;

		public void run() {
			while (flag) {
				System.out.println("正在添加中,请等待……");
				try {
					Thread.sleep(5000);
				} catch (Exception e) {
				}
			}
		}

		public void setflag(boolean flag) {
			this.flag = flag;
		}
	}

}

 

1
0
分享到:
评论

相关推荐

    C语言实现的Hash哈希表

    哈希表(Hash Table)是一种数据结构,它通过哈希函数将关键字映射到数组的索引位置,以此实现快速的查找、插入和删除操作。在C语言中实现哈希表,我们需要理解哈希函数的设计、冲突解决策略以及动态扩容等核心概念...

    thinkPHP框架通过Redis实现增删改查操作的方法详解

    在本文中,我们将深入探讨如何在thinkPHP框架中利用Redis实现增删改查操作。首先,Redis是一个NoSQL数据库,以其高效和丰富的数据结构而受到欢迎。然而,thinkPHP框架默认仅支持Redis作为缓存工具,不直接提供完整的...

    原生php登录增删改查

    本文将详细讨论“原生PHP登录增删改查”这一主题,帮助你深入理解如何利用PHP实现用户登录系统以及数据库操作的基本功能。 一、原生PHP登录系统 1. 用户验证:登录系统的核心是验证用户的用户名和密码。原生PHP中,...

    redis 连接增删改查

    本教程将深入探讨 Redis 的连接管理、数据操作(增删改查)以及相关优化策略。 首先,我们来了解如何在编程环境中建立 Redis 连接。在 .NET 环境下,我们可以使用 StackExchange.Redis 库。安装该库后,通过 `...

    Java操作redis实现增删查改功能的方法示例

    在Java中操作Redis实现增删查改功能,通常会使用Jedis库,这是一个非常流行的Java Redis客户端。在本文中,我们将深入探讨如何使用Java与Redis交互,并实现基本的数据操作。 首先,确保你的开发环境中已经安装并...

    c代码-散列表的建立,查找,插入,删除

    散列表(Hash Table)是一种数据结构,它通过关联键(Key)与值(Value)来存储数据,提供了快速的插入、查找和删除操作。在C语言中,实现散列表通常涉及以下几个关键步骤: 1. **散列函数设计**:散列函数是将键...

    redis客户端操作实战-redisDemo.zip

    4. 键空间通知:开启Redis的键空间通知,客户端可以监听特定事件,如键的增删改查,实现动态监控。 四、代码示例 在"redisDemo-master"项目中,可能会包含以下代码片段: 1. 初始化Redis客户端:创建Jedis或Lettuce...

    redis java操作demo

    本篇将详细讲解如何使用Java操作Redis,包括各个数据类型的操作以及增删改查的基本方法。 首先,我们需要在Java项目中引入Jedis库,它是Java操作Redis最常用的客户端。可以通过Maven或Gradle将其添加到构建文件中。...

    redisdesktopmanager

    3. **增删改查**:Redis Desktop Manager支持基本的数据操作,包括添加新键、删除键、修改键值以及查询键值。对于复杂类型如Hash、List、Set和Sorted Set,可以逐个元素进行操作,或者批量处理。 4. **数据导入与...

    Redis小项目----实现新闻数据的相关操作

    在本项目中,我们主要利用Redis数据库来实现一个新闻数据管理的小系统,涵盖了新闻数据的增删改查以及分页等基本操作。Redis是一个高性能的键值存储系统,常用于缓存、消息队列和实时数据存储等领域。下面将详细阐述...

    数据库知识培训.pptx

    系统权限由DBA授予,如create session、create table等,而对象权限由对象所有者管理,包括对表的增删改查和执行存储过程的权限。数据字典和V$视图的访问权限也是对象权限的一部分。 三、SQL语句 SQL分为DML(Data ...

    MySQL 45 道面试题及答案.docx

    存储过程,就是一些编译好了的SQL语句,这些SQL语句代码像一个方法一样实现一些功能(对单表或多表的增删改查),然后给这些代码块取一个名字,在用到这个功能的时候调用即可。存储过程的优点:1、存储过程是一个预...

    基于PHP的MYSQL数据库管理工具UTF8 hc.zip

    3. 数据操作:增删改查(CRUD)操作,支持批量操作和条件筛选。 4. 查询构建器:提供图形化的界面帮助用户构建复杂的SQL查询。 5. 导入导出:将数据库或表的数据导入/导出为CSV、XML、JSON等格式。 6. 权限控制:对...

    PHP实例开发源码—PHP烈火文章管理系统源码.zip

    文章管理模块通常包括文章的增删改查操作。在PHP中,这可以通过HTTP请求处理,例如POST请求用于创建新文章,GET请求用于读取文章,PUT请求更新文章,DELETE请求删除文章。这些请求会被PHP脚本捕获并处理,与数据库...

    MySQL中对于索引的基本增删查改操作总结

    `index_type`指定了索引的实现方式,如BTREE(默认,适用于大部分场景)和HASH(常用于内存表)。`index_col_name`是你要为其创建索引的列名,可以是单个列或多列,多列之间用逗号分隔。对于VARCHAR类型的字段,可以...

    Redis单机和集群测试Java端代码.zip

    3. 数据操作测试:对集群中的键值对进行增删改查操作,确保操作在正确的节点上执行,并且数据一致性得到保障。 4. 故障转移测试:模拟节点故障,检查集群能否自动恢复并重新分配槽。 5. 性能测试:测量集群在高并发...

    【IT十八掌徐培成】Java基础第22天-03.MySQL常用命令avi.zip

    在IT领域,数据库管理是至关重要的技能之一,尤其是...学习这个资源,你将能够熟练地在Java项目中使用MySQL数据库,进行数据的增删改查以及更复杂的操作。如果你深入理解和实践这些知识,将大大提高你的Java开发能力。

    0-总体设计1

    设计目标包括实现一个支持单表增删改查、精确查找和范围查找、索引以及基本数据类型的数据库,同时构建一个具有高可用性的CP系统,能够动态增减节点并自动进行数据迁移。 在模块划分上,系统被分为以下几个部分: ...

    系统的学习mysql.zip

    - DML(Data Manipulation Language):对数据进行增删改查,如`INSERT INTO`、`DELETE FROM`、`UPDATE`、`SELECT`。 - TCL(Transaction Control Language):事务控制,包括`BEGIN`、`COMMIT`、`ROLLBACK`。 - ...

Global site tag (gtag.js) - Google Analytics