使用散列实现映射表----HashMap的简单实现

先大致说下什么是散列,散列使用一个散列函数,将一个键映射到一个索引上。散列是一种无须执行搜索,即可通过键得到的索引来获取值的技术。
使用散列实现映射表还需考虑一个比较重要的问题:当两个键映射到散列表中的同一个索引上,冲突发生。通常,有两种方法处理冲突:开放地址法(线性探测、二次探测、在哈希法)和链地址法。

下面就是用链地址法处理冲突实现的映射表。

接口MyMap,类Entry、MyHashMap
它们之间关系如下UML图
这里写图片描述
下面是具体实现代码

package com.aekc.algorithm.hashing;

import java.util.Set;

public interface MyMap<K, V> {

    /**
     * 从该映射表中删除所有条目
     */
    void clear();

    /**
     * 如果该映射表包含指定键的条目,则返回true
     * @param key
     * @return
     */
    boolean containsKey(K key);

    /**
     * 如果该映射表将一个或者多个键映射到指定的值,则返回true
     * @param value
     * @return
     */
    boolean containsValue(V value);

    /**
     * 返回一个包含该映射表中所有条目的集合
     * @return
     */
    Set<Entry<K, V>> entrySet();

    /**
     * 返回映射表中指定键的对应值
     * @param key
     * @return
     */
    V get(K key);

    /**
     * 如果该映射表不包含任何映射,则返回true
     * @return
     */
    boolean isEmpty();

    /**
     * 返回该映射表中所有键的集合
     * @return
     */
    Set<K> keySet();

    /**
     * 将一个映射置于该映射表中
     * @param key
     * @param value
     * @return
     */
    V put(K key, V value);

    /**
     * 删除指定键的条目
     * @param key
     */
    void remove(K key);

    /**
     * 返回该映射表中的映射数目
     * @return
     */
    int size();

    /**
     * 返回一个包含该映射表中的集合
     * @return
     */
    Set<V> values();

    /** 该类会自动标识为静态类 */
    class Entry<K, V> {
        K key;
        V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        @Override
        public String toString() {
            return "[" + key + ", " + value + "]";
        }
    }
}

在罗列MyHashMap代码前,先说说什么是负载因子和再散列
负载因子也叫装填因子,它是衡量一个散列表有多满。如果装填因子溢出,则增加散列表的大小,并重新装载条目到一个新的更大的散列表中。这称为再散列。
装载因子a = n / N
这里的n表示元素的数目,N表示散列表中位置的数目。
如果散列表为空则a为0,对于开发地址法,a介于0到1之间。对于链地址法,a可能为任意值。
将装填因子a保持在一定阈值下对于散列的性能是非常重要的。一旦因子超过阈值,则需要增加散列表的大小,并将映射表中所有条目再散列到一个更大的散列表中。由于再散列代价比较大,为了减少出现再散列的可能性,应该至少将散列表的大小翻倍。

package com.aekc.algorithm.hashing;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

public class MyHashMap<K, V> implements MyMap<K, V> {

    /** 默认哈希表大小,必须是2的幂 */
    private static int DEFAULT_INITIAL_CAPACITY = 4;

    /** 最大哈希表大小 1<<30 ~ 2^30 */
    private static int MAXIMUM_CAPACITY = 1 << 30;

    /** 当前哈希表的容量,大小为2的倍数 */
    private int capacity;

    /** 默认负载因子 */
    private static float DEFAULT_MAX_LOAD_FACTOR = 0.75f;

    /** 指定哈希表中使用的负载因子 */
    private float loadFactorThreshold;

    /** 表中的条目数 */
    private int size = 0;

    /** 具有默认容量和负载因子的链表 */
    private LinkedList<MyMap.Entry<K, V>>[] table;

    public MyHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_MAX_LOAD_FACTOR);
    }

    public MyHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_MAX_LOAD_FACTOR);
    }

    public MyHashMap(int initialCapacity, float loadFactorThreshold) {
        if(initialCapacity > MAXIMUM_CAPACITY) {
            this.capacity = MAXIMUM_CAPACITY;
        } else {
            this.capacity = trimToPowerOf2(initialCapacity);
        }
        this.loadFactorThreshold = loadFactorThreshold;
        table = new LinkedList[capacity];
    }

    @Override
    public void clear() {
        size = 0;
        removeEntries();
    }

    @Override
    public boolean containsKey(K key) {
        return get(key) != null;
    }

    @Override
    public boolean containsValue(V value) {
        for(int i = 0; i < capacity; i++) {
            if(table[i] != null) {
                LinkedList<Entry<K, V>> bucket = table[i];
                for(Entry<K, V> entry : bucket) {
                    if(entry.getValue().equals(value)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<MyMap.Entry<K, V>> set = new HashSet<>();
        for(int i = 0; i < capacity; i++) {
            if(table[i] != null) {
                LinkedList<Entry<K, V>> bucket = table[i];
                set.addAll(bucket);
            }
        }
        return set;
    }

    @Override
    public V get(K key) {
        int bucketIndex = hash(key.hashCode());
        if(table[bucketIndex] != null) {
            LinkedList<Entry<K, V>> bucket = table[bucketIndex];
            for(Entry<K, V> entry : bucket) {
                if(entry.getKey().equals(key)) {
                    return entry.getValue();
                }
            }
        }
        return null;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public Set<K> keySet() {
        Set<K> set = new HashSet<>();
        for(int i = 0; i < capacity; i++) {
            if(table[i] != null) {
                LinkedList<Entry<K, V>> bucket = table[i];
                for(Entry<K, V> entry : bucket) {
                    set.add(entry.getKey());
                }
            }
        }
        return set;
    }

    @Override
    public V put(K key, V value) {
        if(get(key) != null) {
            int bucketIndex = hash(key.hashCode());
            LinkedList<Entry<K, V>> bucket = table[bucketIndex];
            for(Entry<K, V> entry : bucket) {
                if (entry.getKey().equals(key)) {
                    V oldValue = entry.getValue();
                    entry.value = value;
                    return oldValue;
                }
            }
        }
        if(size >= capacity * loadFactorThreshold) {
            if(capacity == MAXIMUM_CAPACITY) {
                throw new RuntimeException("Exceeding maximum capacity");
            }
            rehash();
        }
        int bucketIndex = hash(key.hashCode());
        if(table[bucketIndex] == null) {
            table[bucketIndex] = new LinkedList<>();
        }
        table[bucketIndex].add(new MyMap.Entry<>(key, value));
        size++;
        return value;
    }

    @Override
    public void remove(K key) {
        int bucketIndex = hash(key.hashCode());
        if(table[bucketIndex] != null) {
            LinkedList<Entry<K, V>> bucket = table[bucketIndex];
            for(Entry<K, V> entry : bucket) {
                if(entry.getKey().equals(key)) {
                    bucket.remove(entry);
                    size--;
                    break;
                }
            }
        }
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Set<V> values() {
        Set<V> set = new HashSet<>();
        for(int i = 0; i < capacity; i++) {
            if(table[i] != null) {
                LinkedList<Entry<K, V>> bucket = table[i];
                for(Entry<K, V> entry : bucket) {
                    set.add(entry.getValue());
                }
            }
        }
        return set;
    }

    //------------Hash function------------

    private int hash(int hashCode) {
        //supplementalHash(hashCode) % capacity
        return supplementalHash(hashCode) & (capacity - 1);
    }

    /** 确保散列分布均匀 */
    private static int supplementalHash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /** 为初始容量返回2的倍数 */
    private int trimToPowerOf2(int initialCapacity) {
        int capacity = 1;
        while(capacity < initialCapacity) {
            capacity <<= 1;
        }
        return capacity;
    }

    /** 删除桶中所有条目 */
    private void removeEntries() {
        for(int i = 0; i < capacity; i++) {
            if(table[i] != null) {
                table[i].clear();
            }
        }
    }

    /** 对map进行在哈希 */
    private void rehash() {
        Set<Entry<K, V>> set = entrySet();
        capacity <<= 1;
        table = new LinkedList[capacity];
        size = 0;
        for(Entry<K, V> entry : set) {
            put(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("[");
        for(int i = 0; i < capacity; i++) {
            if(table[i] != null && table[i].size() > 0) {
                for(Entry<K, V> entry : table[i]) {
                    builder.append(entry);
                }
            }
        }
        builder.append("]");
        return builder.toString();
    }
}

最后来个测试类

package com.aekc.algorithm.hashing;

public class TestMyHashMap {

    public static void main(String[] args) {
        MyMap<String, Integer> map = new MyHashMap<>();
        map.put("张山", 33);
        map.put("李四", 22);
        map.put("王五", 23);
        map.put("赵六", 45);

        System.out.println(map);
        System.out.println(map.get("李四"));
        map.remove("王五");
        System.out.println(map);
        map.clear();
        System.out.println(map);
    }
}

输出

[[赵六, 45][张山, 33][李四, 22][王五, 23]]
22
[[赵六, 45][张山, 33][李四, 22]]
[]

猜你喜欢

转载自blog.csdn.net/XlxfyzsFdblj/article/details/81205071
今日推荐