Java实现Hash表(已实现迭代器,支持foreach语法)

Java实现Hash表


前言

数组加链表的形式实现的Hash表

一、Hash表是什么?

散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

二、代码

package com.cxf.study.hash;

import java.util.Iterator;
/**
 * @Description: java实现hash表
 * @Author: xiaowang
 * */
public class MyHashMap<K, V> implements Iterable<MyHashMap.Node<K, V>> {
    // 存数据的数组
    private Node<K, V>[] table;
    //键值对个数
    private int size = 0;
    // 数组默认长度
    private static final int DEFAULT_LENGTH = 16;
    // 默认负载因子大小
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 负载因子(负载因子*当前数组长度=当前数组存储数据的阈值)
    private final float LOAD_FACTOR;
    // hash表数组最大长度
    private static final int MAXIMUM_LENGTH = 1 << 30;
    // 当前数组存储数据的阈值,一旦size大于这个值就要进行resize()
    private int threshold;

    /**
     * 构造器
     */
    public MyHashMap() {
        LOAD_FACTOR = DEFAULT_LOAD_FACTOR;
    }

    public MyHashMap(float load) {
        if (load <= 0) {
            throw new RuntimeException("负载因子大小不能为负且不能为0");
        }
        LOAD_FACTOR = load;
    }

    public MyHashMap(int initSize) {
        if (initSize <= 0) {
            throw new RuntimeException("初始化大小不能为负且不能为0");
        }
        if (initSize > MAXIMUM_LENGTH) {
            initSize = MAXIMUM_LENGTH;
        }
        LOAD_FACTOR = DEFAULT_LOAD_FACTOR;
        threshold = tableSizeFor(initSize);
    }

    public MyHashMap(int initSize, float load) {
        if (initSize <= 0) {
            throw new RuntimeException("初始化大小不能为负且不能为0");
        }
        if (load <= 0) {
            throw new RuntimeException("负载因子大小不能为负且不能为0");
        }
        if (initSize > MAXIMUM_LENGTH) {
            initSize = MAXIMUM_LENGTH;
        }
        LOAD_FACTOR = load;
        threshold = tableSizeFor(initSize);
    }

    /**
     * 添加元素,key不允许为null
     */
    public void put(K key, V value) {
        // 判空
        if (key == null) {
            throw new NullPointerException("key must not be null!");
        }
        // 首次添加,需要去初始化散列表
        if (table == null || table.length == 0) {
            initTable();
        }
        // 新节点
        Node<K, V> newNode = new Node<>(key.hashCode(), key, value, null);
        // 获取下标
        int i = indexByHash(key,table);
        // 该下标已经存在的节点
        Node<K, V> existedNode = table[i];
        if (existedNode == null) {
            table[i] = newNode;
            // 计数器
            size++;
        } else {
            // 寻找key相同的节点
            if (isSame(key, existedNode)) {
                existedNode.value = value;
            } else {
                Node<K, V> temp = existedNode.next;
                while (temp != null && !isSame(key, temp)) {
                    existedNode = existedNode.next;
                    temp = existedNode.next;
                }
                if (temp == null) {
                    existedNode.next = newNode;
                    // 计数器
                    size++;
                } else {
                    temp.value = value;
                }
            }
        }
        // 判断size是否大于阈值
        if (size > threshold) {
            resize();
        }
    }
    /**
     * 根据key获取值
     */
    public V get(K key) {
        return getNodeByKey(key) == null ? null : getNodeByKey(key).value;
    }

    /**
     * 移除元素
     */
    public V remove(K key) {
        if (table == null || table.length == 0 || size == 0) {
            return null;
        }
        if (key == null) {
            return null;
        }
        // 获取下标
        int i = indexByHash(key,table);
        // 该下标已经存在的节点
        Node<K, V> existedNode = table[i];
        if (existedNode != null) {
            if (isSame(key, existedNode)) {
                if (existedNode.next == null) {
                    table[i] = null;
                } else {
                    table[i] = existedNode.next;
                }
                size--;
                return existedNode.value;
            } else {
                Node<K, V> temp = existedNode.next;
                while (temp != null && !isSame(key, temp)) {
                    existedNode = existedNode.next;
                    temp = existedNode.next;
                }
                if (temp != null) {
                    // 重新按链
                    existedNode.next = temp.next;
                    size--;
                    return temp.value;
                } else {
                    return null;
                }
            }
        } else {
            return null;
        }

    }
    /**
     * size
     * */
    public int size(){
        return size;
    }
    /**
     * 清空hash表
     * */
    public void clear(){
        Node<K,V>[] tab = table;
        if (tab != null && size > 0){
            for (int i = 0; i < tab.length; i++) {
                tab[i] = null;
            }
            size = 0;
        }
    }
    /**
     * toString
     * */
    public String toString() {
        if (table == null || table.length == 0 || size == 0) {
            return "{}";
        }
        StringBuilder str = new StringBuilder();
        str.append("{");
        Node<K, V> node = getFirstNode();
        for (int i = 0; i < size; ) {
            if (i == size - 1) {
                str.append(node).append("}");
            } else {
                str.append(node).append(",");
            }
            node = getAfterNode(node);
            i++;
        }
        return str.toString();
    }

    /**
     * 获取数组的首个节点
     **/
    private Node<K, V> getFirstNode() {
        if (table != null) {
            Node<K, V> t;
            for (int i = 0; i < table.length; i++) {
                if (table[i] != null) {
                    t = table[i];
                    return t;
                }
            }
        }
        return null;
    }

    /**
     * 获取特殊意义上的下一个节点(如果该节点在链表上有next节点,
     * 则下一个节点为next节点,如果没有,则根据下标遍历数组)
     */
    private Node<K, V> getAfterNode(Node<K, V> node) {
        Node<K, V> nextNode = null;
        // 如果节点有next
        if (node.next != null) {
            nextNode = node.next;
        } else {
            for (int i = indexByHash(node.key,table) + 1; i < table.length; i++) {
                if (table[i] != null) {
                    nextNode = table[i];
                    break;
                }
            }
        }
        return nextNode;
    }
    /**
     * 初始化数组
     */
    private void initTable() {
        if (threshold > 0) {
            table = new Node[threshold];
        } else {
            table = new Node[DEFAULT_LENGTH];
            threshold = (int) (DEFAULT_LENGTH * LOAD_FACTOR);
        }
    }

    /**
     * 一个key对应相同的节点
     */
    private boolean isSame(K key, Node<K, V> node) {
        return node.key == key || (key.hashCode() == node.key.hashCode() && key.equals(node.key));
    }

    /**
     * 根据key获取节点
     */
    private Node<K, V> getNodeByKey(K key) {
        if (table == null || table.length == 0 || size == 0) {
            return null;
        }
        if (key == null) {
            return null;
        }
        // 下标
        int index = indexByHash(key,table);
        Node<K, V> temp = table[index];
        while (temp != null && !isSame(key, temp)) {
            temp = temp.next;
        }
        return temp;
    }

    /**
     * 简易哈希算法(用hashCode值作为hash值),
     * 通过key的hashCode值对数组长度取余算出下标(效率底下,
     * 建议采用HashMap的高16位算法)
     */
    private int indexByHash(K key,Node<K,V>[] tab) {
        return key.hashCode() % tab.length;
    }

    /**
     * 当散列表的元素个数大于阈值的时候,进行重新编排
     * (也就是把当前数组的元素一个一个拿出来,重新算出在新数组
     * 的下标,然后放进新数组里)
     * */
    private void resize() {
        // 老数组
        Node<K, V>[] oldTable = table;
        // 老数组长度
        int oldLength = oldTable.length;
        // 新数组长度
        int newLength = oldLength << 1;
        // 新数组
        Node<K, V>[] newTable = (Node<K, V>[])new Node[newLength];
        // 新阈值
        int newThreshold = (int) (newLength * LOAD_FACTOR);

        for (int i = 0; i < oldLength; i++) {
            Node<K, V> oldNode = oldTable[i];
            if (oldNode != null) {
                // 声明一个中间变量
                Node<K, V> tempNode;
                do {
                    tempNode = oldNode.next;
                    // 重新编排的下标
                    int index = indexByHash(oldNode.key,newTable);
                    // 新数组的位置的元素
                    Node<K, V> existed = newTable[index];
                    // 新数组下标指向老节点
                    newTable[index] = oldNode;
                    // 头插法插入
                    oldNode.next = existed;
                    // 向下走一格
                    oldNode = tempNode;
                } while (oldNode != null);
            }
        }
        // 新阈值赋值
        threshold = newThreshold;
        // table指向新数组
        table = newTable;
    }
    /**
     * 获取刚好大于或等于a的2的n次方,也
     * 可以简写为  a |= a >>> 1
     *           a |= a >>> 2
     *           ...
     * */
    private int tableSizeFor(int a) {
        a = a - 1;
        a = a | a >>> 1;
        a = a | a >>> 2;
        a = a | a >>> 4;
        a = a | a >>> 8;
        a = a | a >>> 16;
        return a < 0 ? 1 : a > MAXIMUM_LENGTH ? MAXIMUM_LENGTH : a + 1;
    }
    /**
     * 匿名内部类实现迭代器
     * */
    @Override
    public Iterator<Node<K, V>> iterator() {
        Iterator iterator = new Iterator<Node<K,V>>() {
            // 当前编号 初始化为0
            private int index = 0;
            // 上一个节点
            private Node<K, V> lastNode;
            // 当前节点 初始化为首个节点
            private Node<K, V> theNode = getFirstNode();

            @Override
            public boolean hasNext() {
                return index < size;
            }

            @Override
            public Node<K, V> next() {
                if (!hasNext()) {
                    throw new RuntimeException("NoSuchElementException");

                }
                lastNode = theNode;
                theNode = getAfterNode(lastNode);
                index++;
                return lastNode;
            }

        };
        return iterator;
    }
    /**
     * 内部类:节点
     * */
    public static class Node<K, V> {
        final int hash;
        final K key;
        V value;
        Node<K, V> next;

        Node(int hash, K key, V value, Node<K, V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        @Override
        public String toString() {
            return key + "=" + value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }


}



三、测试代码

package com.cxf.study.hash;



import java.util.Scanner;

public class TestHash {
    public static void main(String[] args) {
        MyHashMap<Integer,String> map = new MyHashMap<>(2,2.0f);
        Scanner scanner = new Scanner(System.in);
        Scanner scanner2 = new Scanner(System.in);
        boolean isCan = true;
        while (isCan){
            showWord();
            int flag = scanner.nextInt();
            switch (flag){
                case 1 :
                    System.out.println("请输入要添加的key:");
                    Integer key1 = scanner.nextInt();
                    System.out.println("请输入要添加的value:");
                    String value1 = scanner2.nextLine();
                    map.put(key1,value1);
                    System.out.println("添加成功~~~");
                    break;
                case 2 :
                    System.out.println("请输入key:");
                    Integer key2 = scanner.nextInt();
                    String value = map.get(key2);
                    if (value == null){
                        System.out.println("无数据~~~");
                    }else{
                        System.out.println("数据:"+value);
                    }
                    break;
                case 3 :
                    System.out.println("哈希表数据:\n"+map.toString());
                    break;
                case 4 :
                    System.out.println("遍历hash表开始");
                    for (MyHashMap.Node<Integer,String> node:map){
                        System.out.println(node.getKey()+"->"+node.getValue());
                    }
                    System.out.println("遍历结束");
                    break;
                case 5 :
                    System.out.println("哈希表键值对的个数为:"+map.size());
                    break;
                case 6 :
                    map.clear();
                    System.out.println("链表已经清空...");
                    break;
                case 7 :
                    System.out.println("请输入要删除的key:");
                    int key7 = scanner.nextInt();
                    String oldData = map.remove(key7);
                    System.out.println("数据:"+oldData+" 已删除...");
                    break;
                default:
                    isCan = false;
                    System.out.println("已退出...");
                    break;
            }
        }
    }
    private static void showWord(){
        System.out.println("欢迎来到键值记录系统,"+
                "退出:0;"+
                "添加键值对:1;"+
                "根据key获取:2;"+
                "展示hash表:3;"+
                "foreach遍历:4;"+
                "查看键值对个数:5;"+
                "清空hash表:6;"+
                "删除键值对:7;");
        System.out.println("请选择功能:");
    }
}



总结

以上就是今天要讲的内容,本文只是简单介绍了散列表的实现以及测试。

笔者在这里记录几个问题:
1.当数组水平扩容时没有加限制,达到一定数据量(2的30次方*0.75)会报错
2.数组加链表的形式固然能满足解决hash冲突的问题,但链表也有可能太长,会使查询效率大打折扣,jdk1.8采用红黑树进行优化
3.为了方便测试,笔者采用两个参数的有参构造,测试强度不够
4.在对节点分配下标采用的是hashcode%table.length,建议大家参考JDK1.8的HashMap的hash方法

其他同学肯定有更好的实现,有写的不妥的地方还请指出。上面代码没有进行特别详细的测试,如果有发现bug的童鞋希望能及时提出来,共同学习,一起进步。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值