HashMap

本文详细解读了HashMap作为基于哈希表的非同步Map接口实现的特点,包括其链表散列数据结构、存取机制、哈希计算、失败快速机制及线程安全性等内容。重点介绍了HashMap如何通过数组和链表结合的方式存储和检索键值对,以及其在实际应用中的优化策略。

HashMap%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;

HashMap是基于哈希表的Map接口的非同步实现。允许使用null值和null键。

数据结构%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;

11

HashMap是一个%26ldquo;链表散列%26rdquo;的数据结构,即数组和链表的结合体。HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

/** 
 * The table, resized as necessary. Length MUST Always be a power of two. 
 */  
transient Entry[] table;  
  
static class Entry%26lt;K,V%26gt; implements Map.Entry%26lt;K,V%26gt; {  
    final K key;  
    V value;  
    Entry%26lt;K,V%26gt; next;  
    final int hash;  
    %26hellip;%26hellip;  
}

Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。

存取实现%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;

public V put(K key, V value) {  
    // HashMap允许存放null键和null值。  
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
    if (key == null)  
        return putForNullKey(value);  
    // 根据key的keyCode重新计算hash值。  
    int hash = hash(key.hashCode());  
    // 搜索指定hash值在对应table中的索引。  
    int i = indexFor(hash, table.length);  
    // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。  
    for (Entry%26lt;K,V%26gt; e = table[i]; e != null; e = e.next) {  
        Object k;  
        if (e.hash == hash %26amp;%26amp; ((k = e.key) == key || key.equals(k))) {  
            V oldValue = e.value;  
            e.value = value;  
            e.recordAccess(this);  
            return oldValue;  
        }  
    }  
    // 如果i索引处的Entry为null,表明此处还没有Entry。  
    modCount++;  
    // 将key、value添加到i索引处。  
    addEntry(hash, key, value, i);  
    return null;  
}

当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

addEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。addEntry 是HashMap 提供的一个包访问权限的方法,代码如下:

void addEntry(int hash, K key, V value, int bucketIndex) {  
    // 获取指定 bucketIndex 索引处的 Entry   
    Entry%26lt;K,V%26gt; e = table[bucketIndex];  
    // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry  
    table[bucketIndex] = new Entry%26lt;K,V%26gt;(hash, key, value, e);  
    // 如果 Map 中的 key-value 对的数量超过了极限  
    if (size++ %26gt;= threshold)  
    // 把 table 对象的长度扩充到原来的2倍。  
        resize(2 * table.length);  
}

hash(int h)方法根据key的hashCode重新计算一次散列。此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突。

static int hash(int h) {  
    h ^= (h %26gt;%26gt;%26gt; 20) ^ (h %26gt;%26gt;%26gt; 12);  
    return h ^ (h %26gt;%26gt;%26gt; 7) ^ (h %26gt;%26gt;%26gt; 4);  
}

对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 hash 码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,%26ldquo;模%26rdquo;运算的消耗还是比较大的,在HashMap中是这样做的:调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下:

static int indexFor(int h, int length) {  
    return h %26amp; (length-1);  
}

它通过 h %26amp; (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的n 次方,这是HashMap在速度上的优化。

int capacity = 1;  
    while (capacity %26lt; initialCapacity)  
        capacity %26lt;%26lt;= 1;

这段代码保证初始化时HashMap的容量总是2的n次方,即底层数组的长度总是为2的n次方。

当length总是 2 的n次方时,h%26amp; (length-1)运算等价于对length取模,也就是h%length,但是%26amp;比%具有更高的效率。

%26nbsp;

public V get(Object key) {  
    if (key == null)  
        return getForNullKey();  
    int hash = hash(key.hashCode());  
    for (Entry%26lt;K,V%26gt; e = table[indexFor(hash, table.length)];  
        e != null;  
        e = e.next) {  
        Object k;  
        if (e.hash == hash %26amp;%26amp; ((k = e.key) == key || key.equals(k)))  
            return e.value;  
    }  
    return null;  
}

从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

%26nbsp;

归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

Fail-Fast机制%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;

我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

HashIterator() {  
    expectedModCount = modCount;  
    if (size %26gt; 0) { // advance to first entry  
    Entry[] t = table;  
    while (index %26lt; t.length %26amp;%26amp; (next = t[index++]) == null)  
        ;  
    }  
}

在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:

注意到modCount声明为volatile,保证线程之间修改的可见性。

final Entry%26lt;K,V%26gt; nextEntry() {     
    if (modCount != expectedModCount)     
        throw new ConcurrentModificationException();

迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该

仅用于检测程序错误。

我是天王盖地虎的分割线%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;%26nbsp;

内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值