HashMap那些事

HashMap其实是由数组+链表+红黑树组成的,这个特性在JAVA8中已经得以体现。最初并没有红黑树,只是由链表组成,但是,当链表过长时,查询效率会十分低下。所以JAVA8中当链表的长度到达一定的高度时候,便会转换为红黑树,以此来优化性能。
我们可以把HashMap看成是一个数组(它底层的实现也是这样的,只是可能会稍稍复杂),而它是由一个个单链表组成的(数组的每项元素为一个节点的链表)。

  HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。 
  容量 :是哈希表中桶的数量,初始容量只是哈希表在创建时的容量,实际上就是Entry< K,V>[] table的容量 
  加载因子 :是哈希表在其容量自动增加之前可以达到多满的一种尺度。它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75。

HashMap通常会用一个指针数组(假设为table[])来做分散所有的key,当一个key被加入时,会通过Hash算法通过key算出这个数组的下标i,然后就把这个<key, value>插到table[i]中,如果有两个不同的key被算在了同一个i,那么就叫冲突,又叫碰撞,这样会在table[i]上形成一个链表。只是简单地对数组长度执行取模运算(即仅保留Hash二进制的后i位)
一般当容量大于或等于负载因子,HashMap需要扩容,一般是扩大为原来的两倍,然后进行Rehash操作,然后释放原来的空间。


解决hash冲突的办法
1.开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
2.再哈希法
3.链地址法
4.建立一个公共溢出区 


当ReHash时,数组长度加倍变为2i+1,旧数组第j项链表中的每个节点,要么移动到新数组中第j项,要么移动到新数组中第j+2i项,而它们的唯一区别在于Hash值第i+1位的不同(第i+1位为0则仍为第j项,否则为第j+2i项)
也就是原来下标为3,容量大小为8的HashMap 扩充为容器大小为16的HashMap时,原本下标为3下面的链表要么还在下标为3的下面,要么在3+2*(4)= 8的下标下面。

接下来,我们来看看它的源代码

public V put(K key, V value)
{
    ......
    //算Hash值
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    //如果该key已被插入,则替换掉旧的value (链接操作)
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    //该key不存在,需要增加一个结点
    addEntry(hash, key, value, i);
    return null;
}


//检查容量是否超标
void addEntry(int hash, K key, V value, int bucketIndex)
{
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    //查看当前的size是否超过了我们设定的阈值threshold,如果超过,需要resize
    if (size++ >= threshold)
        resize(2 * table.length);
}


//ReHash 开辟空间,转移数据
void resize(int newCapacity)
{
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    ......
    //创建一个新的Hash Table
    Entry[] newTable = new Entry[newCapacity];
    //将Old Hash Table上的数据迁移到New Hash Table上
    transfer(newTable);
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
}

//具体的转移过程
void transfer(Entry[] newTable)
{
    Entry[] src = table;
    int newCapacity = newTable.length;
    //下面这段代码的意思是:
    //  从OldTable里摘一个元素出来,然后放到NewTable中
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}

交接我们就来看看ReHash的过程:

拿下图为例





最后再把数据(key5)插入,然后结束。


HashMap并不能直接应用于多线程环境,这是为什么呢?

当线程一扩容完毕准备,移动数据时,切换到线程二。当线程二中的数据插入到一定程度时,切回线程一,而,e和next的位置还是原来数据的位置,但是现在却颠倒了,如下图


可以看见,此时已经形成环,正如源码中红色标注的代码一样



我们看到,java.util.HashMap并不能直接应用于多线程环境。对于多线程环境中应用HashMap,主要有以下几种选择:
1.使用线程安全的java.util.Hashtable作为替代。
2.使用java.util.Collections.synchronizedMap方法,将已有的HashMap对象包装为线程安全的。
3.使用java.util.concurrent.ConcurrentHashMap类作为替代,它具有非常好的性能。

而以上几种方法在实现的具体细节上,都或多或少地用到了互斥锁。互斥锁会造成线程阻塞,降低运行效率,并有可能产生死锁、优先级翻转等一系列问题。


其实还有其他的办法

点击打开链接可以查看


本文参考https://coolshell.cn/articles/9606.html



【轴承故障诊断】基于融合鱼鹰和柯西变异的麻雀优化算法OCSSA-VMD-CNN-BILSTM轴承诊断研究【西储大学数据】(Matlab代码实现)内容概要:本文提出了一种基于融合鱼鹰和柯西变异的麻雀优化算法(OCSSA)优化变分模态分解(VMD)参数,并结合卷积神经网络(CNN)与双向长短期记忆网络(BiLSTM)的轴承故障诊断模型。该方法利用西储大学公开的轴承数据集进行验证,通过OCSSA算法优化VMD的分解层数K和惩罚因子α,有效提升信号分解精度,抑制模态混叠;随后利用CNN提取故障特征的空间信息,BiLSTM捕捉时间序列的动态特征,最终实现高精度的轴承故障分类。整个诊断流程充分结合了信号预处理、智能优化与深度学习的优势,显著提升了复杂工况下轴承故障诊断的准确性与鲁棒性。; 适合人群:具备一定信号处理、机器学习及MATLAB编程基础的研究生、科研人员及从工业设备故障诊断的工程技术人员。; 使用场景及目标:①应用于旋转机械设备的智能运维与故障预警系统;②为轴承等关键部件的早期故障识别提供高精度诊断方案;③推动智能优化算法与深度学习在工业信号处理领域的融合研究。; 阅读建议:建议读者结合MATLAB代码实现,深入理解OCSSA优化机制、VMD参数选择策略以及CNN-BiLSTM网络结构的设计逻辑,通过复现实验掌握完整诊断流程,并可进一步尝试迁移至其他设备的故障诊断任务中进行验证与优化。
05-23
### Java 中 HashMap 的使用方法 #### 1. **简介** `HashMap` 是 Java 集合框架中的一个重要类,用于存储键值对(key-value pair)。它的底层基于哈希表实现,提供了快速的查找、插入和删除操作。`HashMap` 不保证元素的顺序,并允许一个 `null` 键和多个 `null` 值。 #### 2. **基本操作** 以下是 `HashMap` 的一些常用方法及其功能: - **put(key, value)**: 将指定的键值对存入 `HashMap`。 - **get(key)**: 返回与指定键关联的值。 - **remove(key)**: 移除指定键对应的映射关系。 - **size()**: 返回 `HashMap` 中键值对的数量。 - **clone()**: 创建并返回该 `HashMap` 对象的一个副本。 - **isEmpty()**: 如果此 `HashMap` 映射不包含任何键值对,则返回 true。 这些方法的具体用法可以通过下面的例子来说明。 --- #### 3. **示例代码** ##### 示例 1: 添加键值对并获取大小 ```java // Java program to demonstrate the use of size() method in HashMap import java.util.*; public class SizeExample { public static void main(String[] args) { // Create an empty HashMap Map<Integer, String> map = new HashMap<>(); // Add key-value pairs using put() map.put(10, "C"); map.put(20, "C++"); map.put(50, "JAVA"); map.put(40, "PHP"); map.put(30, "SFDC"); // Print the HashMap content System.out.println("HashMap Content: " + map); // Get the number of entries in the HashMap int size = map.size(); System.out.println("Size of HashMap: " + size); } } ``` 这段代码展示了如何向 `HashMap` 插入数据以及计算其大小[^1]。 --- ##### 示例 2: 删除特定键值对 ```java // Java program to demonstrate the removal operation in HashMap import java.util.*; public class RemoveExample { public static void main(String[] args) { // Initialize a HashMap with some data Map<Integer, String> map = new HashMap<>(); map.put(10, "C"); map.put(20, "C++"); map.put(50, "JAVA"); map.put(40, "PHP"); map.put(30, "SFDC"); // Display initial state System.out.println("Initial HashMap: " + map); // Remove entry associated with key '50' map.remove(50); // Show updated HashMap after deletion System.out.println("Updated HashMap after removing key '50': " + map); } } ``` 这里演示了如何移除某个键所对应的数据项[^2]。 --- ##### 示例 3: 复制一份新的 HashMap 实例 ```java // Example demonstrating cloning functionality within HashMaps. import java.util.*; public class CloneExample { public static void main(String[] args) { // Original HashMap creation and population HashMap<Integer, String> originalMap = new HashMap<>(); originalMap.put(10, "C"); originalMap.put(20, "C++"); originalMap.put(50, "JAVA"); originalMap.put(40, "PHP"); originalMap.put(30, "SFDC"); // Cloning process begins here HashMap<Integer, String> clonedMap = (HashMap<Integer, String>)originalMap.clone(); // Output both maps post-cloning action System.out.println("Original HashMap Contents: " + originalMap); System.out.println("Cloned HashMap Contents: " + clonedMap); } } ``` 本部分解释了复制现有 `HashMap` 的过程[^3]。 --- #### 4. **性能特点与其他注意项** 由于 `HashSet` 内部依赖于 `HashMap` 来管理其成员集合,因此它们共享相似的时间复杂度特性——平均情况下 O(1),最坏情况取决于冲突处理机制[^4]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值