HashMap与Hashtable散列方式比较

HashMap与Hashtable大同小异,这里主要讲一下他们在散列方式上的差异
Hashtable的散列方式是取模
它的put方法有这样一句代码

int index = (hash & 0x7FFFFFFF) % tab.length;

这里的0x7FFFFFFF是一个运算遮罩,防止hash为负数,然后取模
HashMap的散列方式是位运算
它的hash方法将key的hash值的低16位和高16位做一次异或运算(这里的HashMap是jdk1.8以后的HashMap)

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

put方法中调用了putVal方法
其中有一句p = tab[i = (n - 1) & hash])相当于又做了一次与运算,把hash值的高位全部置0
hash方法的目的在于将32位二进制数都充分利用起来,尽量减少hash冲突,假设当前的size是16,不调用hash方法,直接将key.hashCode()与n - 1做与运算,只有最后四位是有效的,其他位全部没有意义,0x00000000和0xABCDEF00,得到的index是一样的,所以这也是HashMap要两倍扩容的原因
而且HashMap的散列方式使用的是简单的与运算和异或运算,相比Hashtable的取模运算,速度会快很多

———————————————————————————————————————













下面是一个自定义的红黑树,没什么实现,没有红黑树,没有用Entry,没有巧妙的散列方式,以后能不能实现还要看老师会不会接着讲(虽然我觉得面试问红黑树的公司如果不是超大厂就是傻逼,但就是有傻逼面试会问红黑树的)
自定义HashMap

  • 数据结构:这里用的是数组+链表的形式,链表上的节点用自定义的类Node来表示
  • put方法:首先判断当前key是否为null,若key为null,hash为0,否则对hashCode()进行取模运算;其次判断是否已经插入,若已经插入,则更新key对应的value值,若尚未插入,则插入到数组对应index上的链表的最新位置
  • get方法:依旧是先判null,得到index值后找到对应链表,逐一比较key值,一样则返回value,未找到则返回null
  • rehash方法:当存储的元素个数大于阈值且插入出现hash冲突时,调用rehash方法进行扩容,然后对所有节点进行重新插入,这个步骤会比较费时,此处的加载因子取的是0.75,和HashMap是一样的,之所以取0.75是因为这个值可以在空间和时间的消耗上取得一个较好的平衡(或许你想了解泊松分布吗)
package util;

import java.util.ArrayList;
import java.util.HashMap;

public class MyHashMap {
    private Node[] nodes;
    private static final int DEFAULT_MAX_CAPACITY = 1 << 30;
    private static final int DEFAULT_CAPACITY = 16;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    int size = DEFAULT_CAPACITY;
    int threshold = (int) (size * DEFAULT_LOAD_FACTOR);
    int count = 0;

    public MyHashMap() {
        nodes = new Node[size];
    }

    public MyHashMap(int size) {
        if (size > DEFAULT_CAPACITY) {
            this.size = size;
            threshold = (int) (size * DEFAULT_LOAD_FACTOR);
        }
        nodes = new Node[size];
    }

    public void put(Object key, Object value) {
        int hash = key == null? 0: key.hashCode();
        int index = hash % size;
        Node root = nodes[index];
        Node temp = root;
        boolean hasSameKey = false;
        while(temp != null) {
        	if(temp.key == key) {
        		temp.value = value;
        		hasSameKey = true;
        		break;
        	}
        	temp = temp.next;
        }
        if(!hasSameKey) {
        	Node node = new Node(key, value, temp);
        	nodes[index] = node;
        	count++;
        	if (count >= threshold && root != null) {
        		rehash();
        	}
        }
    }

    public Object get(Object key) {
        int hash = key == null? 0: key.hashCode();
        int index = hash % size;
        Node temp = nodes[index];
        while (temp != null) {
            if (temp.key.hashCode() == key.hashCode() && temp.key.equals(key)) {
                return temp.value;
            }
            temp = temp.next;
        }
        return null;
    }

    private void rehash() {
        if (size >= DEFAULT_MAX_CAPACITY) {
            size = DEFAULT_MAX_CAPACITY;
            threshold = DEFAULT_MAX_CAPACITY;
        } else if (size < DEFAULT_CAPACITY) {
            size = DEFAULT_CAPACITY;
            threshold = (int) (size * DEFAULT_LOAD_FACTOR);
        } else {
            size *= 2;
            threshold *= 2;
        }
        Node[] temp = nodes;
        nodes = new Node[size];
        count = 0;
        for (Node node : temp) {
            put(node.key, node.value);
        }
    }
    
    public static void main(String[] args) {
    	MyHashMap myMap=new MyHashMap();
    	HashMap map=new HashMap();
    	long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            myMap.put(i, i + "");
        }
        long end = System.currentTimeMillis();
        float time = (end - start) / 1000f;
        System.out.println("MyHashMap" + time + "s");

        start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            map.put(i, i + "");
        }
        end = System.currentTimeMillis();
        time = (end - start) / 1000f;
        System.out.println("HashMap" + time + "s");
    }

    private class Node {
        Object key;
        Object value;
        Node next;

        public Node(Object key, Object value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }
}

运行结果如下:

  • 插入10000000个节点
    插入10000000个节点
  • 插入1000000个节点
    插入1000000个节点
  • 插入100000个节点
    插入100000个节点
    说一下问题,果然很慢吧,原因在于散列方式用的取模,而且没有使用红黑树,当链表上的节点增多之后如果要查找一个节点就要花费很多时间(时间复杂度O(n)),还有比如构造函数的size传入的是一个大于16的非二的指数次幂数,不会做任何处理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值