由于是浅析,所以我只分析常用的接口,注意是Android系统中的JAVA类,可能和JDK的源码有区别。

首先从构造函数开始,

     /**
* Min capacity (other than zero) for a HashMap. Must be a power of two
* greater than 1 (and less than 1 << 30).
*/
private static final int MINIMUM_CAPACITY = 4; /**
* Max capacity for a HashMap. Must be a power of two >= MINIMUM_CAPACITY.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30; /**
* An empty table shared by all zero-capacity maps (typically from default
* constructor). It is never written to, and replaced on first put. Its size
* is set to half the minimum, so that the first resize will create a
* minimum-sized table.
*/
private static final Entry[] EMPTY_TABLE
= new HashMapEntry[MINIMUM_CAPACITY >>> 1]; /**
* The default load factor. Note that this implementation ignores the
* load factor, but cannot do away with it entirely because it's
* mentioned in the API.
*
* <p>Note that this constant has no impact on the behavior of the program,
* but it is emitted as part of the serialized form. The load factor of
* .75 is hardwired into the program, which uses cheap shifts in place of
* expensive division.
*/
static final float DEFAULT_LOAD_FACTOR = .75F; /**
* The hash table. If this hash map contains a mapping for null, it is
* not represented this hash table.
*/
transient HashMapEntry<K, V>[] table; /**
* The entry representing the null key, or null if there's no such mapping.
*/
transient HashMapEntry<K, V> entryForNullKey; /**
* The number of mappings in this hash map.
*/
transient int size; /**
* Incremented by "structural modifications" to allow (best effort)
* detection of concurrent modification.
*/
transient int modCount; /**
* The table is rehashed when its size exceeds this threshold.
* The value of this field is generally .75 * capacity, except when
* the capacity is zero, as described in the EMPTY_TABLE declaration
* above.
*/
private transient int threshold; public HashMap() {
table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
} public HashMap(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("Capacity: " + capacity);
} if (capacity == 0) {
@SuppressWarnings("unchecked")
HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;
table = tab;
threshold = -1; // Forces first put() to replace EMPTY_TABLE
return;
} if (capacity < MINIMUM_CAPACITY) {
capacity = MINIMUM_CAPACITY;
} else if (capacity > MAXIMUM_CAPACITY) {
capacity = MAXIMUM_CAPACITY;
} else {
capacity = Collections.roundUpToPowerOfTwo(capacity);
}
makeTable(capacity);
} public HashMap(int capacity, float loadFactor) {
this(capacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Load factor: " + loadFactor);
} /*
* Note that this implementation ignores loadFactor; it always uses
* a load factor of 3/4. This simplifies the code and generally
* improves performance.
*/
}

通过三个构造函数的源码,我们可以知道:

  • HashMap内部实际上使用HashMapEntry数组来实现的。
  • 当调用new HashMap()时,会创建容量为2的HashMapEntry数组,并且threshold为-1。
  • 当调用HashMap(int capacity)时,HashMap会将传入的capacity转换成最小的大于等于capacity的2的次方,比如:capacity=25,会转换成32。并且threshold为总容量的75%,threshold的作用是当entry的数量大于threshold时,进行扩容。
  • HashMap(int capacity, float loadFactory)实际上和HashMap(int capacity)是一样的,loadFactory参数未被使用(注意这是Android做的修改,实际上JDK中会使用这个参数)。

既然是HashMapEntry数组实现的,我们简单看下这个Entry什么样,

static class HashMapEntry<K, V> implements Entry<K, V> {
final K key;
V value;
final int hash;
HashMapEntry<K, V> next; HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}
}

这里注意关注next属性,有一定经验的朋友肯定知道,这是单向链表的实现,所以实现HashMap的数组的每一项其实是一个单向链表的Head,继续往下看,

接下来我们分析下put(K key, V value)方法,

     void addNewEntryForNullKey(V value) {
entryForNullKey = new HashMapEntry<K, V>(null, value, 0, null);
} private V putValueForNullKey(V value) {
HashMapEntry<K, V> entry = entryForNullKey;
if (entry == null) {
addNewEntryForNullKey(value);
size++;
modCount++;
return null;
} else {
preModify(entry);
V oldValue = entry.value;
entry.value = value;
return oldValue;
}
} private HashMapEntry<K, V>[] makeTable(int newCapacity) {
@SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable
= (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity];
table = newTable;
threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
return newTable;
} private HashMapEntry<K, V>[] doubleCapacity() {
HashMapEntry<K, V>[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
return oldTable;
}
int newCapacity = oldCapacity * 2;
HashMapEntry<K, V>[] newTable = makeTable(newCapacity);
if (size == 0) {
return newTable;
} for (int j = 0; j < oldCapacity; j++) {
/*
* Rehash the bucket using the minimum number of field writes.
* This is the most subtle and delicate code in the class.
*/
HashMapEntry<K, V> e = oldTable[j];
if (e == null) {
continue;
}
int highBit = e.hash & oldCapacity;
HashMapEntry<K, V> broken = null;
newTable[j | highBit] = e;
for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {
int nextHighBit = n.hash & oldCapacity;
if (nextHighBit != highBit) {
if (broken == null)
newTable[j | nextHighBit] = n;
else
broken.next = n;
broken = e;
highBit = nextHighBit;
}
}
if (broken != null)
broken.next = null;
}
return newTable;
} @Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
} int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
} // No entry for (non-null) key is present; create one
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
} void addNewEntry(K key, V value, int hash, int index) {
table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}

从put(K key, V value)的源码我们可以得到如下信息:

  • 当添加key为null的value时,会用单独的HashMapEntry entryForNullKey对象来储存。
  • entry数组的索引是通过hash算出来的:int index = hash & (tab.length - 1)。
  • 当发生碰撞时(也就是算出的index上已经存在entry了),会首先检查是否是同一个hash和key,如果是则更新value,然后直接将old value返回。
  • 新创建的entry会被设置成对应index上的链表Head。
  • 当entry数量大于threshold(capacity的75%)时,对数组进行扩容,扩大为原来的2倍,并重新计算原数组中所有entry的index,然后复制到新数组中。

分析完put后,其他如get、remove、containsKey等接口就大同小异了,在此直接略过。

接下来我们看下Set<K> keySet()接口:

    @Override public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null) ? ks : (keySet = new KeySet());
} private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
int oldSize = size;
HashMap.this.remove(o);
return size != oldSize;
}
public void clear() {
HashMap.this.clear();
}
} Iterator<K> newKeyIterator() { return new KeyIterator(); } private final class KeyIterator extends HashIterator
implements Iterator<K> {
public K next() { return nextEntry().key; }
}

从源码中可以得出如下结论:

  • keySet返回的Set对象实际上和HashMap是强关联的,对Set接口的调用,实际上操作的还是HashMap。
  • Set中的iterator实际上也是实现自HashIterator。
  • entrySet()、valueSet()和keySet()的实现原理一样。

知道HashMap的实现原理后,我们就可以知道他的优缺点了:

优点:读写效率高,接近数组的索引方式。

缺陷:会占用大量的无效内存,为了减少碰撞,Entry数组的容量只能是2的N次幂,并且当entry数大于总容量的75%时就会扩容两倍。

如有问题,欢迎指出!

转载请注明出处。

[原创]Android系统中常用JAVA类源码浅析之HashMap的更多相关文章

  1. Long类源码浅析

    1.Long类和Integer相类似,都是基本类型的包装类,类中的方法大部分都是类似的: 关于Integer类的浅析可以参看:Integer类源码浅析 2.这里主要介绍一下LongCache类,该缓存 ...

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

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

  3. 【Java集合源码剖析】HashMap源码剖析

    转载出处:http://blog.csdn.net/ns_code/article/details/36034955 HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-va ...

  4. android系统中使用TelephonyManager类来获取imei号和其他手机信息

    在AndroidManifest.xml文件中增加<!--允许读取电话状态SIM的权限--><uses-permission android:name="android.p ...

  5. Android 热修复 Tinker接入及源码浅析

    一.概述 放了一个大长假,happy,先祝大家2017年笑口常开. 假期中一行代码没写,但是想着马上要上班了,赶紧写篇博客回顾下技能,于是便有了本文. 热修复这项技术,基本上已经成为项目比较重要的模块 ...

  6. Java集合类源码解析:HashMap (基于JDK1.8)

    目录 前言 HashMap的数据结构 深入源码 两个参数 成员变量 四个构造方法 插入数据的方法:put() 哈希函数:hash() 动态扩容:resize() 节点树化.红黑树的拆分 节点树化 红黑 ...

  7. Java集合源码阅读之HashMap

    基于jdk1.8的HashMap源码分析. 引用于:http://blog.stormma.me/2017/05/31/Java%E9%9B%86%E5%90%88%E6%BA%90%E7%A0%81 ...

  8. ArrayList类源码浅析(一)

    1.首先来看一下ArrayList类中的字段 可以看出,ArrayList维护了一个Object数组,默认容量是10,size记录数组的长度: 2.ArrayList提供了三个构造器:ArrayLis ...

  9. ArrayList类源码浅析(三)

    1.看一个示例 运行上述代码,抛出一个异常: 这是一个典型的并发修改异常,如果把上述代码中的125行注释,把126行打开,运行就能通过了: 原因: 1)因为在迭代的时候,使用的是Itr类的对象,在调用 ...

随机推荐

  1. apt-get程序和系统管理

    检查更新:apt-get update 系统升级:apt-get upgrade 移除软件:apt-get remove xxx 自动移除旧的软件包:apt-get autoremove 完全移除软件 ...

  2. Dynamic CRM 2013学习笔记(三十一)自定义用excel批量导入实体数据

    有一个实体的子表数据量太大,于是客户想用execel来导入实体数据.首先想到的是用系统自带的Import Data,客户嫌太麻烦,比如lookup字段要做map等. 下面是具体的实现步骤: 一.定义e ...

  3. 虚拟机Ubuntu14/15启用root用户登录

    1. 为root用户设置密码 sudo passwd root 需要先输入一次当前用户的登陆密码,然后输入两次root用户的密码(自己设置). 2. 切换至root用户 sudo -s 3. 编辑登陆 ...

  4. Idol之坑

    1. 编码格式不统一 Idol的API 是基于 Http 的 Get 调用,输入N多参数,可以吐出Xml和 Json. 由于Idol没有在输出文档中指定编码格式(ContentType),用 Http ...

  5. 微软 PowerShell Script Explorer 满血复活,正式发布

    一年前的今天,微软在其Windows PowerShell官方博客声明中止 ‘Script Explorer’ 应用程序的开发. 一年后的今天,微软为其Script Explorer注入了新的生命.一 ...

  6. [读书笔记]C#学习笔记七: C#4.0中微小改动-可选参数,泛型的可变性

    前言 下面就开始总结C#4.0的一些变化了, 也是这本书中最后的一点内容了, 这一部分终于要更新完了. 同时感觉再来读第二遍也有不一样的收获. 今天很嗨的是武汉下雪了,明天周六,一切都是这么美好.哈哈 ...

  7. paip.提升性能---mysql 优化cpu多核以及lan性能的关系.

    paip.提升性能---mysql 优化cpu多核以及lan性能的关系. 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http:/ ...

  8. 安装Ubuntu14.04版本的操作系统

    1:安装好虚拟机之后便是安装操作系统,操作系统分为好多种,在这里笔者使用的是Ubuntu14.04版本的操作系统,除此之后还可以使用fedora或者小红帽等等操作系统 软件包http://pan.ba ...

  9. Leetcoede 112 Path Sum 二叉树

    二叉树的从叶子到根的和是否存在 /** * Definition for binary tree * struct TreeNode { * int val; * TreeNode *left; * ...

  10. Android组件Spinner使用

    Spinner组件是Android当中非常常用的一种用于下拉选择的组件. 本blog当中主要解决的几个问题: 如何在XML中初始化Spinner选项 如何使用代码的方式初始化Spinner选项 一个A ...