热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android基础-Android中的HashMap浅析

以下源码基于Android中改造过后的HashMap0.HashMap中的关键变量MINIMUN_CAPACITY4(最小容量)MAXIMUN_CAPACITY

以下源码基于Android中改造过后的HashMap

0.HashMap中的关键变量

  • MINIMUN_CAPACITY = 4 (最小容量)
  • MAXIMUN_CAPACITY = 1 <<30 ; (最大容量)
  • private static final Entry[] EMPTY_TABLE= new HashMapEntry[MINIMUM_CAPACITY >>> 1]; 这里的这个就是hash表,是一种数组链表结构(和字典一样),默认的容量大小为4>>1,也就是2
  • DEFAULT_LOAD_FACTOR 负载因子,默认是0.75F
  • modCount 修改次数
  • threshold 阀值
  • 其他

1、HashMapEntry

看HashMapEntry的构造函数。

HashMapEntry(K key, V value, int hash, HashMapEntry next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}

从中可以看出,这是一个单链表的数据结构,存有key、value、hash值以及下一个节点。

2、HashMap的的初始化

  • HashMap()
  • HashMap(int capacity)
  • HashMap(int capacity,float loadFactor)

第三个构造方法,直接调用的是第一个构造方法,并对loadFactor进行判断(然而,这并没有什么吊用)
那么。我们就来看HashMap的代码吧。

    public HashMap() {
table = (HashMapEntry[]) EMPTY_TABLE;
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
}
  • 这里的这个table是什么呢?因为这是个数组,而数组中每个元素都是单链表,所有,就构成table的样式了。
  • threshold = -1,看注释是说,首次调用替换掉EMPTY_TABLE.

3、添加数据

  • put(K key, V value)
  • putAll(Map

3.1、put(K key,V value)

    @Override public V put(K key, V value) {
if (key == null) {
return putValueForNullKey(value);
}

int hash = Collections.secondaryHash(key);
HashMapEntry[] tab = table;
int index = hash & (tab.length - 1);
for (HashMapEntry 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;
}
  • 若key为null,则将值存放在entryForNullKey(当然会做一些处理)
  • 算出key对应的hash值
  • 根据hash值计算出index值取到对应的链表,如果存在hash值相等并且key值相等的的Entry,就修改value值,并返回旧的value值
  • 如果size++大于了阀值,对进行扩容并从新计算index值
  • 插入一个新的Entry,并返回null

下面来对上面的1,4,5进行说明

3.1.1 putValueForNullKey操作

相对应的源码如下。

    private V putValueForNullKey(V value) {
HashMapEntry entry = entryForNullKey;
if (entry == null) {
addNewEntryForNullKey(value);
size++;
modCount++;
return null;
} else {
preModify(entry);
V oldValue = entry.value;
entry.value = value;
return oldValue;
}
}

这里对应的操作也很简单,如果当前entryForNullKey为null的话,就添加一个,不为null,就修改值

3.1.2 doubleCapacity() 扩容

扩容部分源代码较长,咱们分段来看。

        HashMapEntry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
return oldTable;
}
int newCapacity = oldCapacity * 2;
HashMapEntry[] newTable = makeTable(newCapacity);
if (size == 0) {
return newTable;
}
  • 如果到最大容量了,直接返回
  • 将容量设置为原来的2倍
  • 制造一个table,(ps:制造的时候会将阀值设置为3/4,(容量>>1) + (容量>>2),>>1 相当于/2,>>2 相当于/4)
  • 如果size(原先存储的数目)为0,直接返回
        for (int j = 0; j             /*
* Rehash the bucket using the minimum number of field writes.
* This is the most subtle and delicate code in the class.
*/
HashMapEntry e = oldTable[j];
if (e == null) {
continue;
}
int highBit = e.hash & oldCapacity;
HashMapEntry broken = null;
newTable[j | highBit] = e;
for (HashMapEntry 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;
  • 上面代码的就是将原table中每一处对应的链表取出来,并且从新散列
3.1 addNewEntry添加新的Entry
table[index] = new HashMapEntry(key, value, hash, table[index]);

其中table[index]就是一个单链表,这里就是生成一个HashMapEntry并将其插入到index处的,当然,我们还需要看一下生成的构造方法。

        HashMapEntry(K key, V value, int hash, HashMapEntry next) {
this.key = key;
this.value = value;
this.hash = hash;
this.next = next;
}

结合逻辑可以知道,我们可以看的出,每次是在链表的头部进行数据插入的。

3.2、putAll

    @Override public void putAll(Map map) {
ensureCapacity(map.size());
super.putAll(map);
}
  • ensureCapacity,确保容量(这里就是进行容量检查,不够扩容,具体的细节就不说了)
  • 调用父类去put数据

在这里我们就需要明白父类的实现了。

    public void putAll(Mapextends K, ? extends V> map) {
for (Map.Entryextends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}

从中可以看出,如果穿进来的是另一个HashMap的话,就会将这个HashMap中的Entry挨个加入到运来的HashMap中。

4、get取值

取值的过程因为在散列与散列码中,有提到过,所以这里就不多说了。

5、总结

HashMap查询快速的原因就在于hashtable的思想。就像字典一样。

本博文中只是简单的介绍了下HashMap。当然HaspMap中还有许多值得我们去思考的问题,诸如:

  • 负载因子 为什么是0.75?
  • 初始容量为什么在Java8中改成2了
  • 散列时index的算法
  • 为什么从新散列是那样求index的
  • 其他

等等,这些问题,每一个都值得我们去好好地研究。


推荐阅读
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
author-avatar
手机用户2502941301
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有