java 实现hashmap_自己用java实现HashMap,然后与系统提供的HashMap作比较

解疑答惑;

首次进我博客的“蓝杰们”也许会有个奇怪的想法,为什么,我不使用iteye播博客。而是新浪博客,我的解释很简单,不是不喜欢iteye博客,而是我喜欢它。它却不喜欢我,这是为何呢?举个列子就说清楚了,我发了六七次文章在iteye博客里面,当然那时应该叫javaeye,我的博客锁了五六次。每次锁了,我还要很不情愿的给管理员发份邮件,告诉他老人家,我得博客锁住了,请大哥你行行好,开开门。开完门之后,再发博客,照锁不误。提示的原因说是有什么敏感词汇。我一个个看下去,中文没有问题。英文是代码,也应该没有问题,但是问题就是,的的确确出问题了,我得博客被锁了。想来想去,我就只能怪就它不喜欢我得缘故,那么我只能换地方,不能呆在一个地方坐以待毙,除非新浪等其他博客也不要我了,那我就只能不写了。好了,言归正传!

只言片语:

哈希表是一个神奇的东西,为了让它相当神奇,我付出了神奇的努力,终于可以拿出来见人了。毕竟,这东西就拿来比较的,在此是和系统的哈希map来比较的。

设计思路:

在这里,自己实现的哈希map融合了链表和数组两者,也就是说,融合了数组查找方便、链表删除简便的同时也融合了链表遍历复杂,浪费时间,,以及数组删除麻烦的缺点;但是,总的来说,这里扬了长避了短;最大程度的使得整个程序的性能达到一个比较高的层次;

其实,我们主要是用数组来存储链表的,应该具体的说是,一个数组的每个索引位置都存放着一个链表的头结点,通过头结点就可以获取它的子节点,当然此数组的数据类型是链表型的。用图形来阐述这种思想的话,如图所示:

a4c26d1e5885305701be709a3d33442f.png

而每个节点的话,里面存放的是我们的用户对象;用户对象里面就包含了用户的相关信息;

在此,定义了四个类;

1.Item :

此类的其实就是一个用户类;

里面包含了用户相关的属性和方法;如:用户名,密码;

2.LinkNode :

这个类就是链表的节点类;

含有Item数据类型的属性。

3 HashMap :

我们真正要实现的一个类;

里面含有存放,获取,删除,以及当装载因子达到一定的值得时候,将原先的数据重新第一一个二倍原先数组长度的数组,进而在此通过哈希函数来在此确定老数据在新数组里面的索引位置;

4 TestMain 类;

用来测试我们自己定义的哈希map。

其实,整个的过程就是我们将一个Item类型的对象给其赋值等操作,,然后,将这个Item对象作为LinkNode类的属性值来将其绑定和LinkNode类型的对象;再将其LinkNode对象的给数组,如果数组的索引位置是非空的,那么我们将这个要放入数组中的节点指向数组中原本存放的节点,然后将此节点赋值给此数组索引位置,通俗点来说,就是每次将要放得节点当成一个头结点来放入数组中,而数组中原先的节点全都往后移。这样在置放节点的时候就可以大大的降低时间复杂度,提高系统的性能,否则的话,要每次放入新节点的时候还要遍历原先对应索引位置的链表找出最后一个节点。然后将其加到最后的节点后面,这本身就是一件非常复杂的事情。这是我们在设计的时候就应该不避免的。

这里有些东西需要实现说明一下:

装载因子loadfactor :

对于一个指定的数组来说,当它里面每次置放的时候索引位置的数值从null变成!Null的过程,计数器count从0开始增加,每从null变为!Null的时候count加一。Count和数组的总长度length的比值就是装载因子,各个系统的装载因子一般是不一样的,在java里面一般是0.75。我们在置放的函数调用的时候要进行判断,当loadfactor大于等于0.75的时候,那么要进行ReHash();

哈希函数HashCode();

在这里的目的是为了得到某个节点在数组中存放的位置index。一般情况下我们拿用户对象的某个属性值来计算。比如说,在此,我们使用用户的用户名模数组的长度来实现的。这里username是整形的变量。

即索引位置是index =username%length;

当用户的某个属性为非数字类型的时候,比如说这里的用户名是String类型,那么我们可以调用系统的username.Hashcode();得到,然后再除以数组的长度取模来得到索引的位置。

代码专区:

1.Item类:

*******************************************************************************

package cn.netjava2;

public class Item {

private int username; // 账户定义为整形,方便我们哈希函数的操作,当然可以根据自己的需要定义成String类型也好。

private String password;// 定义一个密码变量;

public void setUsername(int username) {

this.username = username;

}

public int getUsername() {

return username;

}

public void setPassword(String password) {

this.password = password;

}

public String getPassword() {

return this.password;

}

}

*******************************************************************************

2.LinkNode类

*******************************************************************************

package cn.netjava2;

public class LinkNode {

private Item it; // 定义一个Item数据类型的变量;

private LinkNode NextNode;//节点的下一个值;

public void setItem(Item it) {// 给Item类型的变量赋值

this.it = it;

}

public Item getItem() { // 获取Item变量类型的值;

return this.it;

}

// 定义一个哈希函数;

public int HashCode(int username, int length) {

int index = username % length;

return index;

}

// 将当前的node值赋给下一个节点,让其引用;

public void setLNextNode(LinkNode node) {

NextNode = node;

}

// 返回下一个节点;

public LinkNode getNextNode() {

return this.NextNode;

}

}

*******************************************************************************

3.HashMap 类;

*******************************************************************************

package cn.netjava2;

public class HashMap {

private LinkNode[] Lnode; // 我们的目标是要将所存放的数据依次进行封装,然后依次放入

// 这个实现的过程是,先将Item用户对象进行封装,放置在节点LinkNode 对象里面,然后把LinkNode 节点的首节点,也就是根节点放入到

// 数据类型为节点LikNode 类型的数组中;

private static int length = 20; // 数组的初始化长度;

private static int count = 0; // 当数组里面的空间位置每被占用一个,那么数组苏勇的空间就加一;

// 当我们每次实例化HashMap对象的时候,我们就应该让它同时生成一个节点类型的数组;

public HashMap() {

Lnode = new LinkNode[length];

}

public void Put(LinkNode node) { // 将一个已经封装好Item对象的节点放入节点数组l里面;

// 获取置放数组的索引位置;

int index = node.HashCode(node.getItem().getUsername(), length);

LinkNode onenode = Lnode[index];

// 判断此索引位置的情况;

if (onenode == null) {// 当为空值的时候,直接放入,此节点则为根节点;此时,数组的空间被占用了一个,数组的空间计数器则加一;

Lnode[index] = node;

count++;

if (loadSize(count)) {

ReSize();

}

} else {

// 将数组里面的根节点值,赋给下一个,而将我们要添加的节点node放入数组中,充当根节点的角色;

// 这样做的好处是,不用添加的时候编遍数组索引对应的节点。再添加到末节点,降低时间复杂度,更好的提高系统执行的性能,方便用户;

node.setLNextNode(onenode);

Lnode[index] = node;

}

}

// 得到我们所需要的值;比如说,通过一个用户名来得到存放在其中的密码值或者其它信息与用户相关的;

public String getPassword(int username) {

LinkNode node = new LinkNode();

int index = node.HashCode(username, length);

LinkNode lnode = Lnode[index];

String password = "";

while (lnode != null) {

// 从次索引对应的节点中得到Item用户对象,已经对户对象的用户名;如果相同则返回此用户名所对应的密码;

if (lnode.getItem().getUsername() == username) {

password = lnode.getItem().getPassword();

}

lnode = lnode.getNextNode();

}

return password;

}

// 删除一个节点,来清空一个我们不需要了的用户对象;

public void DeleteLNode(int username) { // 通过用户名来删除节点;

// 调用哈希函数来获取索引的位置;

LinkNode node = new LinkNode();

int index = node.HashCode(username, length);

LinkNode rootnode = Lnode[index];

while (rootnode != null) {

LinkNode nextnode = rootnode.getNextNode();

if (nextnode.getItem().getUsername() == username) {

rootnode = nextnode.getNextNode();

nextnode = null;// 如果此节点为需要删除的节点,那么将此目标节点的前一个节点指向目标节点的后一个节点,完成删除过程;

// 同时将此节点清空;

if (rootnode == null) {

Lnode[index] = null;

count--;

} else {

Lnode[index] = rootnode;

}

} else {

//

nextnode = nextnode.getNextNode();

}

}

}

// 当数组里面的存储量超过一定的范围的时候,那么我们需要对其进行扩充数组的操作;

public void ReSize() {

// 建立一个新的数组;容量为前一个数组的两倍;z再次操作之前,我们需要对数组的容量置零;

count = 0;

LinkNode[] newLnode = new LinkNode[length * 2];

// 遍历原先的数组,取出其中的值,然后放入新的数组之中;

for (int i = 0; i 

LinkNode node = Lnode[i];

LinkNode nextnode;

while (node != null) {

nextnode = node.getNextNode();

if (nextnode != null) {

int username = nextnode.getItem().getUsername();

int index = nextnode.HashCode(username, length * 2);

if (newLnode[index] == null) {

newLnode[index] = nextnode; // 如果要置放的新数组索引没有被占用,则直接放在数组所有的位置

} else {

LinkNode Node = newLnode[index]; // 如果已经被占用了,那么将这个节点从数组中取出来;,放到要放入节点的后面;

nextnode.setLNextNode(Node);

newLnode[index] = nextnode;

}

}

node = nextnode;

}

}

length = length * 2;

Lnode = newLnode;

}

// 装载因子的判断;

public boolean loadSize(int count) {

boolean value = false;

int load = count / length;

if (load >= 0.75) {

value = true; // 当因子值是真的时候,说明此时,应该扩展空间;

}

return value;

}

}

*******************************************************************************

4.TestMain 类;

*******************************************************************************

package cn.netjava2;

import java.util.Date;

public class MainTest {

public static void main(String[] args) {

MainTest mt = new MainTest();

// 调用系统提供的;

int pvalue = 1000;

int gvalue = 300;

// mt.TestSystemHashMap(pvalue,gvalue);

System.out.println();

// 调用自己实现的哈希map;

mt.TestMyHasgMap(pvalue, gvalue);

}

// 测试自己的HashMap的方法;

public void TestMyHasgMap(int pvalue, int gvalue) {

HashMap hm = new HashMap();

// 测试10000组值;

int key1 = pvalue;

Date d1 = new Date();

long n1 = d1.getTime();

while (pvalue-- > 0) {

Item it = new Item();

it.setUsername(pvalue);

it.setPassword(pvalue + "");

LinkNode node = new LinkNode();

node.setItem(it);

hm.Put(node);

}

Date d2 = new Date();

long m1 = d2.getTime();

// javax.swing.JOptionPane.showMessageDialog(null,

// "MyHashMap置放"+key+"个数据用的时间是:"+(m-n)+"毫秒");

System.out.println("MyHashMap置放" + key1 + "个数据用的时间是:" + (m1 - n1)

+ "毫秒");

int key2 = gvalue;

Date d3 = new Date();

long n2 = d3.getTime();

while (gvalue-- > 0) {

System.out.println("密码 " + hm.getPassword(gvalue));

}

Date d4 = new Date();

long m2 = d4.getTime();

// javax.swing.JOptionPane.showMessageDialog(null,

// "MyHashMap置放"+key+"个数据用的时间是:"+(m-n)+"毫秒");

System.out.println("MyHashMap获取" + key2 + "个数据用的时间是:" + (m2 - n2)

+ "毫秒");

}

// 测试系统HashMap的方法;

public void TestSystemHashMap(int value1, int value2) {

java.util.Map Hmap = new java.util.HashMap();

int key1 = value1;

Date d1 = new Date();

long n = d1.getTime();

while (value1-- > 0) {

Hmap.put(value1 + "", value1 + "");

}

Date d2 = new Date();

long m = d2.getTime();

System.out.println("SystemHashMap置放" + key1 + "个数据用的时间是:" + (m - n)

+ "毫秒");

// javax.swing.JOptionPane.showMessageDialog(null,

// "SystemHashMap置放"+key+"个数据用的时间是:"+(m-n)+"毫秒");

int key2 = value2;

Date d3 = new Date();

long n1 = d3.getTime();

while (value2-- > 0) {

Hmap.get(value2 + "");

}

Date d4 = new Date();

long m1 = d4.getTime();

System.out.println("SystemHashMap获取" + key2 + "个数据用的时间是:" + (m1 - n1)

+ "毫秒");

}

}

*******************************************************************************

测试结果:

有图有真相。嘿嘿!

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

单独测试一个获取密码的函数。我讲系统的和自己的对比。因为打印很多的缘故先取少点。我们在put的时候,密码和用户相同,我们来测试一下,是不是可以得到username为1-25之间的用户密码是不是为0-25;此取值限于打印篇幅的缘故。

a4c26d1e5885305701be709a3d33442f.png

来看系统的

a4c26d1e5885305701be709a3d33442f.png

这样看起来系统的要少一点,可是有一个比较重要的原因,那就是我们打印时需要时间的。将打印语句去掉之后,我们再看

a4c26d1e5885305701be709a3d33442f.png

是不是一样了。从上述的多组测试中,我们就可以看出来,自己实现的哈希map相对要性能高一些。并且,当测试的数据上百万的时候,我们自己的hashmap换可以承受,但是系统提供的就崩溃了。看图:

a4c26d1e5885305701be709a3d33442f.png

这是我们自己的hashmap实现的。依然可以正常运行。且看。系统的

a4c26d1e5885305701be709a3d33442f.png

出错了吧。错误时超出存储量。

整个思路一开始就想通了,但是实现起来还是有一定的难度的,遇到的困难是节点哪儿,当时没有很好的处理节点之间的关系,然后就一直卡住。至于其他地方慢慢的摸索着弄开的,不会了画图,画不出来继续想。终于也算是实现了,伸个懒腰睡觉觉吧。

结束!(鼓掌)

a4c26d1e5885305701be709a3d33442f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值