HashMap和ConcurrentHashMap

HashMap

HashMap是Java集合框架中的一个核心类,用于存储键值对。它实现了Map接口,并基于哈希表(Hash Table)的数据结构来存储键值对。HashMap允许键为null,并允许多个键映射到同一个值。

数据结构

HashMap底层的数据结构是一个数组,数组中的每个元素是一个链表或红黑树。这个数组称为哈希表(Hash Table),每个链表或红黑树称为桶(Bucket)。
每个键值对通过哈希函数计算键的哈希值,并将其映射到数组中的某个索引位置,从而决定存储位置。

在Java 8之前,HashMap使用链表处理冲突,但当链表长度过长时,性能会退化为O(n)。Java 8引入了红黑树,当链表长度超过阈值(默认是8)时,将链表转换为红黑树,从而提高查找和插入性能。

  • 数组:哈希表是一个固定大小的数组,用于存储键值对。
  • 链表:当两个或多个键通过哈希函数计算出的哈希值相同,即发生了哈希冲突时,这些键值对会被存储在同一个桶中的链表中。
  • 红黑树:当链表中的元素数量超过一定阈值时,链表会被转换为红黑树,以提高查找效率。

实现原理

  • 哈希函数:HashMap使用键的哈希码(hashCode()方法返回的值)计算键的哈希值。哈希值通过一个扰动函数(扰动函数的目的是减少哈希冲突)进一步处理,然后通过取模运算将其映射到数组的索引位置:
// 这里使用了一个位运算(h >>> 16),将哈希码的高位和低位混合,以减少冲突。
static final int hash(Object key) {
   int h;
   return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 冲突处理:当两个键被映射到相同的数组索引时,称为哈希冲突。HashMap使用链地址法(separate chaining)处理冲突,即将相同索引位置的多个键值对存储在一个链表或红黑树中。
  • 插入操作:插入一个键值对时,HashMap首先计算键的哈希值并找到对应的桶索引。如果桶为空,则直接将节点放入该桶。如果桶不为空,则沿链表或红黑树查找该键。如果找到相同的键,则更新值;否则,将新节点添加到链表或红黑树的末尾
  • 查找操作:查找操作根据键计算哈希值和索引,然后在链表或红黑树中查找该键。如果找到,则返回相应的值,否则返回null。
  • 扩容机制:HashMap的初始容量负载因子(load factor)决定了何时进行扩容。默认初始容量为16,默认负载因子为0.75。当元素数量超过容量与负载因子的乘积时(即达到75%的负载因子),HashMap将进行扩容操作,通常是将容量加倍,并重新哈希所有现有元素
  • 优化:Java 8引入了红黑树,当链表长度超过一定阈值时,将其转换为红黑树,以提高性能。

ConcurrentHashMap

ConcurrentHashMap 是 Java 中的一种并发集合类,旨在提供高效的线程安全的哈希表实现。与 Collections.synchronizedMap 不同,ConcurrentHashMap 通过更细粒度的锁机制来实现高性能的并发访问。

数据结构

ConcurrentHashMap 的主要数据结构也是哈希表,但与 HashMap 不同,它采用了分段锁(在 Java 8 之前)和CAS操作(在 Java 8 及之后)来提高并发性能。

Java8之前的实现:

在 Java 8 之前,ConcurrentHashMap 通过使用多个独立的锁(称为“段”,即 Segment)来实现并发控制。每个 Segment 类似于一个小的 Hashtable,具有自己的锁。这种设计使得多个线程可以并发地访问不同段上的数据,从而提高并发性能。

static class Segment<K,V> extends ReentrantLock {
    // 内部维护一个哈希表
    transient volatile HashEntry<K,V>[] table;
}

每个 Segment 内部维护一个哈希表,类似于 HashMap 的实现。通过将整个哈希表划分为多个段,ConcurrentHashMap 可以支持更高的并发度。

Java8之后的实现:

在 Java 8 之后,ConcurrentHashMap 进行了重大的重构和优化。它不再使用 Segment,而是直接在每个桶上使用细粒度的同步机制CAS操作synchronized 块)。
主要的数据结构包括:

  1. Node类:Node 是哈希表中的基本存储单元,类似于 HashMap 中的 Node。
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
}
  1. 桶数组:ConcurrentHashMap 直接使用一个 Node[] 数组作为哈希表的基础结构。
transient volatile Node<K,V>[] table;

实现原理

ConcurrentHashMap 的实现原理主要通过以下几个方面来保证高效的并发访问:

  • 分段锁(Java8之前):使用 Segment 来分段锁定,每个 Segment 内部是一个独立的哈希表,具有自己的锁。这样可以允许多个线程同时访问不同的段,从而提高并发性能。
  • 细粒度锁(Java 8 之后):在 Java 8 之后,通过在每个桶上使用细粒度的锁(如 CAS 操作和 synchronized 块)来实现更高效的并发控制。
  • 冲突处理:通过链表和红黑树处理冲突,当链表长度超过阈值时转换为红黑树。
  • 扩容机制:当负载因子超过阈值时进行扩容,通过重新哈希所有元素并重新分布到新的桶中来实现。

ConcurrentHashMap 和HashMap 对比分析

线程安全性

  • ConcurrentHashMap:是线程安全的,允许多个线程并发访问而不需要额外的同步措施。它通过分段锁或细粒度锁来实现高并发。
  • HashMap:是非线程安全的,如果多个线程尝试修HashMap,可能会产生竞争条件,导致数据不一致。

性能

  • ConcurrentHashMap:在高并发环境下,由于其线程安全的特性,通常具有更好的性能。它允许多个线程同时读写,减少了锁竞争。
  • HashMap:在单线程环境下,由于没有线程安全的要求,通常比ConcurrentHashMap有更高的性能。

锁策略

  • ConcurrentHashMap(Java 8之前):使用分段锁,将数据分成多个段,每个段独立加锁。
  • ConcurrentHashMap(Java 8及之后):使用更细粒度的锁,每个节点可以独立加锁,提高了并发性能。
  • HashMap:不需要锁,但在多线程环境下,需要外部同步。

内部数据结构

  • ConcurrentHashMap:使用数组、链表和红黑树(Java 8及之后)来存储数据。当链表长度超过阈值时,链表会转换为红黑树。
  • HashMap:使用数组和链表来存储数据。当链表长度超过阈值时,会进行扩容操作。

内存占用

  • ConcurrentHashMap:由于需要存储锁信息或维护更复杂的数据结构,通常比HashMap占用更多的内存。
  • HashMap:内存占用相对较小,因为它不需要额外的线程安全措施。

迭代器

  • ConcurrentHashMap:提供了弱一致性迭代器,允许在迭代过程中看到部分修改,但不会抛出ConcurrentModificationException。
  • HashMap:迭代器是强一致性的,如果检测到并发修改,会抛出ConcurrentModificationException。

适用场景

  • ConcurrentHashMap:适用于需要高并发访问和修改的场景,如多线程应用程序中的共享数据存储。
  • HashMap:适用于单线程环境或在多线程环境中由外部同步控制访问的场景。

扩展性

  • ConcurrentHashMap:设计时就考虑了并发,因此更容易扩展到多线程环境。
  • HashMap:在多线程环境下需要额外的同步措施,这可能会限制其扩展性。
  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值