4.2 哈希表

  在实际项目的开发,经常遇到字典的需求。所谓字典,就是把一堆key-value的键值对存入容器中,用key作为条件能快速查出value。几乎每一种编程语言都实现了字典,但是C语言标准库没有字典的实现。按照上述我对字典的定义,任何数据结构都可以实现字典。就拿数组来说,数组每个位置放上键值对,那也是个字典啊,只是性能会很差。字典的高性能实现有多种方案,常见的方案有哈希表、跳表和红黑树实现。
  哈希表的常见的实现方案是这样的:
  底层是一个数组,数组的每一项都是一个链表,哈希值相同的在一个链表上。在扩容时,需要把底层数组扩大,然后每一个元素计算在新数组的的哈希值,然后再拷贝过去。如图所示:
在这里插入图片描述
  扩容后就是这样的:
在这里插入图片描述

  哈希表并不复杂,但是一定要动手练一练,亲自写哈希表的实现。以下是我自己的实现代码:

package com.yongthing.map.hash;

import java.util.LinkedList;
import java.util.ListIterator;

/**
 * 哈希表
 * created at 22/01/2022
 *
 * @author 花书粉丝
 * <a href="mailto://yujianbo@chtwm.com">yujianbo@chtwm.com</a>
 * @since 1.0.0
 */
public class HashMap<K, V> {
    static class Entry<K, V> {
        K key;
        V value;
    }

    private LinkedList[] elements;
    private int size;

    public HashMap() {
        elements = new LinkedList[8];
    }

    /**
     * 添加元素方法
     *
     * @param key
     * @param value
     */
    public void put(K key, V value) {
        if (key == null) {
            throw new IllegalArgumentException("key不能为空");
        }
        final Entry<K, V> entry = new Entry<>();
        entry.key = key;
        entry.value = value;
        // 扩容
        if (size > elements.length * 0.75) {
            //elements = makeCapacity(elements);
        }
        add(entry, elements);
        size++;
    }


    public V get(K key) {
        if (key == null) {
            return null;
        }
        final int index = hash(key, this.elements);
        final LinkedList element = elements[index];
        if (element == null) {
            return null;
        }
        for (Object e : element) {
            final Entry<K, V> entry = (Entry<K, V>) e;
            if (entry.key.equals(key)) {
                return entry.value;
            }
        }
        return null;
    }

    private static <K, V> LinkedList[] makeCapacity(LinkedList[] elements) {
        final LinkedList[] newElements = new LinkedList[(elements.length + 1) << 1];
        // 将数据拷贝过来
        for (LinkedList linkedList : elements) {
            if (linkedList != null && !linkedList.isEmpty()) {
                for (Object e : linkedList) {
                    add((Entry<K, V>) e, newElements);
                }
            }
        }
        return newElements;
    }

    private static <K, V> void add(Entry<K, V> entry, LinkedList[] elements) {
        K key = entry.key;
        final int index = hash(key, elements);
        LinkedList list = elements[index];
        if (list == null) {
            list = new LinkedList<Entry<K, V>>();
            list.add(entry);
            elements[index] = list;
        } else {
            final ListIterator<Entry<K, V>> iterator = list.listIterator();
            while (iterator.hasNext()) {
                final Entry<K, V> next = iterator.next();
                if (next.key.equals(key)) {
                    // 重复时替换
                    iterator.set(entry);
                    return;
                }
            }
            // 不存在重复则添加
            list.add(entry);
        }

    }

    private static <K> int hash(K key, LinkedList[] elements) {
        return key.hashCode() % elements.length;
    }

}

  java.util包下自带了一个HashMap,是面试的热门,有时候会问十几个关于HashMap的细节问题。基本原理肯定是不会问那么多,其实问题最多的就是两个问题:扩容与并发。

哈希表扩容

  首先有个扩容因子0.75,这个扩容因子的意思是如果键值对数量已经占据哈希表大小的0.75,再加元素就会对哈希表进行扩容了。哈希表的扩容是按2的指数扩容的,根据哈希算法,扩容后的元素要么在原索引,要么再移动2的指数。
  举个例子,哈希值为9,扩容前哈希表大小为8,那么索引为 9 & ( 8 − 1 ) = 1 9 \& (8-1)=1 9&(81)=1,扩容后就是 9 & ( 16 − 1 ) = 9 9\& (16-1)=9 9&(161)=9.而对于哈希值为17的,扩容前是 17 & ( 8 − 1 ) = 1 17 \& (8-1)=1 17&(81)=1,扩容后是 17 & ( 16 − 1 ) = 1 17 \& (16-1)=1 17&(161)=1,扩容前后在哈希表中的索引是不变的。
  HashMap是放完元素后再进行扩容的。扩容机制比较复杂,我就不展开说了。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒过来摸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值