HashMap

下面实现一个用于存储键值对的数据格式类,它包含以下属性
  • 用于存放元素的key,和对应的值value的实体 (称为node)
  • 用于存放元素实体的数组 node[] table
  • node元素尽可能散列在table上
  • node为链表格式实体
64a63042-280e-3651-ba05-abeaa96fea07.jpg

以上为该工具类的一个基础架构 (注意该工具线程不安全)

代码实现
  • 我们创建一个接口 MyMap
//简单起见,我们暂时只预留两个方法
package deng.yb.map;

public interface MyMap {
    //根据key获取元素
    Object get(Object key);
    //保存元素
    Object put(Object key, Object value);
}
  • 实现该接口,和添加工具类需要的属性
package deng.yb.map;

public class MyHashMap implements MyMap {
    //用于存放Node元素的数组
    Node[] table;
    //数组默认大小 16
    final static int DEFAULT_INITIAL_CAPACITY= 1   << 4;
    //存放元素的实体,链表结构,通过next指针指向下一个元素
    static class Node {
        //元素key的hash值,用于散列分布在table上 
        final int hash;
        //元素key
        final Object key;
        //元素值
        Object value;
        //具有相同hash值,但key不同元素地址
        Node next;
        //元素构造方法
        Node(int hash, Object key, Object value, Node next){
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

   //简单添加key的hash方法
   int hash(Object key) {
        if (null == key) {
            return 0;
        }
        
        return key.hashCode();
    }

    //获取元素
    public Object get(Object key) {
        // TODO Auto-generated method stub
        return null;
    }
    
    //存储元素
    public Object put(Object key, Object value) {
        // TODO Auto-generated method stub
        return null;
    }
    
}
  • 我们首先实现put方法,代码如下
    public Object put(Object key, Object value) {
        //该工具类不允许存储key为空的元素
        if (key == null) {
            return null;
        }
        
        //数组采用懒加载方式初始化
        if (table == null) {
            table = new Node[DEFAULT_INITIAL_CAPACITY];
        }
        
        int length = table.length;
        int positition;
        int hash;

        //注意要保证hash后的key在tables分布均匀
        //假如数组指定的位置没有元素,则把该地址指向元素对象
        if (table[positition = (length - 1 & (hash = hash(key)))] == null)  {
            table[positition] = new Node(hash, key, value ,null);
        } else {
            //假如指定的数组位置
            //假如确实存在则替换
            Node e = table[positition];
            if (e.hash == hash && (key == e.key || key.equals(e.key))) {
                Object oldValue = e.value;
                e.value = value;
                return oldValue;
                
            } else {
                //把新添的元素挂在链表上 , hash值相同的key在同一个链表上
                Node n;
                //自旋直至把新元素添加在相同hash链表尾部,或者把链表除头部以外相同key的替换掉值
                for (;;) {
                    //判断所在节点链表下一个是否为空
                    if ((n = e.next) == null) {
                        e.next = new Node(hash(key), key, value, null);
                        break;
                    }
                    
                    //判断链表以下有没有相同key
                    if (n.hash == hash && (key == n.key || key.equals(n.key))) {
                        Object oldValue = n.value;
                        n.value = value;
                        return oldValue;
                    }
                    
                    e = n;
                }
            }
        }
        
        return null;
    }
  • 上述代码最难理解的是,新添的元素怎么散列分布在数组上
    1). 和DEFAULT_INITIAL_CAPACITY 做&操作 - 保证元素在数组大小范围内分布
    2). 为什么是 (length - 1) & hash(key)

    • 首先数组大小的要保证是2的n次方,会发现结果-1在转换为二进制的时候,数码都为1;举例 (8-1)= 111; (16-1)= 1111; (32-1)= 11111;...这样在与hash(key)做&操作的时候就能尽量根据hash值不同,相对均匀的分布在数组上,方便后续查询操作
  • 接着实现get方法,代码如下

 //查找元素
    public Object get(Object key) {
        if (key == null) {
            return null;
        }
        
        int length = 0;
        Node first;
        if (table != null && (length = table.length) > 0) {
            //定位元素在数组中的位置
            if ((first = table[(length - 1) & hash(key)])  != null) {
                //查找该链表下的第一个node节点是否匹配
                if (first.key == key || key.equals(first.key)) {
                    return first.value;
                }
                
                //遍历链表
                Node next;
                if ((next = first.next) != null) {
                    for (;;) {
                        //查找链表next节点下的元素
                        if (next.key == key || key.equals(next.key)) {
                            return next.value;
                        }
                        
                        next = next.next;
                        //到达链表尾节点结束
                        if (null == next) {
                            break;
                        }
                    }
                }
            }
        }

        return null;
    }
  • 测试一下


    测试自己写的map工具.png
  • 随着添加的元素越来越多,为了保证查找方便,需要添加扩容resize方法

//MyHashMap新增resize方法
//该类加上  
//临界值
int threshold = DEFAULT_INITIAL_CAPACITY;
//元素个数
int size;
  • put方法加上以下
 //元素个数超过临界值就开始扩容
 if (++size >= threshold) {
       resize();
 }
        
 return null;
  • resize方法
//扩容
private void resize() {
        if (table != null) {
            Node[] oldTab = table;
            int oldLength = oldTab.length;
            
            //临界值为原来两倍
            threshold <<= 1;
            
            //新数组长度为原来两倍
            int newCap = oldLength << 1;
            
            //扩容
            Node[] newTab = new Node[newCap];
            
            //元素复制
            for (int i = 0 ; i < oldLength ; i++) {
                Node e;
                if ((e = table[i]) != null) {
                    
                    if (e.next == null) {
                        //直接复制
                        newTab[(newCap - 1) & hash(e.key)] = e;
                    } else {
                        //扩容的方式是*2,转换为二进制相当于最高位进1
                        //通过之前的元素key的hash跟扩容后的容量做&操作,来判断元素需不需要移动, 需要移动的都是做&操作后,高位为1
                        //需要移动的位置是 (原来的位置 + 原来数组的长度)
                        
                        //不需要移动的链表头和尾指针 (低位链表的头和尾)
                        Node lowerHeader = null, lowerTail = null;
                        
                        //需要移动的链表头和尾  (高位链表头和尾)
                        Node higherHeader = null, higherTail = null;
                        
                        Node next;
                        do {
                            next = e.next;
                            
                            if ((hash(e.key) & newCap) == 0) {
                                //元素不需要移动
                                if (lowerTail == null) {
                                    lowerHeader = e;
                                } else {
                                    lowerTail.next = e;
                                }
                                
                                lowerTail = e;
                                
                            } else {
                                //元素需要移动
                                if (higherTail == null) {
                                    higherHeader = e;
                                } else {
                                    higherTail.next = e;
                                }
                                
                                higherTail = e;
                            }
                            
                            
                        } while ((e=next) != null);
                        
                        //最后只需要把链表的第一个节点放好数组中即可
                        if (lowerTail != null) {
                            lowerTail.next = null;
                            newTab[i] = lowerHeader;
                        }
                        
                        if (higherTail != null) {
                            higherTail.next = null;
                            newTab[i + oldLength] = higherHeader;
                        }
                        
                    }
                }
            }
            
            table = newTab;
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容