赞
踩
http://t.csdnimg.cn/z4XpR 在此篇博客中,介绍了Map集合实现类HashMap和TreeMap的使用,但并没有深究其底层的数据结构,下面介绍的就是HashMap和HashSet底层的数据结构——哈希表
在Java 中,计算哈希值实际上是调用Object类中的hashCode()方法。
常见哈希函数有:
负载因子是一个衡量哈希表使用情况的指标,通常定义为当前哈希表中元素数量与桶的数量的比值。负载因子的调节可以用来控制哈希表的性能。
通常情况下,当负载因子超过某个阈值时(Java中该阈值为0.75),会触发哈希表的扩容操作,以减少哈希冲突的概率,同时会对数组每个元素(桶)进行重新哈希,保持哈希表的高效性能。Java中的HashMap和HashSet等基于哈希表的数据结构都提供了负载因子参数和自动扩容的机制。
在Java中,哈希冲突主要是通过哈希表的两种解决方法来处理的:开放定址法(Open Addressing)和链地址法(Chaining)(重点掌握)。
开放定址法:当发生哈希冲突时,使用一定的规则来查找下一个可用的空槽位。常见的开放定址法包括线性探测(Linear Probing)、二次探测(Quadratic Probing)和双重哈希(Double Hashing)。这些方法都是在哈希表中按照一定的步长进行探测,直到找到一个空槽位为止。
链地址法:首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子 集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
在Java中,常用的哈希表实现类如HashMap、HashSet等都采用了链地址法来解决哈希冲突。当发生哈希冲突时,新的元素会被添加到桶对应的链表中。刚才我们提到了,哈希桶其实可以看作将大集合的搜索问题转化为小集合的搜索问题了,如果冲突严重,就意味着小集合的搜索性能其实也是不佳的,这个时候我们就可以将这个所谓的小集合搜索问题继续进行转化,例如:
即链表过长时(链表长度大于8,哈希表(数组)长度大于等于64),就会将该条链表转化为红黑树来进行存储和查找操作。这种方法可以保证在平均情况下具有较高的查找效率,并且具有较好的扩展性。
在Java中,哈希表的底层是由数组+链表/红黑树组成的,这个数组的每个元素被称为“桶”(bucket),每个桶可以存放一个或多个键值对。
我这里实现的哈希表中的键值对都为int类型,数组每个元素(桶)是一个链表,哈希函数使用较简单的除留余数法,负载因子为0.75
据此可以列出大概结构:
- public class HashBucket {
- private static class Node {
- private int key;
- private int value;
- Node next;
-
- public Node(int key, int value) {
- this.key = key;
- this.value = value;
- }
- }
-
- private Node[] array;
- private int size; // 当前的数据个数
- private static final double LOAD_FACTOR = 0.75;
- private static final int DEFAULT_SIZE = 10;//默认桶的大小
-
- public HashBucket() {
- array = new Node[DEFAULT_SIZE];
- }
-
- //添加键值对
- public int put(int key, int value) {
- }
-
- //重新哈希
- private void resize() {
- }
-
- //判断负载因子是否大于0.75
- private double loadFactor() {
- return size * 1.0 / array.length;
- }
-
- //获取键所对应的值
- public int get(int key) {
- }
- }
完整代码如下:
- //哈希表
- public class HashBucket {
- private static class Node {
- private int key;
- private int value;
- Node next;
-
- public Node(int key, int value) {
- this.key = key;
- this.value = value;
- }
- }
-
- private Node[] array;
- private int size; // 当前的数据个数
- private static final double LOAD_FACTOR = 0.75;
- private static final int DEFAULT_SIZE = 10;//默认桶的大小
-
- public HashBucket() {
- array = new Node[DEFAULT_SIZE];
- }
-
- public int put(int key, int value) {
- //找到插入位置
- int index = key % array.length;
- Node node = new Node(key, value);
- //拿到该位置结点,遍历
- Node cur = array[index];
- //查看是否有相同键的结点
- while (cur != null) {
- if (cur.key == key) {
- cur.value = value;
- return value;
- }
- cur = cur.next;
- }
- cur = array[index];
- //走到最后一个结点,尾插。
- while (cur != null && cur.next != null) {
- cur = cur.next;
- }
- if (cur != null) {
- cur.next = node;
- } else {
- array[index] = node;
- }
- size++;
- //判断负载因子是否超过0.75
- if (loadFactor() >= LOAD_FACTOR) {
- //重新哈希
- resize();
- }
- return -1;
- }
-
- //重新哈希
- private void resize() {
- Node[] newArray = new Node[array.length * 2];
- //遍历数组,对每个结点重新哈希
- for (int i = 0; i < array.length; i++) {
- Node cur = array[i];
- while (cur != null) {
- int index = cur.key % newArray.length;
- //进行尾插
- Node node = newArray[index];
- while (node != null && node.next != null) {
- node = node.next;
- }
- if (node != null) {
- node.next = new Node(cur.key, cur.value);
- } else {
- //如果直接赋cur,则cur的next也会带上
- newArray[index] = new Node(cur.key, cur.value);
- }
- cur = cur.next;
- }
- }
- array = newArray;
- }
-
-
- private double loadFactor() {
- return size * 1.0 / array.length;
- }
-
- public int get(int key) {
- //1.找到位置 2.遍历
- int index = key % array.length;
- Node cur = array[index];
- while (cur != null) {
- if (cur.key == key) {
- return cur.value;
- }
- cur = cur.next;
- }
- return -1;
- }
- }
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。