`
humn_chou
  • 浏览: 23432 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

HashMap实现原理

 
阅读更多

前言

HashMap是Java中常用的集合,而且HashMap的一些思想,对于我们平时解决业务上的一些问题,在思路上有帮助,基于此,本篇博客将分析HashMap底层设计思想,并手写一个迷你版的HashMap!

 

对HashMap的思考

HashMap底层数据结构

第一,如图所示,HashMap有3个要素:hash函数+数组+单链表

第二,对于hash函数而言,需要考虑些什么?

要快,对于给定的Key,要能够快速计算出在数组中的index。那么什么运算够快呢?显然是位运算!

要均匀分布,要较少碰撞。说白了,我们希望通过hash函数,让数据均匀分布在数组中,不希望大量数据发生碰撞,导致链表过长。那么怎么办到呢?也是利用位运算,通过对数据的二进制的位进行移动,让hash函数得到的数据散列开来,从而减低了碰撞的概率。

如果发生了碰撞怎么办?上面的图其实已经说明了JDK的HashMap是如何处理hash冲突的,就是通过单链表解决的。那么除了这个方法,还有其他思路么?比如说,如果发生冲突,那么记下这个冲突的位置为index,然后在加上固定步长,即index+step,找到这个位置,看一下是否仍然冲突,如果继续冲突,那么按照这个思路,继续加上固定步长。其实这就是所谓的线性探测来解决Hash冲突的方法!

 

通过写一个迷你版的HashMap来深刻理解

定义接口

接口

定义一个接口,对外暴露快速存取的方法。

注意MyMap接口内部定义了一个内部接口Entry。

接口实现

MyHashMap定义

HashMap的要素之一,就是数组,自然在这里,我们要定义数组,数组的初始化大小,还要考虑扩容的阀值。

看MyHashMap的构造

构造方法

构造方法有什么好说的呢?

仔细观察下,你会发现,其实这里使用到了“门面模式”。这里的2个构造方法其实指向的是同一个,但是对外却暴露了2个“门面”!

Entry

Entry

HashMap的要素之一,单链表的体现就在这里!

看put如何实现

put

第一,要考虑是否扩容?

HashMap中的Entry的数量(数组以及单链表中的所有Entry)是否达到阀值?

第二,如果扩容,意味着新生成一个Entry[],不仅如此还得重新散列。

第三,要根据Key计算出在Entry[]中的位置,定位后,如果Entry[]中的元素为null,那么可以放入其中,如果不为空,那么得遍历单链表,要么更新value,要么形成一个新的Entry“挤压”单链表!

hash函数

MyHashMap提供的hash函数

JDK的HashMap提供的hash函数

我这里参考了JDK的HashMap的hash函数的实现,这里也再次说明了:要想散列均匀,就得进行二进制的位运算!

resize和rehash

resize/rehash

这里可以看出,对于HashMap而言,如果频繁进行resize/rehash操作,是会影响性能的。

resize/rehash的过程,就是数组变大,原来数组中的entry元素一个个的put到新数组的过程,需要注意的是一些状态变量的改变。

get实现

get

get很简单,只需要注意在遍历单链表的过程中使用== or equals来判断下即可。

Test测试

利用MyHashMap进行存取

运行结果

result

OK,一个迷你版的HashMap就写好了,你学到了么?

注:关注作者微信公众号,了解更多分布式架构、微服务、nettyMySQLspringJVM、算法、性能优化、等知识点。

 

 

package com.suning.map;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by 17030057 on 2018/8/16.
 */
public class MyHashMap<K,V> implements MyMap<K,V> {

    private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //数组默认长度
private static final int MAXIMUM_CAPACITY = 1 << 30;//数组最大长度
private static final float DEFAULT_LOAD_FACTOR = 0.75f; //数组扩容阀值默认比例
private Entry<K,V>[] table = null; //数组
private int initialCapacity;//数组长度
private float loadFactor;//数组扩容阀值比例
private int entryUseSize; //mapentry数量
public MyHashMap(){
        this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);
}

    public MyHashMap(int initialCapacity,float loadFactor){
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);
this.initialCapacity = initialCapacity;
this.loadFactor = loadFactor;
table = new Entry[this.initialCapacity];
}

    //单链表(可以转换成list)
class Entry<K,V> implements MyMap.Entry<K,V> {

        private K key;
private V value;
private Entry<K,V> next;
public Entry() {
        }

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

        @Override
public K getKey() {
            return key;
}

        @Override
public V getValue() {
            return value;
}
    }

    //hash函数 让key均匀分布
private int hash(K k) {
        int hashCode = k.hashCode();
hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12);
return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4);
}

    //扩容
private void resize(int newCapacity) {
        Entry[] newTable = new Entry[newCapacity];
//修改数组大小
initialCapacity = newCapacity;
entryUseSize = 0;
//重新hash
List<Entry<K,V>> entryList = new ArrayList<>();//得到老entry链表
for (Entry<K,V> entry : table) {
            if(null != entry) {
                do {
                    entryList.add(entry);
entry = entry.next;
} while (entry != null);
}
        }
        //覆盖就数组
if(newTable.length > 0) {
            table = newTable;
}
        //重新组建hashMap
for (Entry<K,V> entry : entryList) {
            put(entry.getKey(),entry.getValue());
}
    }

    @Override
public V put(K k, V v) {
        V oldValue = null;
//是否需要扩容
if(entryUseSize >= initialCapacity*loadFactor) {
            resize(2 * initialCapacity);
}
        //得出hash值,计算位置
int index = hash(k) & (initialCapacity - 1);
if (table[index] == null) {
            table[index] = new Entry<K,V>(k,v,null);
++entryUseSize;
} else {
            Entry<K,V> entry = table[index];
Entry<K,V> e = entry;
while (e == entry) {
                if(k == e.getKey() || k.equals(e.getKey())) {
                    oldValue = e.value;
e.value = v;
return oldValue;
}
                e = e.next;
}
            table[index] = new Entry<K,V>(k,v,entry);
++entryUseSize;
}
        return oldValue;
}

    @Override
public V get(K k) {
        int index = hash(k) & (initialCapacity - 1);
if(table[index] == null) {
            return null;
} else {
            Entry<K,V> entry = table[index];
do {
                if(k == entry.getKey() || k.equals(entry.getKey())) {
                    return entry.value;
}
                entry = entry.next;
} while (entry != null);
}
        return null;
}

    public static void main(String[] args) {

        System.out.println(1 << 5);
System.out.println(1 << 30);
System.out.println(16 >>> 2);
//        MyHashMap<String,String> map = new MyHashMap<>();
//        for (int i =0;i<1000;i++) {
//            map.put("key"+i , "value"+i);
//        }
//        for (int i =0;i<1000;i++) {
//            System.out.println(map.get("key"+i));
//        }
}
}
分享到:
评论

相关推荐

    hashmap实现原理

    在深入探讨HashMap的实现原理之前,我们需要了解两个关键的接口方法:`hashCode()`和`equals()`。 根据《Effective JAVA》的建议,当重写`equals()`方法时,也应重写`hashCode()`方法。这是因为在HashMap中,`...

    基于HashMap的用户标签处理兼Java中HashMap实现原理研究.pdf

    "基于HashMap的用户标签处理兼Java中HashMap实现原理研究" 本文研究了基于HashMap的用户标签处理方法,并对Java中HashMap的实现原理进行了深入研究。HashMap是一种高效的数据结构,可以快速地存储和检索数据。本文...

    hashmap实现原理.pdf

    hashmap实现原理.pdf

    Jdk1.8中的HashMap实现原理.docx

    《Jdk1.8中的HashMap实现原理》 HashMap作为Java编程语言中常用的数据结构,它在Jdk1.8中的实现结合了哈希表、链表以及红黑树的特性,提供高效且灵活的键值对存储功能。本文将深入探讨HashMap的内部结构、工作原理...

    Jdk1.8中的HashMap实现原理.pdf

    总结起来,JDK1.8中的HashMap实现原理主要包括以下几个要点: 1. 链表散列结构:数组+链表,链表用于处理哈希碰撞。 2. 红黑树优化:当链表长度超过8时,转换为红黑树,减少查找、插入和删除的时间复杂度。 3. 内部...

    Jdk1.8 HashMap实现原理详细介绍

    以下是对JDK 1.8 HashMap实现原理的详细解释。 首先,HashMap是一个基于哈希表的数据结构,它实现了Map接口。它不保证映射的顺序,并且是非同步的。这意味着在多线程环境中,如果不进行同步控制,可能会出现并发...

    详解Java HashMap实现原理

    以下是对HashMap实现原理的详细解析。 首先,HashMap内部使用了一个瞬时变量数组`table`(也称为“桶”)来存储键值对。桶是一个Entry对象数组,它的大小可以动态调整,并且长度必须是2的幂。初始时,`table`是一个...

    Java8 HashMap的实现原理分析

    Java8之后新增挺多新东西,接下来通过本文给大家介绍Java8 HashMap的实现原理分析,对java8 hashmap实现原理相关知识感兴趣的朋友一起学习吧

    深入解析java HashMap实现原理

    综上所述,理解HashMap的实现原理对于优化Java程序性能至关重要,尤其是在处理大量数据或并发场景时。在实际应用中,应根据具体需求选择合适的数据结构,例如,如果需要线程安全,可以选择ConcurrentHashMap;如果对...

    HashMap底层原理.md

    HashMap底层原理.md

    源码解析jdk8.0集合:HashMap的底层实现原理.pdf

    根据提供的文件信息,以下是对JDK 8.0中HashMap实现原理的详细知识点介绍: 1. HashMap概述 HashMap是Java集合框架的一部分,它实现了Map接口,用于存储键值对。其核心特点在于基于哈希表的映射机制,能够通过键...

    源码解析jdk7.0集合(3):HashMap的底层实现原理.pdf

    ### JDK 7.0集合系列解析之HashMap实现原理 #### 一、HashMap概述 HashMap是Java集合框架中Map接口的典型实现之一,主要用于存储键值对(Key-Value)数据。其特性如下: - 线程不安全,方法为非同步方法,因此多...

    深入Java集合学习系列:HashMap的实现原理

    在Java编程语言中,集合框架是开发者日常工作中不可或缺的一部分,HashMap作为其中的重要成员,它的实现原理对于理解Java性能优化和数据结构有深远的意义。HashMap是一个基于哈希表的数据结构,它实现了Map接口,...

    HashMap原理的深入理解

    HashMap原理的深入理解 HashMap是基于哈希表的Map接口的非同步实现,提供了所有可选的映射操作,并允许使用null值和null键。HashMap储存的是键值对,HashMap很快。此类不保证映射的顺序,特别是它不保证该顺序恒久...

    java HashMap原理分析

    Java HashMap原理分析 Java HashMap是一种基于哈希表的数据结构,它的存储原理是通过将Key-Value对存储在一个数组中,每个数组元素是一个链表,链表中的每个元素是一个Entry对象,Entry对象包含了Key、Value和指向...

    HashMap底层原理

    HashMap的底层原理主要依赖于哈希表,这是一种数据结构,它通过计算键的哈希码来实现高效的查找操作。 在HashMap中,每个元素都是一个键值对,存储在一个Entry对象中。当向HashMap添加键值对时,首先会计算键的哈希...

    Java HashMap实现原理分析(一)

    HashMap的实现原理基于哈希表,它的核心是通过哈希函数将键(key)映射到一个数组的特定位置,从而实现快速访问。本文将深入解析HashMap的put和get操作的内部机制。 首先,HashMap的底层数据结构是一个动态扩容的...

    HashMap的实现原理

    ### HashMap的实现原理 #### 1. HashMap概述 HashMap 是 Java 集合框架中一个非常重要的类,它实现了 Map 接口,并提供了基于哈希表的存储方式。与其它 Map 实现不同的是,HashMap 允许使用 `null` 键和 `null` 值...

Global site tag (gtag.js) - Google Analytics