电子政务网站建设要求个人网页制作模板三张
news/
2025/9/22 21:48:08/
文章来源:
电子政务网站建设要求,个人网页制作模板三张,用.net做的网站吗,百度惠生活小程序前言
除了基本的数组#xff0c;还有其他高级的数据结构#xff0c;用于更复杂的数据存储和检索需求。其中#xff0c;HashMap 是 Java 集合框架中的一部分#xff0c;用于存储键值对#xff08;key-value pairs#xff09;。HashMap 允许我们通过键来快速查找和检索值还有其他高级的数据结构用于更复杂的数据存储和检索需求。其中HashMap 是 Java 集合框架中的一部分用于存储键值对key-value pairs。HashMap 允许我们通过键来快速查找和检索值类似于字典或关联数组的概念。HashMap 在实际编程中广泛应用于各种场景包括缓存、数据库索引、数据存储等。
HashMap 和 HashTable
HashMap 和 HashTable 都是键值对存储的数据结构它们可以用于快速查找和检索数据。虽然它们在用法上很相似但也存在一些重要的区别 HashMap HashMap 是 Java 集合框架中的一部分它允许空键key和空值value并且是非线程安全的。HashMap 使用了哈希表的数据结构能够在常数时间内查找元素。 HashTable HashTable 也是键值对存储的数据结构但它不允许空键和空值而且是线程安全的。HashTable 使用了类似于 HashMap 的哈希表实现但在多线程环境下性能更好。
示例代码 - 使用 HashMap
让我们看一下如何使用 HashMap 存储和检索数据
// 创建一个 HashMap
HashMapString, Integer scores new HashMap();// 插入键值对
scores.put(Alice, 95);
scores.put(Bob, 88);
scores.put(Charlie, 92);// 检索值
int aliceScore scores.get(Alice); // 获取 Alice 的成绩HashMap底层代码解析
理解 HashMap 内部工作原理的关键部分是研究其核心方法如 resize、treeifyBin 和 putVal。以下是这些方法的简要源码示例和解释
1. resize 方法
resize 方法用于对 HashMap 进行扩容。当键值对数量达到阈值时调用 resize 方法进行扩容通常将容量翻倍。
final NodeK,V[] resize() {NodeK,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})NodeK,V[] newTab (NodeK,V[])new Node[newCap]; // 创建新的桶数组table newTab; // 将桶数组引用指向新的桶数组if (oldTab ! null) { // 如果旧桶数组不为空for (int j 0; j oldCap; j) { // 遍历旧桶数组NodeK,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) // 如果节点是红黑树节点((TreeNodeK,V)e).split(this, newTab, j, oldCap); // 进行拆分else { // 如果是链表NodeK,V loHead null, loTail null;NodeK,V hiHead null, hiTail null;NodeK,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(NodeK,V[] tab, int hash) {int n, index; NodeK,V e;if (tab null || (n tab.length) MIN_TREEIFY_CAPACITY)resize(); // 扩容else if ((e tab[index (n - 1) hash]) ! null) { // 获取当前位置的节点TreeNodeK,V hd null, tl null;do {TreeNodeK,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, dont 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) {NodeK,V[] tab; // 声明桶数组NodeK,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 {NodeK,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 ((TreeNodeK,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 时有一些重要的注意事项和最佳实践以确保正确性和性能。以下是一些关键的注意事项 线程安全性HashMap 不是线程安全的数据结构如果在多线程环境下使用需要考虑采取适当的同步机制或者使用线程安全的替代品如 ConcurrentHashMap。 键的不可变性HashMap 中的键应该是不可变的对象。如果键发生了变化可能导致无法正常获取或删除值。 哈希冲突哈希冲突是指不同的键映射到相同的哈希桶。为了处理冲突HashMap 使用链表或红黑树。为了获得良好的性能尽量避免大量哈希冲突可以考虑使用良好的哈希函数或合适的数据分布。 哈希函数重写如果自定义对象作为键应该确保重写了 hashCode() 和 equals() 方法以确保正确的哈希和相等性比较。 初始化容量和负载因子在创建 HashMap 时可以指定初始容量和负载因子。根据预期的键值对数量选择适当的初始容量可以提高性能。负载因子是用于触发扩容的阈值通常选择合适的默认值即可。 遍历在遍历 HashMap 时尽量不要修改其结构添加或删除键值对否则可能会导致不确定的行为或异常。 Null 键和 Null 值HashMap 允许键和值为 null。但要小心处理 null 键以防止 NullPointerException。 性能考虑HashMap 在查找操作上有很好的性能但在插入和删除操作上可能会有较差的性能特别是在存在大量哈希冲突时。在需要频繁插入和删除的场景中可以考虑使用 LinkedHashMap 或 ConcurrentHashMap。 扩容代价HashMap 在达到负载因子阈值时会自动进行扩容这涉及到重新分配键值对到新的桶数组。频繁的扩容操作可能会影响性能因此应根据应用的需求选择适当的初始容量和负载因子。 equals 和 hashCode 方法如果自定义对象作为键确保正确实现了 equals 和 hashCode 方法以便正确地比较和查找键。
总的来说HashMap 是一个非常有用的数据结构但在使用时需要谨慎考虑上述注意事项以确保其正确性和性能。根据应用的需求还可以考虑使用其他实现了特定场景需求的 Map 接口的实现类如 ConcurrentHashMap、LinkedHashMap 等。
JDK1.7和1.8对比
HashMap 在 JDK 1.7 和 JDK 1.8 中都有存在但在 JDK 1.8 中进行了一些重要的改进。以下是 JDK 1.7 和 JDK 1.8 中 HashMap 的主要区别 数据结构 JDK 1.7JDK 1.7 中的 HashMap 使用数组 链表的数据结构。具体说它使用数组存储桶buckets每个桶存储一个链表。这意味着当多个键映射到同一个桶时它们会在同一个链表上存储。 JDK 1.8JDK 1.8 中的 HashMap 在链表长度达到一定阈值8时将链表转化为红黑树。这一改进在处理大量键值对时提高了查找性能因为红黑树的查找时间复杂度为 O(log n)。 哈希冲突解决 JDK 1.7JDK 1.7 使用链表来解决哈希冲突。当多个键映射到同一个桶时它们会形成一个链表需要遍历链表来查找。 JDK 1.8JDK 1.8 在链表长度达到一定阈值时会将链表转化为红黑树这大大提高了处理长链表的性能。 并发性能 JDK 1.7JDK 1.7 中的 HashMap 不是线程安全的如果多个线程同时操作一个 HashMap可能会导致数据不一致或死锁等问题。为了在多线程环境下使用 HashMap需要自行添加同步机制。 JDK 1.8JDK 1.8 中的 HashMap 在处理并发操作时进行了优化。它引入了更高效的锁机制例如分段锁和 CAS 操作以提高并发性能。此外JDK 1.8 还引入了 ConcurrentHashMap 类专门用于高并发环境。 迭代性能 JDK 1.7JDK 1.7 中的 HashMap 在迭代时性能较差因为即使没有哈希冲突它也需要遍历整个桶数组包括空桶。 JDK 1.8JDK 1.8 中的 HashMap 在迭代时性能得到了提升特别是在没有哈希冲突的情况下。这是由于它使用了更好的数据结构和算法来加速迭代。 空间利用 JDK 1.7JDK 1.7 中的 HashMap 对空间的利用不是很高因为桶的数量必须是 2 的幂次方可能会导致浪费空间。 JDK 1.8JDK 1.8 中的 HashMap 在一定程度上改进了空间利用通过采用树结构来存储哈希冲突的键值对减少了空间浪费。
总的来说JDK 1.8 中的 HashMap 在性能和并发性能上有重大改进特别是在处理大量数据和高并发访问时表现更优越。因此如果使用 Java 8 或更高版本通常建议使用 JDK 1.8 中的 HashMap 实现。但要注意如果需要在多线程环境中使用 HashMap最好考虑使用 ConcurrentHashMap 或其他线程安全的数据结构。
结语:
本文已经提供了有关 HashMap 的深入信息包括其底层代码和注意事项。下一章将继续探讨 HashTable以便能够全面了解这两个重要的数据结构。
如果您有任何疑问、建议或更正请随时都可以留言或私信作者我们将非常乐意为您提供帮助并改进文章。期待在下一章中继续分享有价值的知识。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/910484.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!