在这一篇里我主要说一下自己对于系统自带各种hash函数的看法。
1)hashMap:
hashMap是一种基于Map的结构,通过键值Key存储对应的Value,构造原理与我的hash代码相同(通过hash函数的计算,得出index,存入数据),但是具体的实现存在一些差异。首先,这里插入数据时需要指定key与Value值,put方法得到用户输入的key后将Key.hashCode通过位运算转换为int(hashCode返回的是对象存储的物理位置,两个对象如果不相同,那么他们的hashCode必然不同),然后对所得的整数与数组长度进行计算得出index插入位。
其实看源码会发现,这里实际实现存储的还是一个数组(此数组定义使用了关键字transient,即串行化时不会包含此变量,我对于串行化的理解就是将对象进行存储或者传输,然后可以再读取所存储的字节重新构建一个与原始对象状态相同的对象,也就是反串行化。比如定义了String name以及transient String pwd,我们实例化一个具有这两个属性的对象a("艾","123"),然后将对象a通过流对象写出到磁盘,再读入后打印,我们会发现pwd属性变为了空。我觉得transient关键字就像一种保护措施,比如银行等机构要将用户操作存入磁盘或者备份,又不能将密码等私人信息写入,便可以使用此方法使操作如往常一样执行,同时也避免了信息泄露),数组中存储了定义的Entry<K,V> e,计算得到index后便找到数组table中的这一位,对内部存储的链表结构进行遍历查找(e = e.next),如果hash(由hashCode转换得出,所以除非两个元素相同才会有相同的hash)与key都相同,便修改已存储的值,如果未找到相同元素且找到了e = null,即将相应元素插入这个位置。get方法的实现与put同一原理,先根据输入的key计算hash值,得出index,找到对应数组节点,在节点内的链表结构中遍历寻找所求数据。
再看源码的过程中,我也找到了几个使得代码高效的方法:
/** * Applies a supplemental hash function to a given hashCode, which * defends against poor quality hash functions. This is critical * because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */ static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * Returns index for hash code h. */ static int indexFor(int h, int length) { return h & (length-1); }
可以看出在将hashCode转换为int值的过程中,直接采用了位运算,在计算index时,也使用了&与运算,比如3&7=3,因为3的二进制表达为0011,7的二进制表达为0111,0011&0111与运算=0011。这些直接进行的位运算极大的提高了机器的操作效率,尤其是大量数据处理,比起我的直接对int进行操作确实高明了很多。还有一点是在将原有table中的元素转换入新的table时,采用了读取后直接转换存入的方法,我之前程序中是将所有的元素读出放入一个list,再遍历该list,将元素计算放入新的数组中,毋庸置疑这也导致了很大的时间上的消耗。这里,我将系统的reHash部分实现贴出来:
/** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); } /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
transfer代码中遍历了原来的table,读取每一个位置内部存储的元素,计算后放入新的table中,并清除原有数组中此位置的元素,还采用了e = e.next以及do、while方法寻找index链表内部的全部存储元素,要比我的递归查询速度快,最后将新的table赋给原有table。
看过了源码后,我将之前所写的代码里中间部分的list过度去除,数据操作的速度提升了一些,但还是和系统的有差距,问题应该在计算部分,我的代码中采用 存入值 mod 数组长度 的方法,着实不是很明白这里位运算的操作原理,是不是与运算在加快处理速率的基础上还能够提升数据存储的均衡性呢?求讲解~~~还有一点很深的感触就是java的源码真的写得很精简,用最少的行数实现了功能的最大化。起初阅读源码觉得有点不习惯,等慢慢理解了之后回过头看自己的代码,真的觉得差距很大,里面定义了各种其实不必要的变量或者过渡的操作,这属于代码质量的问题,好的代码习惯或者风格都是在平时点点滴滴积累起来的,以后一定要注意自己代码的“美观度”啊~~
在这里也提一下,记得原来在数据结构的课程中也了解过hash结构,跟我所写的hash有一点点的区别,其中包含了一个二次线性再散列的方法,主要用来处理当某一个元素位存储了过多的元素,如果此时再插入一个元素在这个位置,势必要进行很多次的遍历比较是否为相同元素,花费过高的时间代价。此时可以使用一个线性处理,比如index = 2 * index + a(这里只是打个比方,具体的函数可以自己去查阅),将元素存放在其他位置,这个过程也可以称之为散列,这样就可以规避一些过高花费的操作。当然,实际中这个问题也可以通过一些其它方式来解决,比如设计合理的装载因子以及hash函数,使得数组的负载处在一个均衡状态,在遍历花费还未对搜索时间造成可估计影响之前,经由判断调用reHash,重构数组。
2)HashSet:
HashSet实现了一个Set接口,其内部实现实际上就是一个HashMap,所以在这里我们不再赘述它的实现。在我对几个系统Hash类进行测试的时候也发现,HashSet打印出的数据是无序的,完全与Set的特性相一致,其余均为有序的存储。
3)HashTable:
常拿来和HashMap作对比的类,那么先说一下两者的区别吧:HashMap是一个实现了map接口的实现类(HashSet实现了Set接口),而HashTable是继承了Dictionary的子类;在HashMap中,允许使用null作为键,可是这样的键只有一个,可以有一个或多个键所对应的值为null,因此当get()方法返回null值时,即可以表示HashMap中没有该键,也可以表示该键所对应的值为null,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。HashTable中不允许出现null的键值;第三个区别是最重要的,也正是这个区别使得两个类存在了是否可以使用null值做键的差别—同步。如果阅读HashTable的源码,你一定会发现,除了HashMap中出现过的关键字transient被用来定义属性外,所有的方法都被一个关键字修饰,那就是Synchronized。
这个被称作同步的关键字主要是被用来保证在多线程的情况下,并发的操作会被控制使得每次只有一个线程执行操作,引用一个专业点的说法就是:当两个并发线程访问同一个对象object中的这个synchronized同步代码块时,一个时间内只能有一个线程得到执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块(在这里要声明一下:synchronized关键字无论加在方法上还是对象上,它锁定的都是对象,而不是把一段代码或者函数当做锁,而且每个对象只有一个锁与之相关联),当然,此时其他线程可以访问代码中的其他非Synchronized方法,但是要注意其他线程对object中所有其它synchronized同步代码块的访问将被阻塞,也就是说,当一个线程访问object的一个synchronized同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。也正是因为这一点,使用Synchronized关键字可能会导致庞大的系统开销,所以在使用的时候要慎重。
说完了两者的区别,我们来看一下HashTable的内部实现。HashTable也是基于map结构的存储,它的实现思路与我Myhash非常相像,比如在这段实现put的代码中,直接取key值的hashCode,& 0x7FFFFFFF实现了对这个32位值的首位取正数,然后将这个值与当前数组的长度取模,得到插入位置index:
get的原理与put相同,此处不再赘述。现在我们来看看Table中的reHash部分: 可以看出,在Hashtable中新建数组的长度是原来的两倍加1,相对于Hashmap中的do、while循环,它的新值存放通过潜逃的for循环、old = old.next、old !=null控制实现,其他的方法比较好理解,在这里也不再说,想要研究的同学自己去查看吧~ 在这里我再补充一点,相信大家再看代码的时候会注意到,HashMap以及HashTable都使用到了Entry<K,V>这个类来初始化数组索引位的对象,这与他们是基于map存储数据的特点相关,这里的Entry<K,V>是接口Map.Entry<K,V> 的实现类,接口内部定义了属性int hash、K key、 V value、Entry<K,V> next,还有一些方法名,不同的类实现的方法肯定会存在差异,但是接口的使用保证了外部输入、结果返回的一致性。 就写到这里了,文章里的都是自己的一些认识,很可能存在问题,欢迎大家指正~~public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<K,V>(hash, key, value, e);
count++;
return null;
}
protected void rehash() {
int oldCapacity = table.length;
Entry[] oldMap = table;
int newCapacity = oldCapacity * 2 + 1;
Entry[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)(newCapacity * loadFactor);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
发表评论
-
hash表
2011-12-14 14:12 995编程中常用 ... -
java树的概念(二叉树)
2011-08-19 21:15 2503在最近的学习中,我接触到了树的概念并且学习了二叉树 ... -
java集合框架总结
2011-08-10 12:09 868[size=small][/size] java中的集合 ... -
File与文件搜索器
2011-08-05 13:15 1040[size=small][/size] 经过最近对于F ... -
Java关键字
2011-08-05 12:05 713[/size]java中的关键字有总共有51个,再加上2个保留 ... -
接口与抽象类
2011-07-21 10:13 7491.为什么使用接口 与类相似,定义了一个模板,其中所要实 ... -
构造函数
2011-07-21 10:08 8801、构造函数的概念: 构造函数也叫做构造方法或者构造器 ... -
类和对象
2011-07-21 10:06 7451、对象是具体存在的、可被描述的非抽象的实体 一台电脑, ...
相关推荐
hashcat-6.1.1 hashcat-6.1.1 hashcat-6.1.1hashcat-6.1.1hashcat-6.1.1hashcat-6.1.1
IDM5可能代表“Internet Download Manager 5”,这是一个流行的下载管理工具,能够提升下载速度,支持断点续传,安排下载任务,甚至捕获网页视频。版本号“5”可能意味着这是该软件的第五个主要版本,通常每个大版本...
- `duplicate` 属性用于检测重复文件,通过文件名、大小和最后修改时间来生成唯一的hashKey,从而避免重复上传相同的文件。 - `formData` 定义了每次上传请求时携带的额外参数,这些参数可用于身份验证或上传策略...
1. **分布式哈希表(DHT,Distributed Hash Table)**:这是一种用于存储和查找数据的分布式数据结构,常用于P2P网络。DHT将数据分散存储在网络中的各个节点上,通过哈希函数确定数据存储的位置,使得节点间可以高效...
常见的校验方法有MD5(Message-Digest Algorithm 5)和SHA(Secure Hash Algorithm)系列。在C#中,`System.Security.Cryptography`命名空间提供了这些算法的实现。在下载过程中,我们可以计算已下载部分的校验值并...
为了保证不重复爬取使用Redis作hash表,所有爬取的任务都放到hash表中进行标记。 爬取太频繁会被知乎返回429(too many request),应对的策略是挂代理,一种方法是使用专业的云代理服务(有点贵),另一种是[自建代理...
一、A*搜索算法 一(续)、A*,Dijkstra,BFS 算法性能比较及A*算法的应用 ...十一、从头到尾彻底解析Hash 表算法 十二、快速排序算法之所有版本的c/c++实现 十三、通过浙大上机复试试题学SPFA 算法
* Hash 表空间大小和 Key 的个数决定了冲突率(或者用负载因子衡量),再合理的范围内,key 越多自然 hash 表空间越大,消耗的内存自然也会很大。 * 再加上大量指针本身是长整型,所占用的内存空间也很大。 解决 ...
常见的校验方式有MD5(Message-Digest Algorithm 5)和SHA(Secure Hash Algorithm)系列。MD5是一种广泛使用的哈希算法,它将任意长度的数据转化为固定长度的哈希值。如果文件传输完成后,通过计算源文件和目标文件...
十一、从头到尾彻底解析Hash表算法 十一(续)、倒排索引关键词Hash不重复编码实践 十二、快速排序算法 (快速排序算法3篇文章) 十二(续)、快速排序算法的深入分析 十二(再续):快速排序算法之所有版本的c/c++...
《程序员编程艺术第一~二十七章集锦与总结》是由July编写的,旨在教导读者如何进行有效的编程。这本书涵盖了从编程基础到高级技术的广泛内容,对于任何希望提升编程技能的人来说,都是一份宝贵的资源。...
这一过程涉及到HTTP的GET请求和响应,以及可能的断点续传技术,以支持大文件的分段下载。 接着,我们讨论hash算法。Hash(哈希)算法是一种将任意长度的数据映射为固定长度的输出,通常称为哈希值。它具有快速计算...
Hash Join方法 首先对一个较小的表(称为“build”表)构建哈希表,然后遍历另一个较大的表(称为“probe”表),对每个元组使用连接字段计算哈希值,并在哈希表中查找匹配项。如果找到匹配,就将结果输出。这种...
"数据库原理及应用:第五章数据库设计(续3)" 数据库原理及应用是数据库领域中比较重要的知识点,本章节主要讲述数据库设计的第五章,包括数据库设计概述、需求分析、概念结构设计、逻辑结构设计、数据物理设计、...
.NET框架为开发者提供了丰富的加密算法,包括对称加密、非对称加密以及哈希(Hash)算法。在本文中,我们将深入探讨这些加密算法,并基于.NET的类库创建一个可扩展且易于维护的加密助手类。 首先,让我们关注哈希...
- `upload_hash`:根据文件内容生成哈希值,用于防止重复上传。 3. **工作流程** - 客户端发起 HTTP 请求,包含待上传的文件。 - Nginx 接收到请求,Upload Module 开始处理文件上传。 - 文件被分割成小块...
web-uploaderjs (html5 + html4) 文件上传管理器,支持上传进度显示,支持秒传+分片上传+断点续传...也可自行实现hash计算)上传文件的同时可以指定上传参数,支持上传类型过滤完善的事件回调,可针对上传的每个过程进
web-uploader js (html5 + html4) 文件上传管理器,支持上传进度显示,支持秒传+分片上传+断点续...也可自行实现hash计算) 上传文件的同时可以指定上传参数,支持上传类型过滤 完善的事件回调,可针对上传的每个过程进
也可自行实现hash计算) 5.上传文件的同时可以指定上传参数,支持上传类型过滤 6.完善的事件回调,可针对上传的每个过程进行单独处理 7.上传核心与UI界面分离,方便的UI接口,可以很方便的定制上传界面包括上传按钮