当前位置: 首页 > news >正文

Java面试八股文宝典:初识数据结构-数组的应用扩展之HashMap

前言

除了基本的数组,还有其他高级的数据结构,用于更复杂的数据存储和检索需求。其中,HashMap 是 Java 集合框架中的一部分,用于存储键值对(key-value pairs)。HashMap 允许我们通过键来快速查找和检索值,类似于字典或关联数组的概念。HashMap 在实际编程中广泛应用于各种场景,包括缓存、数据库索引、数据存储等。

HashMapHashTable

HashMapHashTable 都是键值对存储的数据结构,它们可以用于快速查找和检索数据。虽然它们在用法上很相似,但也存在一些重要的区别:

  • HashMap HashMap 是 Java 集合框架中的一部分,它允许空键(key)和空值(value),并且是非线程安全的。HashMap 使用了哈希表的数据结构,能够在常数时间内查找元素。

  • HashTable HashTable 也是键值对存储的数据结构,但它不允许空键和空值,而且是线程安全的。HashTable 使用了类似于 HashMap 的哈希表实现,但在多线程环境下性能更好。

示例代码 - 使用 HashMap

让我们看一下如何使用 HashMap 存储和检索数据:

// 创建一个 HashMap
HashMap<String, Integer> scores = new HashMap<>();// 插入键值对
scores.put("Alice", 95);
scores.put("Bob", 88);
scores.put("Charlie", 92);// 检索值
int aliceScore = scores.get("Alice"); // 获取 Alice 的成绩

HashMap底层代码解析

理解 HashMap 内部工作原理的关键部分是研究其核心方法,如 resizetreeifyBinputVal。以下是这些方法的简要源码示例和解释:

1. resize 方法

resize 方法用于对 HashMap 进行扩容。当键值对数量达到阈值时,调用 resize 方法进行扩容,通常将容量翻倍。

final Node<K,V>[] resize() {Node<K,V>[] oldTab = table; // 保存旧的桶数组int oldCap = (oldTab == null) ? 0 : oldTab.length; // 旧容量int oldThr = threshold; // 旧的阈值int newCap, newThr = 0; // 新容量和新阈值if (oldCap > 0) { // 如果旧容量大于0if (oldCap >= MAXIMUM_CAPACITY) { // 如果旧容量已经达到最大值threshold = Integer.MAX_VALUE; // 阈值设置为最大值,禁止再次扩容return oldTab; // 返回旧的桶数组} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // 扩容为原容量的两倍,并更新新阈值} else if (oldThr > 0) // 如果旧的阈值大于0newCap = oldThr; // 使用阈值作为新容量else { // 如果没有指定容量和阈值,使用默认值newCap = DEFAULT_INITIAL_CAPACITY; // 默认初始容量newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 默认阈值}if (newThr == 0) { // 如果新阈值为0float ft = (float)newCap * loadFactor; // 计算新阈值newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE); // 如果未超过最大容量,使用计算值,否则使用最大值}threshold = newThr; // 更新阈值@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // 创建新的桶数组table = newTab; // 将桶数组引用指向新的桶数组if (oldTab != null) { // 如果旧桶数组不为空for (int j = 0; j < oldCap; ++j) { // 遍历旧桶数组Node<K,V> e;if ((e = oldTab[j]) != null) { // 如果当前位置有节点oldTab[j] = null; // 清空旧桶位置if (e.next == null)newTab[e.hash & (newCap - 1)] = e; // 如果只有一个节点,直接放入新桶位置else if (e instanceof TreeNode) // 如果节点是红黑树节点((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 进行拆分else { // 如果是链表Node<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) { // 如果哈希码对旧容量求与为0if (loTail == null)loHead = e; // 放入低位链表头部elseloTail.next = e; // 放入低位链表尾部loTail = e; // 更新低位链表尾节点}else { // 否则放入高位链表if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead; // 更新旧桶位置为低位链表}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead; // 更新旧桶位置为高位链表}}}}}return newTab; // 返回新的桶数组
}
  • resize 方法首先计算新的容量和阈值。
  • 如果旧表(oldTab)不为空,它将遍历旧表的桶,根据哈希值重新分配节点到新表(newTab)中。
  • 如果节点的数量超过一定阈值,它可能会将链表转化为红黑树,以提高性能。

2. treeifyBin 方法

treeifyBin 方法用于将链表转换为红黑树,以提高查找性能。该方法通常在链表长度超过8时被调用。

final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize(); // 扩容else if ((e = tab[index = (n - 1) & hash]) != null) { // 获取当前位置的节点TreeNode<K,V> hd = null, tl = null;do {TreeNode<K,V> p = replacementTreeNode(e, null); // 创建红黑树节点if (tl == null)hd = p; // 如果链表为空,将红黑树节点设置为头节点else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);if ((tab[index] = hd) != null) // 如果头节点不为空,将链表转化为红黑树hd.treeify(tab);}
}
  • treeifyBin 方法首先检查表的容量,如果小于 MIN_TREEIFY_CAPACITY,则会调用 resize 方法进行扩容。
  • 如果表的容量足够大,它将遍历桶中的链表,将每个节点替换为红黑树节点,并构建红黑树。

3. pubVal 方法

putVal 方法是 HashMap 中用于向哈希表中添加键值对的核心方法。

/*** Associates the specified value with the specified key in this map.* If the map previously contained a mapping for the key, the old* value is replaced.** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {Node<K,V>[] tab; // 声明桶数组Node<K,V> p; // 声明当前节点int n, i; // n为桶数组的长度,i为计算出的槽位索引// 如果桶数组为空或长度为0,需要初始化//判断tab是不是为空,如果为空,则将容量进行初始化,也就是说,初始换操作不是在new HashMap()的时候进行的,而是在第一次put的时候进行的if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 计算槽位索引:初始化操作以后,根据当前key的哈希值算出最终命中到哪个桶上去,并且这个桶上如果没有元素的话,则直接new一个新节点放进去if ((p = tab[i = (n - 1) & hash]) == null) // 如果当前槽位为空tab[i] = newNode(hash, key, value, null); // 直接创建新节点并放入槽位else {Node<K,V> e; K k;// 先判断一下这个桶里的第一个Node元素的key是不是和即将要存的key值相同,如果相同,则把当前桶里第一个Node元素赋值给e,这个else的最下边进行了判断,如果e!=null就执行把新value进行替换的操作if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p; // 如果找到相同键的节点,将e指向该节点else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {// 如果遍历到链表尾部仍未找到相同键的节点,将新节点添加到链表尾部if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 如果链表长度达到阈值,将链表转化为红黑树if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break; // 如果找到相同键的节点,退出循环p = e;}}/*** 只要e不为空,说明要插入的key已经存在了,覆盖旧的value值,然后返回原来oldValue* 因为只是替换了旧的value值,并没有插入新的元素,所以不需要下边的扩容判断,直接* return掉*/if (e != null) { // 如果找到相同键的节点V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value; // 替换值afterNodeAccess(e);return oldValue; // 返回旧值}}++modCount;/*** 判断容量是否已经到了需要扩充的阈值了,如果到了,则进行扩充* 如果上一步已经判断key是存在的,只是替换了value值,并没有插入新的元素,所以不需要判断* 扩容,不会走这一步的*/if (++size > threshold)resize(); // 如果超过阈值,进行扩容afterNodeInsertion(evict);return null; // 返回null表示插入成功
}

putVal 方法用于将键值对放入HashMap中。此方法涵盖了查找、替换、插入和扩容等关键操作,是HashMap内部工作的核心部分。

注意事项

在使用 HashMap 时,有一些重要的注意事项和最佳实践,以确保正确性和性能。以下是一些关键的注意事项:

  1. 线程安全性HashMap 不是线程安全的数据结构,如果在多线程环境下使用,需要考虑采取适当的同步机制,或者使用线程安全的替代品,如 ConcurrentHashMap

  2. 键的不可变性HashMap 中的键应该是不可变的对象。如果键发生了变化,可能导致无法正常获取或删除值。

  3. 哈希冲突:哈希冲突是指不同的键映射到相同的哈希桶。为了处理冲突,HashMap 使用链表或红黑树。为了获得良好的性能,尽量避免大量哈希冲突,可以考虑使用良好的哈希函数或合适的数据分布。

  4. 哈希函数重写:如果自定义对象作为键,应该确保重写了 hashCode()equals() 方法,以确保正确的哈希和相等性比较。

  5. 初始化容量和负载因子:在创建 HashMap 时,可以指定初始容量和负载因子。根据预期的键值对数量,选择适当的初始容量可以提高性能。负载因子是用于触发扩容的阈值,通常选择合适的默认值即可。

  6. 遍历:在遍历 HashMap 时,尽量不要修改其结构(添加或删除键值对),否则可能会导致不确定的行为或异常。

  7. Null 键和 Null 值HashMap 允许键和值为 null。但要小心处理 null 键,以防止 NullPointerException

  8. 性能考虑HashMap 在查找操作上有很好的性能,但在插入和删除操作上可能会有较差的性能,特别是在存在大量哈希冲突时。在需要频繁插入和删除的场景中,可以考虑使用 LinkedHashMapConcurrentHashMap

  9. 扩容代价HashMap 在达到负载因子阈值时会自动进行扩容,这涉及到重新分配键值对到新的桶数组。频繁的扩容操作可能会影响性能,因此应根据应用的需求选择适当的初始容量和负载因子。

  10. equals 和 hashCode 方法:如果自定义对象作为键,确保正确实现了 equalshashCode 方法,以便正确地比较和查找键。

总的来说,HashMap 是一个非常有用的数据结构,但在使用时需要谨慎考虑上述注意事项,以确保其正确性和性能。根据应用的需求,还可以考虑使用其他实现了特定场景需求的 Map 接口的实现类,如 ConcurrentHashMapLinkedHashMap 等。

JDK1.7和1.8对比

HashMap 在 JDK 1.7 和 JDK 1.8 中都有存在,但在 JDK 1.8 中进行了一些重要的改进。以下是 JDK 1.7 和 JDK 1.8 中 HashMap 的主要区别:

  1. 数据结构

    • JDK 1.7:JDK 1.7 中的 HashMap 使用数组 + 链表的数据结构。具体说,它使用数组存储桶(buckets),每个桶存储一个链表。这意味着当多个键映射到同一个桶时,它们会在同一个链表上存储。

    • JDK 1.8:JDK 1.8 中的 HashMap 在链表长度达到一定阈值(8)时,将链表转化为红黑树。这一改进在处理大量键值对时提高了查找性能,因为红黑树的查找时间复杂度为 O(log n)。
      在这里插入图片描述

  2. 哈希冲突解决

    • JDK 1.7:JDK 1.7 使用链表来解决哈希冲突。当多个键映射到同一个桶时,它们会形成一个链表,需要遍历链表来查找。

    • JDK 1.8:JDK 1.8 在链表长度达到一定阈值时,会将链表转化为红黑树,这大大提高了处理长链表的性能。

  3. 并发性能

    • JDK 1.7:JDK 1.7 中的 HashMap 不是线程安全的,如果多个线程同时操作一个 HashMap,可能会导致数据不一致或死锁等问题。为了在多线程环境下使用 HashMap,需要自行添加同步机制。

    • JDK 1.8:JDK 1.8 中的 HashMap 在处理并发操作时进行了优化。它引入了更高效的锁机制,例如分段锁和 CAS 操作,以提高并发性能。此外,JDK 1.8 还引入了 ConcurrentHashMap 类,专门用于高并发环境。

  4. 迭代性能

    • JDK 1.7:JDK 1.7 中的 HashMap 在迭代时性能较差,因为即使没有哈希冲突,它也需要遍历整个桶数组,包括空桶。

    • JDK 1.8:JDK 1.8 中的 HashMap 在迭代时性能得到了提升,特别是在没有哈希冲突的情况下。这是由于它使用了更好的数据结构和算法来加速迭代。

  5. 空间利用

    • JDK 1.7:JDK 1.7 中的 HashMap 对空间的利用不是很高,因为桶的数量必须是 2 的幂次方,可能会导致浪费空间。

    • JDK 1.8:JDK 1.8 中的 HashMap 在一定程度上改进了空间利用,通过采用树结构来存储哈希冲突的键值对,减少了空间浪费。
      在这里插入图片描述

总的来说,JDK 1.8 中的 HashMap 在性能和并发性能上有重大改进,特别是在处理大量数据和高并发访问时表现更优越。因此,如果使用 Java 8 或更高版本,通常建议使用 JDK 1.8 中的 HashMap 实现。但要注意,如果需要在多线程环境中使用 HashMap,最好考虑使用 ConcurrentHashMap 或其他线程安全的数据结构。

结语:

本文已经提供了有关 HashMap 的深入信息,包括其底层代码和注意事项。下一章将继续探讨 HashTable,以便能够全面了解这两个重要的数据结构。

如果您有任何疑问、建议或更正,请随时都可以留言或私信作者,我们将非常乐意为您提供帮助并改进文章。期待在下一章中继续分享有价值的知识。

相关文章:

Java面试八股文宝典:初识数据结构-数组的应用扩展之HashMap

前言 除了基本的数组&#xff0c;还有其他高级的数据结构&#xff0c;用于更复杂的数据存储和检索需求。其中&#xff0c;HashMap 是 Java 集合框架中的一部分&#xff0c;用于存储键值对&#xff08;key-value pairs&#xff09;。HashMap 允许我们通过键来快速查找和检索值&…...

ES6 特性

一、ES6 1.1 ES6 概念 1.1.1 什么是 ES ES 全称 EcmaScript 是脚本语言的规范JavaScript 是 EcmaScript 的一种实现ES 新特性就是指 JavaScript 的新特性 1.1.2 为什么要使用 ES 语法简单&#xff0c;功能丰富框架开发应用前端开发职位要求 1.1.3 为什么要学习 ES6 ES6 …...

重拾html5

新增的position: sticky; 基于用户的滚动位置来定位&#xff0c;粘性定位的元素是依赖于用户的滚动&#xff0c;在 position:relative 与 position:fixed 定位之间切换。ie15以上的低版本不支持&#xff0c;Safari 需要使用 -webkit- prefix&#xff1b; vertical-align: midd…...

递归学习——记忆化搜索

目录 ​编辑 一&#xff0c;概念和效果 二&#xff0c;题目 1.斐波那契数 1.题目 2.题目接口 3.解题思路 2.不同的路径 1.题目 2.题目接口 3.解题思路 3.最长增长子序列 1.题目 2.题目接口 3.解题思路 4.猜数字游戏II 1.题目 2.题目接口 3.解题思路 总结&a…...

ChatGPT帮助一名儿童确诊病因,之前17位医生无法确诊

9月13日&#xff0c;Today消息&#xff0c;一位名叫Alex的4岁儿童得了一种浑身疼痛的怪病&#xff0c;每天需要服用Motrin&#xff08;美林&#xff09;才能止痛。3年的时间&#xff0c;看了17名医生无法确诊病因。&#xff08;新闻地址&#xff1a;https://www.today.com/heal…...

Laf 云开发平台及其实现原理

Laf 产品介绍 自我介绍 大家好&#xff0c;我是来自 Laf 团队的王子俊&#xff0c;很高兴今天能在这里给大家分享我们 Laf 云开发平台及其实现原理。本来想说一点什么天气之类的话作为开头&#xff0c;但主持人都说完啦&#xff0c;我就不多说了&#xff0c;还是直接开始今天…...

浅谈STL|STL函数对象篇

一.函数对象概念 概念: 重载函数调用操作符的类&#xff0c;其对象常称为函数对象 函数对象使用重载的()时&#xff0c;行为类似函数调用&#xff0c;也叫仿函数 本质: 函数对象(仿函数)是一个类&#xff0c;不是一个函数 特点 函数对象在使用时&#xff0c;可以像普通函数那…...

自建私人图床方案:使用Cpolar+树洞外链轻松部署超轻量级图床,实现高效图片存储

文章目录 1.前言2. 树洞外链网站搭建2.1. 树洞外链下载和安装2.2 树洞外链网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3 Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测试5.结语…...

从零基础到精通Flutter开发:一步步打造跨平台应用

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 导言 Flutter是一种流行…...

SpringBoot整合WebSocket【代码】

系列文章目录 一、SpringBoot连接MySQL数据库实例【tk.mybatis连接mysql数据库】 二、SpringBoot连接Redis与Redisson【代码】 三、SpringBoot整合WebSocket【代码】 四、SpringBoot整合ElasticEearch【代码示例】 文章目录 系列文章目录代码下载地址一、效果演示二、引入依赖…...

微服务 第一章 Java线程池技术应用

系列文章目录 第一章 Java线程池技术应用 文章目录 系列文章目录[TOC](文章目录) 前言1、Java创建线程方式回顾1.1、继承Thread类(只运行一次)1.1.1、改造成主线程常驻&#xff0c;每秒开启新线程运行1.1.2、匿名内部类1.1.3、缺点1.1.4、扩展知识&#xff1a;Java内部类1.1.4…...

行业追踪,2023-09-14

自动复盘 2023-09-14 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…...

传输层协议--UDP

引入 传输层负责数据能够从发送端传输到接收端。 端口号&#xff08;Port&#xff09; 端口号标识了一个主机上进行通信的一个进程。 两个问题&#xff1a; 1. 一个进程可以绑定多个端口号吗&#xff1f;--可以 2.一个端口号可以绑定多个进程吗&#xff1f;--不可以 我们…...

微信会员卡开发流程

功能需求&#xff1a; 通过微信第三方平台创建的模板小程序&#xff0c;想要实现用户在小程序支付一定金额后领取会员卡&#xff0c;领取会员卡后可给用户下发一定数量的优惠券&#xff0c;并且实现用户在小程序消费享受商品折扣。 开发流程&#xff1a; 一、了解微信的3个平…...

《算法竞赛·快冲300题》每日一题:“点灯游戏”

《算法竞赛快冲300题》将于2024年出版&#xff0c;是《算法竞赛》的辅助练习册。 所有题目放在自建的OJ New Online Judge。 用C/C、Java、Python三种语言给出代码&#xff0c;以中低档题为主&#xff0c;适合入门、进阶。 文章目录 题目描述题解C代码Java代码Python代码 “ 点…...

常见高级语言的输入与输出训练(一)

文章目录 题目概述1 输入描述: 输出描述: 输入 输出 示例C语言代码 题目概述2 题目描述 输入描述: 输出描述: 输入 输出 示例Java代码 前言 本文主要讲解两个算法题的代码实现 题目概述1 计算ab 打开以下链接可以查看正确的代码 数据范围&#xff1a;数据组数满…...

恭喜!龙蜥获得 2023 大学生操作系统设计赛二等奖及特殊贡献奖

经过多月的激烈角逐&#xff0c;2023 全国大学生系统能力大赛操作系统设计赛&#xff08;以下简称“2023 大学生操作系统赛”&#xff09; 圆满结束。经过 2023 大学生操作系统赛评审组和技术委员会的复核&#xff0c;面向全国公布了此次大赛的获奖名单。龙蜥社区不负众望&…...

中项系统集成项目管理2023上半年真题及解析

中项系统集成项目管理2023上半年真题及解析 上午题1. 在 (1) 领域&#xff0c;我国还远未达到世界先进水平&#xff0c;需要发挥新型举国体制优势&#xff0c;集中政府和市场两方面的力量全力发展2. ChatGPT于 2022年 11 月 30 日发布&#xff0c;它是人工智能驱动的 (2) 工具3…...

实时显示当前文件夹下的文件大小,shell脚本实现

图片来源于网络&#xff0c;如果侵权请联系博主删除&#xff01; 需求&#xff1a; 写一个shell终端命令&#xff0c;实时显示当前文件夹下的文件大小 实现&#xff1a; 您可以使用以下的Shell脚本命令来实时显示当前文件夹下的文件大小&#xff1a; while true; docleardu …...

7、Spring之依赖注入源码解析(下)

resolveDependency()实现 该方法表示,传入一个依赖描述(DependencyDescriptor),该方法会根据该依赖描述从BeanFactory中找出对应的唯一的一个Bean对象。 @Nullable Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Null…...

RMBG-2.0在远程办公中的应用:Zoom虚拟背景实时抠像插件开发指南

RMBG-2.0在远程办公中的应用&#xff1a;Zoom虚拟背景实时抠像插件开发指南 远程办公已经成为许多人的日常&#xff0c;视频会议更是其中的核心环节。你是否厌倦了千篇一律的虚拟背景图片&#xff1f;或者因为摄像头背景杂乱而不敢开启视频&#xff1f;今天&#xff0c;我们将…...

Python实战:出租车计费模拟器开发(附完整代码与测试用例)

Python实战&#xff1a;出租车计费模拟器开发&#xff08;附完整代码与测试用例&#xff09; 出租车计费系统是城市交通中不可或缺的一部分&#xff0c;而用Python模拟这一过程不仅能帮助初学者理解条件分支和输入输出处理&#xff0c;还能培养将现实问题转化为代码的思维能力。…...

基于LSTM时间序列预测思想优化百川2-13B的对话连贯性

基于LSTM时间序列预测思想优化百川2-13B的对话连贯性 你有没有遇到过这种情况&#xff1f;和一个大模型聊得正起劲&#xff0c;聊了十几轮甚至几十轮之后&#xff0c;你突然发现&#xff0c;它好像“失忆”了。你之前明明告诉过它你的名字、你的职业&#xff0c;甚至你们刚刚讨…...

图像标注难题如何破解?LabelImg工具全面解析与实战指南

图像标注难题如何破解&#xff1f;LabelImg工具全面解析与实战指南 【免费下载链接】labelImg LabelImg is now part of the Label Studio community. The popular image annotation tool created by Tzutalin is no longer actively being developed, but you can check out L…...

赣州琴行哪家最可靠

在赣州&#xff0c;选择一家可靠的琴行对于孩子的钢琴启蒙和成长至关重要。今天我们就来聊聊赣州的几家知名琴行&#xff0c;看看哪家最适合您的孩子。1. 可六琴行&#xff1a;专注儿童钢琴启蒙&#xff0c;天天练琴模式为什么选择可六琴行&#xff1f;1.1 专注儿童钢琴启蒙具体…...

惯性导航系统深度解析:从平台式到捷联式的技术演进与精度优化

1. 惯性导航系统的基本原理 想象一下你被蒙上眼睛放在一个陌生的城市里&#xff0c;只给你一个计步器和指南针&#xff0c;要求你记录自己的行走路线。这就是惯性导航系统&#xff08;INS&#xff09;工作的基本场景——它通过测量运动载体的加速度和角速度&#xff0c;像做数…...

Legacy iOS Kit:让旧款iOS设备重获新生的全方位解决方案

Legacy iOS Kit&#xff1a;让旧款iOS设备重获新生的全方位解决方案 【免费下载链接】Legacy-iOS-Kit An all-in-one tool to downgrade/restore, save SHSH blobs, and jailbreak legacy iOS devices 项目地址: https://gitcode.com/gh_mirrors/le/Legacy-iOS-Kit 旧设…...

SiameseUIE详细步骤:cd .. + cd nlp_structbert_siamese-uie_chinese-base执行逻辑

SiameseUIE详细步骤&#xff1a;cd .. cd nlp_structbert_siamese-uie_chinese-base执行逻辑 1. 为什么需要这个执行顺序 当你拿到一个已经部署好的AI模型镜像&#xff0c;第一件事就是找到正确的打开方式。cd .. 和 cd nlp_structbert_siamese-uie_chinese-base 这个看似简…...

老Mac焕发新生:突破硬件限制的macOS升级全攻略

老Mac焕发新生&#xff1a;突破硬件限制的macOS升级全攻略 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 当你的Mac提示"无法更新到最新系统"&#xff0c;当常…...

告别Keil5新建工程手忙脚乱:GD32F303保姆级环境搭建与文件管理心法

告别Keil5新建工程手忙脚乱&#xff1a;GD32F303保姆级环境搭建与文件管理心法 第一次打开Keil5新建GD32工程时&#xff0c;面对官网下载的几十个库文件&#xff0c;你是否感到无从下手&#xff1f;明明跟着教程一步步操作&#xff0c;最后却发现工程文件散落各处&#xff0c;移…...