数据结构(哈希表(下)方法讲解)

前言:

在前一部分中,我们探讨了哈希表的基本原理、设计思想、优势与挑战,并了解了它在实际项目中的应用场景。哈希表作为一种高效的数据结构,在查找、插入和删除等操作上具有显著优势,但要真正掌握它的使用,深入理解其操作方法是至关重要的。

本部分将专注于哈希表的 方法讲解。哈希表的操作方法包括插入、删除、查找等核心功能,而这些方法的实现直接影响哈希表在实际开发中的表现。理解这些方法背后的工作原理和优化技巧,有助于提高开发效率,并确保系统的高性能和稳定性。

本部分内容将涵盖以下几个方面:

  1. 哈希表常见方法

    • 我们将详细介绍哈希表的常用操作方法,如 插入(insert)查找(search)删除(delete)更新(update)遍历(traverse) 等。每个方法都会通过具体的代码示例进行讲解,帮助您理解如何在实践中使用这些方法。
  2. 哈希函数与冲突处理

    • 哈希函数是哈希表操作的核心,它决定了数据如何被映射到哈希表中的槽位。我们将介绍如何设计高效的哈希函数以及哈希冲突的处理策略(如链式法、开放地址法等),并分析这些策略对方法性能的影响。
  3. 性能分析与优化

    • 虽然哈希表的平均时间复杂度为 O(1),但不同操作和不同数据集的情况可能导致性能波动。我们将深入分析如何通过优化方法来提升哈希表的性能,减少哈希冲突、避免不必要的扩容等问题。
  4. 语言特定的哈希表方法

    • 各种编程语言提供了哈希表的实现和接口,如 Python 中的 dictJava 中的 HashMapC++ 中的 unordered_map 等。我们将介绍这些语言中特定的哈希表方法,帮助您理解不同语言中的哈希表实现及其特点。
  5. 实践应用示例

    • 我们将通过具体的编程示例,展示如何利用哈希表的各种方法解决实际问题,帮助您在项目中灵活运用哈希表,提高开发效率。

通过本部分的学习,您将能够深入理解哈希表的方法实现与使用技巧,掌握如何根据实际需求选择合适的哈希表操作,并优化其性能。这些知识将为您在实际开发中处理复杂的数据存储和检索问题奠定坚实的基础。

接下来,让我们一起深入探讨哈希表的核心方法与操作,帮助您更好地掌握这一数据结构的实际应用。

1 哈希表的方法介绍:

1.1 基本方法

1.1.1 put(K key, V value)
  • 作用:将指定的键与指定的值关联。若该键已存在,更新其对应的值;如果键不存在,则插入新键值对。
  • 返回值:如果之前存在该键,返回旧值;如果不存在,返回 null

HashMap<Integer, String> map = new HashMap<>();
map.put(1, "Apple"); // 插入键值对 (1, "Apple")
map.put(2, "Banana"); // 插入键值对 (2, "Banana")

1.1.2 get(Object key)
  • 作用:返回与指定键关联的值。如果该键不存在,则返回 null
  • 返回值:指定键对应的值;如果该键不存在,返回 null

String value = map.get(1);  // 返回 "Apple"
String valueNotFound = map.get(3);  // 返回 null

1.1.3 remove(Object key)

  • 作用:移除与指定键关联的键值对。
  • 返回值:返回与指定键关联的值,如果该键不存在,返回 null

String removedValue = map.remove(1);  // 返回 "Apple",移除键值对 (1, "Apple")

1.1.4 clear()
  • 作用:清空 HashMap 中的所有键值对。
  • 返回值:无返回值。

map.clear();  // 清空所有条目

1.1.5 containsKey(Object key)
  • 作用:检查 HashMap 中是否包含指定的键。
  • 返回值:如果存在该键,返回 true;否则返回 false

boolean hasKey = map.containsKey(2);  // 返回 true
boolean hasKeyNotFound = map.containsKey(3);  // 返回 false

1.1.6 containsValue(Object value)
  • 作用:检查 HashMap 中是否包含指定的值。
  • 返回值:如果存在该值,返回 true;否则返回 false

boolean hasValue = map.containsValue("Banana");  // 返回 true
boolean hasValueNotFound = map.containsValue("Orange");  // 返回 false

1.1.7 size()
  • 作用:返回 HashMap 中键值对的数量。
  • 返回值:键值对的数量(整数)。

int size = map.size();  // 返回 2

1.1.8 isEmpty()
  • 作用:检查 HashMap 是否为空。
  • 返回值:如果 HashMap 为空,返回 true;否则返回 false

boolean empty = map.isEmpty();  // 返回 false

1.2 视图方法

视图方法返回不同类型的集合视图,允许通过 HashMap 操作键、值或键值对。

1.2.1 keySet()
  • 作用:返回 HashMap 中所有键的集合视图。
  • 返回值:包含所有键的 Set 集合。

Set<Integer> keys = map.keySet();  // 返回所有键的 Set

1.2.2 values()
  • 作用:返回 HashMap 中所有值的集合视图。
  • 返回值:包含所有值的 Collection 集合。

Collection<String> values = map.values();  // 返回所有值的 Collection

1.2.3 entrySet()
  • 作用:返回 HashMap 中所有键值对的集合视图。每个键值对封装在一个 Map.Entry 对象中。
  • 返回值:包含所有键值对的 Set<Map.Entry<K, V>> 集合。

Set<Map.Entry<Integer, String>> entries = map.entrySet();  // 返回所有键值对的 Set

1.3 计算方法

这些方法允许在 HashMap 中执行一些常见的计算和修改操作。

1.3.1 putIfAbsent(K key, V value)
  • 作用:如果指定的键不存在于 HashMap 中,则插入键值对;如果该键已存在,则不做任何修改。
  • 返回值:如果该键已存在,返回当前的值;如果键不存在,则返回 null

map.putIfAbsent(3, "Orange");  // 插入键值对 (3, "Orange"),如果键 3 已存在,则不插入

1.3.2 replace(K key, V value)
  • 作用:用指定的值替换 HashMap 中给定键的当前值。如果该键不存在,则返回 null
  • 返回值:如果键存在,返回旧值;否则返回 null

map.replace(2, "Grape");  // 替换键 2 的值为 "Grape"

1.3.3 replace(K key, V oldValue, V newValue)
  • 作用:如果 HashMap 中的键存在且对应值等于 oldValue,则将该键的值替换为 newValue
  • 返回值:如果键值对被替换,返回 true;否则返回 false

boolean replaced = map.replace(2, "Banana", "Grape");  // 返回 true,键 2 的值被替换为 "Grape"

1.3.4 compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
  • 作用:根据指定的 remapping 函数计算并更新键值对。如果键不存在,则插入新的键值对;如果键已存在,则更新该键的值。
  • 返回值:更新后的值;如果映射结果为 null,则删除该键。

map.compute(2, (key, value) -> value + " Fruit");  // 键 2 的值更新为 "Grape Fruit"

1.3.5 computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
  • 作用:如果指定的键不存在,使用 mappingFunction 计算值并插入。如果键已经存在,则不做任何操作。
  • 返回值:计算后的值。

map.computeIfAbsent(4, key -> "Peach");  // 如果键 4 不存在,插入键值对 (4, "Peach")

1.3.6 computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
  • 作用:如果指定的键存在,使用 remappingFunction 更新值。如果键不存在,则不做任何操作。
  • 返回值:更新后的值;如果映射结果为 null,则删除该键。

map.computeIfPresent(2, (key, value) -> value + " Juice");  // 键 2 的值更新为 "Grape Juice"

1.4  并发方法

这些方法是专门设计用于并发环境的。

1.4.1 putAll(Map<? extends K, ? extends V> m)
  • 作用:将指定映射中的所有键值对插入到 HashMap 中。
  • 返回值:无返回值。

Map<Integer, String> anotherMap = new HashMap<>();
anotherMap.put(3, "Peach");
map.putAll(anotherMap);  // 将 anotherMap 中的所有键值对插入到 map 中

1.4.2 forEach(BiConsumer<? super K, ? super V> action)
  • 作用:对 HashMap 中的每个键值对执行指定的操作。
  • 返回值:无返回值。

map.forEach((key, value) -> System.out.println(key + ": " + value));  // 遍历所有键值对

2. 哈希表常见的性能优化方法

1. 合理选择初始容量(Initial Capacity)

  • 问题HashMap 的默认初始容量为 16,而负载因子为 0.75。当 HashMap 中的条目数量超过当前容量的 75% 时,HashMap 会进行扩容。扩容过程中会重新计算每个元素的哈希值并重新分配槽位,这会导致性能下降。
  • 优化方法:根据预计的数据量合理设置初始容量,避免过多的扩容操作。
    • 如果你预计 HashMap 中将存储大量数据,最好在创建 HashMap 时指定一个较大的初始容量。
    • 例如,如果预计存储 1000 个元素,可以设置初始容量为 1024,这样 HashMap 就不需要在插入过程中多次扩容。

HashMap<Integer, String> map = new HashMap<>(1024);  // 设置初始容量为 1024

2. 合理设置负载因子(Load Factor)

  • 问题:负载因子决定了 HashMap 何时进行扩容。负载因子越高,HashMap 扩容的频率就越低,内存使用效率更高;但同时,哈希冲突的可能性也增加,导致查询性能下降。
  • 优化方法:如果内存有限并且插入的元素较多,可以考虑适当降低负载因子,减少扩容的频率;如果哈希冲突较多,适当提高负载因子可以减少扩容的次数。
    • 通常,默认负载因子为 0.75,这是一个折衷值。如果你希望减少扩容频率(并能承受更多的内存使用),可以将其设置为更高的值(如 0.8 或 0.85)。

HashMap<Integer, String> map = new HashMap<>(1024, 0.85f);  // 初始容量为 1024,负载因子为 0.85

3. 优化哈希函数(Hash Function)

  • 问题HashMap 中的每个键值对都通过哈希函数计算一个哈希值来决定存储位置。如果哈希函数不均匀,可能导致哈希冲突(即多个不同的键具有相同的哈希值),从而影响性能。
  • 优化方法
    • 使用 高质量的哈希函数,确保哈希值的均匀分布,减少冲突。Java 的 String 类提供了一个高效的 hashCode() 方法,通常它足够好,但如果你使用自定义的对象作为键,建议重写 hashCode()equals() 方法。
    • 避免简单的哈希函数(如对哈希码进行直接取模运算),它们可能导致很多键的哈希值集中在某些桶中,增加冲突的概率。

@Override
public int hashCode() {
    // 自定义的 hashCode 实现,确保哈希值的均匀分布
    int result = 17;
    result = 31 * result + (key != null ? key.hashCode() : 0);
    result = 31 * result + (value != null ? value.hashCode() : 0);
    return result;
}

4. 避免不必要的扩容(Resizing)

  • 问题HashMap 扩容操作会导致性能下降,尤其是在大数据量插入时。每次扩容时,所有的条目都需要重新计算哈希值并重新分配桶,因此扩容是一个昂贵的操作。
  • 优化方法:通过合理设置初始容量和负载因子,尽量避免频繁的扩容。在使用 HashMap 时,评估存储数据的规模,并设置合适的初始容量和负载因子,以减少扩容的频率。

5. 使用 ConcurrentHashMap 替代 HashMap(多线程环境下)

  • 问题HashMap非线程安全 的,因此在多线程环境下,多个线程同时对其进行操作时,可能导致数据不一致或并发问题。为了保证线程安全,Hashtable 是线程安全的,但它的性能较差。
  • 优化方法:如果在多线程环境中使用 HashMap,应该使用 ConcurrentHashMap,它提供了更高效的线程安全实现,通过分段锁机制(Segmented Locking)大大提高了并发性能。

ConcurrentHashMap<Integer, String> concurrentMap = new ConcurrentHashMap<>();

6. 避免使用大对象作为键(Key)

  • 问题:在 HashMap 中,键通常是通过 hashCode() 方法计算哈希值并进行存储的。如果使用的是非常大的对象作为键,它们的哈希值计算可能会较慢,从而影响 HashMap 的性能。
  • 优化方法:避免使用大型对象或非常复杂的对象作为键。最好使用 String 或简单的原始数据类型(如 Integer, Long)作为键。

7. 合并多个 HashMap(合并操作优化)

  • 问题:在某些情况下,你可能需要将多个 HashMap 合并成一个。直接使用 putAll() 方法会导致多次哈希计算,降低效率。
  • 优化方法:对于大量的 putAll 操作,可以考虑使用 并行流parallelStream())进行合并操作,或者选择性能更高的策略来进行合并。

map1.putAll(map2);  // 合并 map2 到 map1 中

8. 使用不可变键(Immutable Keys)

  • 问题HashMap 的键应该是不可变的,因为键的哈希值是基于对象的 hashCode() 方法计算的。如果键对象是可变的,修改键的属性会导致哈希值改变,从而破坏 HashMap 的内部结构,导致不可预料的错误。
  • 优化方法:确保 HashMap 的键是不可变的,或者确保在键值对插入之后不会修改键对象。

9. 减少 HashMap 的垃圾回收压力

  • 问题HashMap 中存储的对象会被垃圾回收机制回收。当大量对象被存储和删除时,HashMap 可能会频繁触发垃圾回收,导致性能下降。
  • 优化方法:通过定期清理 HashMap,避免存储大量无用数据。同时,可以考虑使用 WeakHashMapSoftHashMap,它们可以在内存不足时自动释放不再使用的条目。

WeakHashMap<Integer, String> weakMap = new WeakHashMap<>();

结语

在这篇文章中,我们深入探讨了 哈希表 作为一种高效的数据结构,特别是其在 Java 中的实现——HashMap。通过对哈希表的基本原理、常用方法、性能优化技巧以及与其他数据结构的对比,我们全面了解了如何高效地使用哈希表,以及如何在实际开发中最大化其优势。

总结要点:

  • 哈希表的优势HashMap 提供了快速的插入、查找和删除操作,平均时间复杂度为 O(1),这使得它在处理大规模数据时非常高效。
  • 常见挑战:哈希表在处理哈希冲突、内存使用、元素顺序不确定性等方面存在一定的挑战,需要开发者在使用时加以注意。
  • 优化策略:合理设置初始容量和负载因子、选择高效的哈希函数、避免频繁扩容以及在并发环境中使用 ConcurrentHashMap,都是提升性能的有效方法。
  • 应用场景:哈希表非常适合用于需要快速查找、频繁插入和删除操作的场景,如缓存实现、数据去重、频繁查询的应用等。

展望未来

随着数据量的不断增加和应用场景的多样化,哈希表的设计和优化将继续面临新的挑战。在实际开发中,开发者不仅要了解哈希表的基本用法,还需要结合具体应用场景来选择最合适的优化策略和数据结构。

最后,无论是基础的 HashMap,还是在并发环境中使用的 ConcurrentHashMap,它们都为 Java 开发者提供了强大的工具支持。通过不断地理解和实践,能够帮助我们在复杂系统中实现更高效的数据处理和存储,提升整体系统的性能。

感谢您的阅读,希望本文能为您的编程实践提供实用的参考和指导!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/943169.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OCR实践-Table-Transformer

前言 书接上文 OCR实践—PaddleOCR Table-Transformer 与 PubTables-1M table-transformer&#xff0c;来自微软&#xff0c;基于Detr&#xff0c;在PubTables1M 数据集上进行训练&#xff0c;模型是在提出数据集同时的工作&#xff0c; paper PubTables-1M: Towards comp…

【Maven】Maven打包机制详解

Maven打包的类型&#xff1f; 以下是几种常见的打包形式&#xff1a; 1、jar (Java Archive) 用途&#xff1a;用于包含 Java 类文件和其他资源&#xff08;如属性文件、配置文件等&#xff09;的库项目。特点&#xff1a; 可以被其他项目作为依赖引用。适合创建独立的应用程…

设备的分配与回收

目录 1、设备分配应考虑的因素 2、静态分配与动态分配 3、设备分配管理中的数据结构 &#xff08;1&#xff09;设备控制表 DCT &#xff08;2&#xff09;控制器控制表COCT &#xff08;3&#xff09;通道控制表CHCT &#xff08;4&#xff09;系统设备表SDT 4、分配过…

清空DNS 缓存

如果遇到修改了host文件&#xff0c;但是IP和域名的映射有问题的情况&#xff0c;可以尝试刷新DNS缓存。 ipconfig/flushdns win建加R建&#xff0c;然后输入cmd&#xff0c;然后回车 然后回车&#xff0c;或者点击确定按钮。 出现如下所示标识清空DNS 缓存成功。

Python使用requests_html库爬取掌阅书籍(附完整源码及使用说明)

教程概述 本教程先是幽络源初步教学分析掌阅书籍的网络结构&#xff0c;最后提供完整的爬取源码与使用说明&#xff0c;并展示结果&#xff0c;切记勿将本教程内容肆意非法使用。 原文链接&#xff1a;Python使用requests_html库爬取掌阅书籍&#xff08;附完整源码及使用说明…

Java爬虫实战:深度解析VIP商品详情获取技术

在数字化时代&#xff0c;数据的价值不言而喻。对于电商平台而言&#xff0c;掌握VIP商品的详细信息是提升服务质量、优化用户体验的关键。然而&#xff0c;这些信息往往被复杂的网页结构和反爬虫策略所保护。本文将带你深入了解如何使用Java编写爬虫&#xff0c;以安全、高效地…

硬件开发笔记(三十二):TPS54331电源设计(五):原理图BOM表导出、元器件封装核对

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/144753092 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

编程初学者使用 MariaDB 数据库反射生成

编程初学者使用 MariaDB 数据库反射生成 数据库反射生成&#xff0c;是动词算子式通用代码生成器提供的高级功能&#xff0c;可以利用已有的数据库&#xff0c;反射生成相应数据库的前端和后端项目。此功能自动化程度很高&#xff0c;并且支持完善的元数据和数据编辑&#xff…

机器人加装电主轴【铣削、钻孔、打磨、去毛刺】更高效

机器人加装电主轴进行铣削、钻孔、打磨、去毛刺等作业&#xff0c;展现出显著的优势&#xff0c;并能实现高效加工。 1. 高精度与高效率 电主轴特点&#xff1a;高速电主轴德国SycoTec的产品&#xff0c;转速可达100000rpm&#xff0c;功率范围广&#xff0c;精度≤1μm&#…

RCCL/NCCL中的Transports方式选择:P2P or SHM or NET

本篇文章主要总结以下在传输路径方式选择的时候&#xff0c;选择每一种方式应该满足的条件和优先度。 本文初步总结&#xff0c;之后还会进行更新&#xff0c;欢迎大家补充 源码位置&#xff1a;tools/topo_expl Topo结构&#xff1a; 初始化判断前 ret设置为0&#xff0c;代…

upload-labs关卡记录11

先上传一个一句话木马试试&#xff0c;居然可以上传成功&#xff0c;复制图片链接&#xff0c;在另一个窗口打开&#xff1a; 会发现&#xff0c;我们明明上传的是shell.php&#xff0c;但是这里就是没有了php,这样我们在执行我们相关的语句的时候就无法执行了&#xff1a; 就…

elementUI——upload限制图片或者文件只能上传一个——公开版

最近在写后台管理系统时&#xff0c;遇到一个需求&#xff0c;就是上传图片&#xff0c;有且仅能上传一张。 效果图如下&#xff1a; 功能描述&#xff1a;上传图片时&#xff0c;仅支持单选&#xff0c;如果上传图片成功后&#xff0c;展示图片&#xff0c;并隐藏添加图片的…

springboot餐厅点餐系统丨源码+数据库+万字文档+PPT

作者简介&#xff1a; 作者&#xff1a;学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”&#xff0c;支持远程部署调试、运行安装。 技术框架 开发语言&#xff1a;Java 框架&#xff1a;springbo…

ArkTs组件(2)

一.下拉列表组件&#xff1a;Select 1.接口 Select(options: Array<SelectOption>) 参数名类型必填说明optionsArray<SelectOption>是设置下拉选项。 SelectOption对象说明 名称类型必填说明valueResourceStr是 下拉选项内容。 iconResourceStr否 下拉选项图片…

【MATLAB第110期】#保姆级教学 | 基于MATLAB的PAWN全局敏感性分析方法(无目标函数)含特征变量置信区间分析

【MATLAB第110期】#保姆级教学 | 基于MATLAB的PAWN全局敏感性分析方法&#xff08;无目标函数&#xff09;含特征变量置信区间分析 一、介绍 PAWN&#xff08;Probabilistic Analysis With Numerical Uncertainties&#xff09;是一种基于密度的全局敏感性分析&#xff08;Gl…

请购单一直提示需求部门不能为空无法提交

终于发现了它的逻辑。用户很多次反馈&#xff0c;提交请购单时&#xff0c;提示需求部门不能为空&#xff0c;既使选择了需求部门&#xff0c;保存时&#xff0c;神奇的是会清空掉部门的信息&#xff0c;提交时就会有错误提示出来。 原因&#xff1a;光选择单头上的需求部门是…

leetcode 面试经典 150 题:矩阵置零

链接矩阵置零题序号73题型二维数组解题方法标记数组法难度中等熟练度✅✅✅✅ 题目 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1]…

AIGC:生成图像动力学

文章目录 前言一、介绍二、方法2.1、运动预测模块运动纹理 2.2、图像渲染模块 三、数据集实验总结 前言 让静态的风景图能够动起来真的很有意思&#xff0c;不得不说CVPR2024 best paper实质名归&#xff0c;创意十足的一篇文章&#xff01;&#xff01;&#xff01; paper&a…

python: Oracle Stored Procedure query table

oracel sql script CREATE OR REPLACE PROCEDURE SelectSchool(paramSchoolId IN char,p_cursor OUT SYS_REFCURSOR ) AS BEGINOPEN p_cursor FORSELECT *FROM SchoolWHERE SchoolId paramSchoolId; END SelectSchool; /-- 查询所有 CREATE OR REPLACE PROCEDURE SelectScho…

社区版Dify 轻松实现文生图,Dify+LLM+ComfyUI

社区版Dify 轻松实文生图&#xff0c;DifyLLMComfyUI Dify 安装可参考这里ComfyUI 其实 比 WebUI更简单更实用DifyComfyUIDifyLLM1. Qwen 通义千问大模型系列2. OpenAI大模型系列3. 本地Ollama搭建 DifyLLMComfyUI Dify 安装可参考这里 这是一个在Dify上实现 文生图的教程&…