Java里各种基础容器的实现都是链表外加引用的形式。So,hashmap也不例外。

那小考虑下:hashmap是怎么产生的呢?

常用的两种数据结构数组和链表各有优劣,数组寻址容易,插入和删除困难;而链表寻址困难,插入删除容易,那我们能不能综合两者的特性呢?那就是哈希表啦。hashmap是java中对于哈希表的无锁实现。

首先,说句题外话:Hashtable是HashMap的难弟儿,hashtable是hashmap的线程安全版本,它的实现和HashMap实现基本一致,除了它不能包含null值的key和value,并且它在计算hash值和数组索引值的方式要稍微简单一些。对于线程安全的实现,Hashtable简单的将所有操作都标记成synchronized,即对当前实例的锁,这样容易引起一些性能问题,所以目前一般使用性能更好的ConcurrentHashMap。另外,对于HashMap是可以解决同步问题的,通过调用Map Collections.synchronizedMap(Map m),当然与可以自己在使用地方加锁。

下面进入正题:

哈希表有多种实现方法,java中实现采用的是拉链法,我们可以当成“链表的数组”,如下图所示:

先创建一个数组,在数组中存放是是链表的头节点。Java的HashMap里面实现了一个静态内部类Entry,其重要的属性有Key Value Entry(next)。而数组中保存的就是Entry。

我们先来看hashmap构造函数的代码。如下:

        this.loadFactor = DEFAULT_LOAD_FACTOR; //装填因子,扩容的时候使用
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); //默认的容量阈值
table = new Entry[DEFAULT_INITIAL_CAPACITY];//构造一个以Entry为对象的数组
init(); //空方法

由以上代码可以看出,初始化hashmap的过程只是初始化了一个Entry类型数组,Entry就是刚才说的Java实现的内部类,我们来看下Entry的代码?

 static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
      .......
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
} public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}

可以看出Entry是一个链表一样结构,其中有next域用于指向下一个Entry。

既然是Entry数组,那为什么能线性插入删除呢?那么我们看一下往hashmap里put之后都发生了什么呢?来看代码。

public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key); // 对key的哈希在做一个一些移位异或的操作,目的是防止一些鸡肋的key的哈希函数
int i = indexFor(hash, table.length);//返回hash对应的数组的下标。最简单的实现肯定是用hash%length,但这里不是这样,这里的处理很巧妙,下面还会再介绍。
    //遍历链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
        //如果key存在的话,就替换掉
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++; //修改次数,用于fail-fast策略
addEntry(hash, key, value, i); //添加entry到对应的数组中,函数见下 
      void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);//如果大小超过阈值的话,resize阈值。resize的过程原存在的Entry会重新计算索引值,并且Entry链的顺序也会发生颠倒(如果还在同一个链中的话);而该新添加的Entry的索引值也会重新计算。,消耗还是蛮大的,所以如果知道hashmap大小的话,最好还是给个初始值
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);//插入到头部,e表示Entry.next
size++;
}
      }
return null;
}

上文说的利用key的二次hash值求对应的下标的时候,最常规的想法就是用hash%length,但是在jdk里,没这么实现。而是

 static int indexFor(int h, int length) {
return h & (length-1);
}

这个方法非常巧妙,它通过 h & (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的 n 次方(可以看初始化的代码,扩容的时候也是扩容到2的那次方)。这看上去很简单,其实比较有玄机的,而当数组长度为16时,即为2的n次方时,2的n次方得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了复杂的异或和位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。这样效率提高了,而且冲突的可能性也减少了。

知道put操作作了什么,get的话就不难了。

就先写到这。

hashmap源码的更多相关文章

  1. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  2. HashMap源码分析

    最近一直特别忙,好不容易闲下来了.准备把HashMap的知识总结一下,很久以前看过HashMap源码.一直想把集合类的知识都总结一下,加深自己的基础.我觉的java的集合类特别重要,能够深刻理解和应用 ...

  3. JAVA源码分析-HashMap源码分析(一)

    一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...

  4. Java集合---HashMap源码剖析

    一.HashMap概述二.HashMap的数据结构三.HashMap源码分析     1.关键属性     2.构造方法     3.存储数据     4.调整大小 5.数据读取           ...

  5. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  6. 【JAVA集合】HashMap源码分析(转载)

    原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...

  7. HashMap源码解读(转)

    http://www.360doc.com/content/10/1214/22/573136_78188909.shtml 最近朋友推荐的一个很好的工作,又是面了2轮没通过,已经是好几次朋友内推没过 ...

  8. HashMap源码剖析

    HashMap源码剖析 无论是在平时的练习还是项目当中,HashMap用的是非常的广,真可谓无处不在.平时用的时候只知道HashMap是用来存储键值对的,却不知道它的底层是如何实现的. 一.HashM ...

  9. Java中HashMap源码分析

    一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtab ...

  10. 转:【Java集合源码剖析】HashMap源码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955   您好,我正在参加CSDN博文大赛,如果您喜欢我的文章,希望您能帮我投一票 ...

随机推荐

  1. BatteryWarning 电池预警

    MTK BatteryWarning 在mediatek/external/batterywarning下,会编译生成一个可执行文件:batterywraning main()函数中,会间断读取 /s ...

  2. Prime Path(BFS)

    Prime Path Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 131072/65536K (Java/Other) Total S ...

  3. boost log库

    http://blog.csdn.net/sheismylife/article/category/1820481

  4. mac xmind快捷键

    tab:新建分支 command +z : 撤销 command + "+":放大 command + "-":缩小 shift + enter : 文字换行

  5. eclipse中maven工程的创建javaweb项目

    第一步.new一个新的工程 ,选中maven project,点击next 第2步.不做选择,点击next 第3步.选择maven-archetype-webapp,点击next 第四步.填写项目名称 ...

  6. captche验证码

    JCaptcha 是一个用来生成验证码的开源Java类库 CaptchaServiceSingleton类(单态类) package com.dongbin.testy; import com.oct ...

  7. Jena文档《An Introduction to RDF and the Jena RDF API》的译文

    前言本文是一篇对W3C的资源描述框架(RDF)和 Jena(一个Java的RDF API)的教程性介绍. 本文是为那些不熟悉RDF的, 以及那些通过建立原形可以达到最好学习效果的, 或是因为其他原因希 ...

  8. 《Windows驱动开发技术详解》之定时器

    I/O定时器 I/O定时器是DDK提供的一种定时器.它每个1s钟系统会调用一次I/O定时器例程.I/O定时器例程运行在DISPATCH_LEVEL级别,因此在这个例程中不能使用分页内存,否则会引起页故 ...

  9. 查询页面checkbox使用

    HTML <input type="checkbox" id="IsChildGroup" name="IsChildGroup" v ...

  10. A框架 第二部 实例化接收到的get类,调用父类抽象方法,自动执行方法call_user_func_array()

    01父类抽象类 abstract.php <?phpabstract class controller_abstract{ protected $app; function __construc ...