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

佛山三水区有没有网站建设公司手机软件开发培训班

佛山三水区有没有网站建设公司,手机软件开发培训班,网站开发实用技术电子版,网站备案升级文章目录 一、 源码深度解析1.1 窥探Java集合框架中的设计思想1.2 逐行解读HashMap的源代码1.2.1 类信息1.2.2 常量属性1.2.3 变量属性1.2.4 节点信息1.2.5 构造方法1.2.6 put方法1.2.6.1 putVal方法1.2.6.2 putTreeVal方法1.2.6.3 tieBreakOrder方法1.2.6.4 treeifyBin方法1.2… 文章目录 一、 源码深度解析1.1 窥探Java集合框架中的设计思想1.2 逐行解读HashMap的源代码1.2.1 类信息1.2.2 常量属性1.2.3 变量属性1.2.4 节点信息1.2.5 构造方法1.2.6 put方法1.2.6.1 putVal方法1.2.6.2 putTreeVal方法1.2.6.3 tieBreakOrder方法1.2.6.4 treeifyBin方法1.2.6.5 treeify方法 1.2.7 get方法1.2.8 remove方法1.2.9 resize方法 二、 应用与最佳实践2.1 在实际项目中如何合理使用HashMap2.2 最佳实践和注意事项 三、 结论3.1 对HashMap的全面总结3.2 鼓励读者深入学习和实践 一、 源码深度解析 1.1 窥探Java集合框架中的设计思想 Java集合框架是Java编程中非常重要的一部分提供了各种数据结构和算法使得开发者能够高效地组织和操作数据。 Java集合框架的设计思想主要包括以下几个方面 通用性Generality Java集合框架被设计成通用的、可重用的组件。这样一来同一套集合框架可以用于存储和处理各种类型的对象。 可扩展性Scalability 集合框架提供了一系列接口和类允许开发者实现自定义的集合类型。这种可扩展性使得开发者能够根据具体的需求创建新的集合类。 高性能Performance 集合框架在设计时考虑了性能的因素通过选择合适的数据结构和算法来提高操作的效率。例如ArrayList和LinkedList都是List接口的实现但它们在插入和删除操作上有不同的性能表现。 互操作性Interoperability 集合框架中的各个接口和类都被设计成可以互相操作的。这种互操作性使得不同的集合类之间能够轻松地进行转换和使用。 可读性和简洁性Readability and Simplicity 集合框架的设计追求代码的可读性和简洁性。通过提供一组清晰的接口和方法开发者可以更容易地理解和使用集合框架。 线程安全性Thread Safety 部分集合类如Hashtable、Vector被设计成线程安全的适用于多线程环境。此外Java提供了一些在多线程环境中使用时更安全的并发集合类如ConcurrentHashMap。 Fail-Fast机制快速失败机制 集合框架在迭代过程中使用了快速失败机制当多个线程对同一集合进行修改时可能会抛出ConcurrentModificationException以提醒开发者在迭代过程中不要修改集合。 总体来说Java集合框架的设计思想注重通用性、性能、可扩展性和简洁性为开发者提供了丰富而强大的工具用于处理各种不同类型的数据。 1.2 逐行解读HashMap的源代码 1.2.1 类信息 1.2.2 常量属性 1.2.3 变量属性 1.2.4 节点信息 1.2.5 构造方法 1、构造一个具有默认初始容量16和默认加载因子0.75的空HashMap。 2、构造一个带指定初始容量和默认加载因子0.75的空HashMap。 3、构造一个带指定初始容量和加载因子的空HashMap。 4、构造一个映射关系与指定Map相同的新HashMap。 5、扩展LRU实现方式与思路。 在实现一个基于LRU策略的缓存时通常会使用一个数据结构来存储缓存中的数据并且需要记录数据的访问顺序。常见的数据结构是双向链表和哈希表的结合。 基于双向链表和哈希表的实现方式 使用双向链表双向链表可以记录数据的访问顺序当某个数据被访问时可以将其移动到链表的头部或尾部。头部表示最近访问的数据尾部表示最久未被访问的数据。使用哈希表哈希表用于快速查找缓存中的数据可以将数据的键key映射到对应的链表节点以实现快速的查找和插入操作。 实现LRU缓存的基本思路如下 当需要访问缓存中的数据时首先在哈希表中查找该数据是否存在。如果存在则将该数据移动到链表的头部表示最近被访问过。如果不存在需要从后端的存储中加载数据并插入到链表的头部同时更新哈希表。当缓存已满时需要淘汰链表尾部的数据同时更新哈希表。 通过这种方式实现了一个基于LRU淘汰策略的缓存系统可以确保最近最少被使用的数据会被及时地淘汰从而保持缓存中的数据是最有用的。 1.2.6 put方法 由于put方法代码量偏多故使用代码注释形式解读源码。 1.2.6.1 putVal方法 /*** HashMap的put操作源码注释*/ public V put(K key, V value) {// 先根据hash()方法计算存放元素在table数组中的下标return putVal(hash(key), key, value, false, true); }// hash方法 static final int hash(Object key) {int h;// 如果key为null返回hash0// 如果key不为null那么进行key的hashCode值的高低位异或运算返回结果作为hash值return (key null) ? 0 : (h key.hashCode()) ^ (h 16); }// putVal方法 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {NodeK,V[] tab; NodeK,V p; int n, i;// 如果第一次插入table为空或者length等于0if ((tab table) null || (n tab.length) 0)// 调用resize方法进行初始化n (tab resize()).length;// 通过hash值计算索引位置将该索引位置的头节点赋值给p且p为空// 实则进行取余操作 ptab[hash%length]if ((p tab[i (n - 1) hash]) null)// 在该索引位置新增一个节点tab[i] newNode(hash, key, value, null);else {// table表该索引位置不为空则会发生hash冲突NodeK,V e; K k;// 当p节点的hash值和key跟传入的相等if (p.hash hash ((k p.key) key || (key ! null key.equals(k))))// p节点即为要查找的目标节点将p节点赋值给e节点e p;// 当p节点为TreeNodeelse if (p instanceof TreeNode)// 调用红黑树的putTreeVal方法查找目标节点e ((TreeNodeK,V)p).putTreeVal(this, tab, hash, key, value);else {// 判断完则p节点为普通链表节点//遍历节点操作使用binCount统计链表的节点数for (int binCount 0; ; binCount) {// p的next节点为空时代表找不到目标节点if ((e p.next) null) {// 新增一个节点并插入链表尾部p.next newNode(hash, key, value, null);// 当binCount节点数超过8个// TREEIFY_THRESHOLD - 1由于循环是从p节点的下一个节点开始的if (binCount TREEIFY_THRESHOLD - 1)// 调用treeifyBin方法将链表节点转为红黑树节点treeifyBin(tab, hash);break;}// e节点的hash值和key值与传入的相同if (e.hash hash ((k e.key) key || (key ! null key.equals(k))))// e节点即为目标节点跳出循环break;// 将p指向下一个节点继续往后遍历p e; }}// e节点不为空代表目标节点存在if (e ! null) {// 使用传入的value覆盖该节点的value即保留原来元素的valueV oldValue e.value;// 当oldValue为空if (!onlyIfAbsent || oldValue null)// 替换valuee.value value;// 用于LinkedHashMapafterNodeAccess(e);// 返回一个原有的valuereturn oldValue;}}// 添加操作执行后对modCount、size做加一操作modCount;// 插入节点后节点数超过阈值if (size threshold)// 调用resize方法进行扩容resize();// 用于LinkedHashMapafterNodeInsertion(evict); return null; }1.2.6.2 putTreeVal方法 /*** 当阈值达到触发红黑树的put的源码注释*/ final TreeNodeK,V putTreeVal(HashMapK,V map, NodeK,V[] tab,int h, K k, V v) {Class? kc null;boolean searched false;// TreeNodeK,V继承于LinkedHashMap.EntryK,V// parent节点不等于空则查找根节点// 即得出索引位置的头节点并不一定为红黑树的根节点TreeNodeK,V root (parent ! null) ? root() : this;// 将根节点赋值给p节点开始进行查找for (TreeNodeK,V p root;;) {int dir, ph; K pk;// 当传入的hash值小于p节点的hash值if ((ph p.hash) h)// 将dir赋值为-1代表向p的左边查找树dir -1;// 否则传入的hash值大于p节点的hash值else if (ph h)// 将dir赋值为1代表向p的右边查找树dir 1;// 当传入的key值等于p节点的key值else if ((pk p.key) k || (k ! null k.equals(pk)))// 即p节点为目标节点, 返回p节点return p;// 当k所属的类没有实现Comparable接口 或 k和p节点的key相等else if ((kc null (kc comparableClassFor(k)) null) ||(dir compareComparables(kc, k, pk)) 0) {// 由于局部变量初始为false即当第一次判断是符合条件的if (!searched) {TreeNodeK,V q, ch;// 查找过后标记为truesearched true;// 从p节点的左节点和右节点分别调用find方法进行查找if (((ch p.left) ! null (q ch.find(h, k, kc)) ! null) ||((ch p.right) ! null (q ch.find(h, k, kc)) ! null))// 查找到目标节点则返回return q;}// 否则使用定义的一套规则来比较k和p节点的key的大小, 用来决定向左还是向右查找// dir0则代表kpk则向p左边查找反之亦然dir tieBreakOrder(k, pk); }// xp赋值为x的父节点,中间变量,用于下面给x的父节点赋值TreeNodeK,V xp p; // dir0则向p左边查找,否则向p右边查找,如果为nullif ((p (dir 0) ? p.left : p.right) null) {// xp的next节点NodeK,V xpn xp.next; // 创建新的节点, 其中x的next节点为xpn, 即将x节点插入xp与xpn之间TreeNodeK,V x map.newTreeNode(h, k, v, xpn);// 调整x、xp、xpn之间的属性关系// 如果dir 0if (dir 0) // 代表x节点为xp的左节点xp.left x;else // 如果dir 0, 则代表x节点为xp的右节点xp.right x;// 将xp的next节点设置为xxp.next x; // 将x的parent和prev节点设置为xpx.parent x.prev xp; // 当xpn不为空if (xpn ! null)// 将xpn的prev节点设置为x节点,与上文的x节点的next节点对应((TreeNodeK,V)xpn).prev x;// 进行红黑树的插入平衡调整moveRootToFront(tab, balanceInsertion(root, x));return null;}} }1.2.6.3 tieBreakOrder方法 /*** 使用定义的一套规则来比较k和p节点的key的大小*/ static int tieBreakOrder(Object a, Object b) { int d;if (a null || b null ||(d a.getClass().getName().compareTo(b.getClass().getName())) 0)// a b为-1a b为1d (System.identityHashCode(a) System.identityHashCode(b) ?-1 : 1);return d; }1.2.6.4 treeifyBin方法 /*** 将链表转为红黑树*/ final void treeifyBin(NodeK,V[] tab, int hash) {int n, index; NodeK,V e;// 当tab为空或者tab的长度小于64if (tab null || (n tab.length) MIN_TREEIFY_CAPACITY)// 进行扩容resize();// 根据hash值计算索引值将该索引位置的节点赋值给e且e不为nullelse if ((e tab[index (n - 1) hash]) ! null) {TreeNodeK,V hd null, tl null;// 从e开始遍历该索引位置的链表do {// 将链表节点转红黑树节点TreeNodeK,V p replacementTreeNode(e, null);// tl为空代表为第一次循环if (tl null) // 将头节点赋值给hdhd p;else {// 如果不是第一次遍历// 当前节点的prev属性设为上一个节点p.prev tl; // 上一个节点的next属性设置为当前节点tl.next p; }// 将p节点赋值给tl用于在下一次循环中作为上一个节点进行一些链表的关联操作p.prev tl 和 tl.next ptl p;} while ((e e.next) ! null);// 将tab该索引位置赋值为新的TreeNode的头节点如果该节点不为空if ((tab[index] hd) ! null)// 以头节点(hd)为根节点, 构建红黑树hd.treeify(tab);} }// 将链表节点转红黑树节点 TreeNodeK,V replacementTreeNode(NodeK,V p, NodeK,V next) {return new TreeNode(p.hash, p.key, p.value, next); }1.2.6.5 treeify方法 /*** 将哈希表中的链表结构转换为树形结构以提高查找效率*/ final void treeify(NodeK,V[] tab) {TreeNodeK,V root null;// 开始一个循环初始化变量x为当前节点然后在每次迭代中将x更新为nextfor (TreeNodeK,V x this, next; x ! null; x next) {// next赋值为x的下个节点next (TreeNodeK,V)x.next; // 将x的左右节点设置为空x.left x.right null; // 如果还没有根节点if (root null) {// 根节点没有父节点设为nullx.parent null; // 根节点必须为黑色,false为黑色x.red false; // 在将x设置为根节点root x; }// 如果有根节点else {// k赋值为x的keyK k x.key;// h赋值为x的hash值int h x.hash; // 定义一个类型未知的变量kcClass? kc null;// 开始一个无限循环初始化变量p为root节点表示对树进行遍历for (TreeNodeK,V p root;;) {int dir, ph;K pk p.key;// 比较当前节点x的哈希值h与节点p的哈希值ph根据比较结果给变量dir赋值// 如果x节点的hash值小于p节点的hash值则将dir赋值为-1, 代表向p的左边查找if ((ph p.hash) h)dir -1;// 如果x节点的hash值大于p节点的hash值则将dir赋值为1, 代表向p的右边查找else if (ph h)dir 1;// x的hash值和p的hash值相等则比较key值// 如果k没有实现Comparable接口 或者 x节点的key和p节点的key相等else if ((kc null (kc comparableClassFor(k)) null) ||(dir compareComparables(kc, k, pk)) 0)// 使用定义的一套规则来比较x节点和p节点的大小用来决定向左还是向右查找dir tieBreakOrder(k, pk);// 将p节点的父节点赋值给xp,中间变量用于下面给x的父节点赋值TreeNodeK,V xp p; // dir0则向p左边查找,否则向p右边查找,如果为null,则代表该位置即为x的目标位置if ((p (dir 0) ? p.left : p.right) null) {// x的父节点即为最后一次遍历的p节点x.parent xp; // 如果dir 0, 代表x节点为父节点的左节点if (dir 0) xp.left x;// 如果dir 0, 代表x节点为父节点的右节点else xp.right x;// 进行红黑树的插入平衡(通过左旋、右旋和改变节点颜色来保证当前树符合红黑树的要求)root balanceInsertion(root, x);break;}}}}// 如果root节点不在tab索引位置的头节点, 则将其调整为头节点moveRootToFront(tab, root); }1.2.7 get方法 /*** HashMap的get操作源码注释*/ public V get(Object key) {NodeK,V e;// 调用hash(key)方法计算键key的哈希值然后调用getNode方法获取与该键对应的节点将结果赋给变量e// 如果e为null则返回null否则返回e节点的值e.valuereturn (e getNode(hash(key), key)) null ? null : e.value; }// 获取与该键对应的节点 final NodeK,V getNode(int hash, Object key) {NodeK,V[] tab; NodeK,V first, e; int n; K k;// 对table进行校验table不为空 table长度大于0 table索引位置(使用table.length - 1和hash值进行位与运算)的节点不为空if ((tab table) ! null (n tab.length) 0 (first tab[(n - 1) hash]) ! null) {// 检查first节点的hash值和key是否和入参的一样如果一样则first即为目标节点直接返回first节点if (first.hash hash // always check first node((k first.key) key || (key ! null key.equals(k))))return first;// 如果first不是目标节点并且first的next节点不为空则继续遍历if ((e first.next) ! null) {if (first instanceof TreeNode)// 如果是红黑树节点则调用红黑树的查找目标节点方法getTreeNodereturn ((TreeNodeK,V)first).getTreeNode(hash, key);// 继续遍历do {// 执行链表节点的查找向下遍历链表, 直至找到节点的key和入参的key相等时,返回该节点if (e.hash hash ((k e.key) key || (key ! null key.equals(k))))return e;} while ((e e.next) ! null);}}// 找不到符合的返回空return null; }1.2.8 remove方法 /*** HashMap的remove操作源码注释*/ public V remove(Object key) {NodeK,V e;return (e removeNode(hash(key), key, null, false, true)) null ?null : e.value; }final NodeK,V removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {NodeK,V[] tab; NodeK,V p; int n, index;// 如果table不为空 table长度大于0 根据hash值计算出来的索引位置不为空, 将该位置的节点赋值给pif ((tab table) ! null (n tab.length) 0 (p tab[index (n - 1) hash]) ! null) {NodeK,V node null, e; K k; V v;// 如果p的hash值和key都与入参的相同, 则p即为目标节点, 赋值给nodeif (p.hash hash ((k p.key) key || (key ! null key.equals(k))))node p;// 否则将p.next赋值给e向下遍历节点else if ((e p.next) ! null) {// 如果p是TreeNode则调用红黑树的方法查找节点if (p instanceof TreeNode)node ((TreeNodeK,V)p).getTreeNode(hash, key);else {// 否则进行普通链表节点的查找do {// 当节点的hash值和key与传入的相同,则该节点即为目标节点if (e.hash hash ((k e.key) key ||(key ! null key.equals(k)))) {// 赋值给node, 并跳出循环node e; break;}// p节点赋值为本次结束的e在下一次循环中e为p的next节点p e; // e指向下一个节点} while ((e e.next) ! null); }}// 如果node不为空即根据传入key和hash值查找到目标节点则进行移除操作if (node ! null (!matchValue || (v node.value) value ||(value ! null value.equals(v)))) {// 如果是TreeNode则调用红黑树的移除方法if (node instanceof TreeNode)((TreeNodeK,V)node).removeTreeNode(this, tab, movable);// 如果node是该索引位置的头节点则直接将该索引位置的值赋值为node的next节点// “node p”只会出现在node是头节点的时候如果node不是头节点则node为p的next节点else if (node p)tab[index] node.next;// 否则将node的上一个节点的next属性设置为node的next节点// 即将node节点移除, 将node的上下节点进行关联(链表的移除)elsep.next node.next;modCount;--size;// 供LinkedHashMap使用afterNodeRemoval(node); // 返回被移除的节点return node;}}return null; }1.2.9 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;// 旧表的容量不为0if (oldCap 0) {// 判断旧表的容量是否超过最大容量值if (oldCap MAXIMUM_CAPACITY) {// 将阈值设置为Integer.MAX_VALUE并直接返回旧表threshold Integer.MAX_VALUE;return oldTab;}// 将newCap赋值为oldCap的2倍如果newCap最大容量小于最大容量值并且oldCap16else if ((newCap oldCap 1) MAXIMUM_CAPACITY oldCap DEFAULT_INITIAL_CAPACITY)// 将新阈值设置为原来的两倍newThr oldThr 1; // double threshold}// 如果旧表的容量的阈值大于0, 是因为初始容量被放入阈值则将新表的容量设置为旧表的阈值else if (oldThr 0)newCap oldThr;else {// 旧表的容量为0, 旧表的阈值为0这种情况是没有传初始容量的new方法创建的空表将阈值和容量设置为默认值newCap DEFAULT_INITIAL_CAPACITY;newThr (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 如果新表的阈值为空, 则通过新的容量*负载因子获得阈值if (newThr 0) {float ft (float)newCap * loadFactor;newThr (newCap MAXIMUM_CAPACITY ft (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}// 将当前阈值设置为刚计算出来的新的阈值定义新表容量为刚计算出来的新容量将table设置为新定义的表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;// 将索引值为j的旧表头节点赋值给eif ((e oldTab[j]) ! null) { // 将旧表的节点设置为空, 以便垃圾收集器回收空间oldTab[j] null; // 如果e.next为空, 则代表旧表的该位置只有1个节点计算新表的索引位置, 直接将该节点放在该位置if (e.next null)newTab[e.hash (newCap - 1)] e;// 如果是红黑树节点则进行红黑树的重hash分布(跟链表的hash分布基本相同)else if (e instanceof TreeNode)((TreeNodeK,V)e).split(this, newTab, j, oldCap);else { // preserve order// 如果是普通的链表节点则进行普通的重hash分布// 存储索引位置为:“原索引位置”的节点NodeK,V loHead null, loTail null; // 存储索引位置为:“原索引位置oldCap”的节点NodeK,V hiHead null, hiTail null; NodeK,V next;do {next e.next;// 如果e的hash值与旧表的容量进行与运算为0,则扩容后的索引位置跟旧表的索引位置一样if ((e.hash oldCap) 0) {// 如果loTail为空if (loTail null) // 将loHead赋值为第一个节点loHead e; else// 否则将节点添加在loTail后面loTail.next e; // 并将loTail赋值为新增的节点loTail e; }else {// 如果hiTail为空, 代表该节点为第一个节点if (hiTail null)// 将hiHead赋值为第一个节点hiHead e; else// 否则将节点添加在hiTail后面hiTail.next e; // 并将hiTail赋值为新增的节点hiTail e; }} while ((e next) ! null);// 如果loTail不为空说明旧表的数据有分布到新表上“原索引位置”的节点则将最后一个节点的next设为空并将新表上索引位置为“原索引位置”的节点设置为对应的头节点if (loTail ! null) {loTail.next null;newTab[j] loHead;}// 如果hiTail不为空说明旧表的数据有分布到新表上“原索引oldCap位置”的节点则将最后一个节点的next设为空并将新表上索引位置为“原索引oldCap”的节点设置为对应的头节点if (hiTail ! null) {hiTail.next null;newTab[j oldCap] hiHead;}}}}}// 返回新表return newTab; }二、 应用与最佳实践 2.1 在实际项目中如何合理使用HashMap 合理使用HashMap的建议 缓存数据可以使用HashMap作为缓存的数据结构将计算结果或者频繁访问的数据存储在HashMap中以提高数据的访问速度。数据索引在需要按照某个字段快速查找数据的场景下可以使用HashMap来构建索引以便快速定位数据对象。配置信息存储可以使用HashMap来存储应用程序的配置信息例如键值对形式的配置参数。数据分组当需要对数据进行分组时可以使用HashMap来进行分组存储以便快速获取特定分组的数据。缓存计算结果在一些需要频繁计算的场景下可以使用HashMap来缓存计算结果避免重复计算。快速访问和修改HashMap提供了快速的查找、插入和删除操作适合于需要频繁进行这些操作的场景。代替多层嵌套的条件判断有时候可以使用HashMap代替多层嵌套的条件判断提高代码的可读性和可维护性。事件处理可以在事件驱动的系统中使用HashMap来保存事件处理器根据事件类型快速找到对应的处理器进行处理。路由表在网络相关的应用中可以使用HashMap来构建路由表快速查找目标地址对应的路由信息。 2.2 最佳实践和注意事项 最佳实践 初始化容量 在创建HashMap时尽量提供初始容量以减少扩容操作的频率。这可以通过构造函数中的参数来完成如 HashMap(int initialCapacity)。 MapString, Integer map new HashMap(16); // 初始化容量为16负载因子 负载因子是指哈希表在自动扩容之前可以达到多满的一个度量。默认负载因子是0.75你可以根据你的需求调整。 MapString, Integer map new HashMap(16, 0.8f); // 指定负载因子为0.8 选择好的哈希函数 如果你使用自定义对象作为键确保实现了合适的hashCode()和equals()方法。 线程安全性 如果在多线程环境下使用HashMap考虑使用ConcurrentHashMap它提供了更好的并发性能。 MapString, Integer concurrentMap new ConcurrentHashMap(); 遍历方式 使用entrySet()来遍历HashMap而不是分别使用keySet()和values()。这可以避免多次计算哈希码。 for (Map.EntryString, Integer entry : map.entrySet()) {// 处理每个键值对 } 注意事项 空指针检查 在使用get()方法获取值之前最好先检查键是否存在以避免空指针异常。 if (map.containsKey(key)) {// 避免空指针异常Integer value map.get(key);// 处理值 } 键和值的类型 注意HashMap的键和值的类型确保它们符合你的预期。使用泛型可以在编译时提供类型检查。 扩容代价 HashMap在达到一定负载因子时会自动扩容这可能导致性能损失。在性能敏感的场景中可以考虑手动调整容量以减少扩容的发生。 不要过度使用HashMap 在某些情况下可能有更适合的数据结构如LinkedHashMap、TreeMap等取决于你的需求。 了解并发限制 如果在并发环境下使用HashMap要注意可能出现的并发限制确保你的代码是线程安全的。 三、 结论 3.1 对HashMap的全面总结 HashMap的全面总结 概述 定义 HashMap是Java集合框架中的一部分实现了Map接口用于存储键值对。特性 允许存储null键和null值非同步不是线程安全的。 基本操作 插入元素 put(key, value)方法用于插入键值对。获取元素 get(key)方法用于根据键获取值。删除元素 remove(key)方法用于根据键删除键值对。 内部实现 数组链表/红黑树 HashMap内部使用一个数组来存储桶(bucket)每个桶是一个链表或者红黑树用于解决哈希冲突。哈希算法 通过对键的哈希码进行运算确定键在数组中的位置。 哈希冲突 链表解决冲突 相同哈希码的键值对以链表形式存储在同一桶中。红黑树优化 当链表长度过长时会将链表转换为红黑树以提高检索效率。 扩容和负载因子 负载因子 默认为0.75表示当HashMap中的元素个数达到容量乘以负载因子时进行扩容操作。扩容 当达到负载因子时HashMap会创建一个新的容量是原容量两倍的数组将原有元素重新分配到新的数组中。 性能 平均时间复杂度 插入、删除、查找的平均时间复杂度都是O(1)在理想情况下。注意 由于哈希冲突和扩容操作性能可能会有所下降。 使用注意事项 线程安全 HashMap不是线程安全的如果需要在多线程环境中使用可以考虑使用ConcurrentHashMap。equals和hashCode方法 为了正确存储和检索对象键的类必须正确实现equals和hashCode方法。 JDK版本变化 JDK 8 引入了红黑树优化以提高性能。JDK 9 基于树的桶(bin)中的元素现在是按插入顺序排序。 3.2 鼓励读者深入学习和实践 源码分析 阅读HashMap的源代码是学习其实现原理的一种有效方式。通过查看Java标准库的HashMap源码你可以深入了解它是如何处理哈希冲突、计算哈希码、扩容等细节的。实际应用 将HashMap应用于实际项目中观察其在不同场景下的性能表现。理解何时使用HashMap以及如何调整其初始容量等参数是很重要的。深入学习数据结构和算法 了解哈希表是如何在计算机科学中工作的并学习其他数据结构和算法有助于更好地理解HashMap的优势和局限性。参与开源项目 如果可能参与开源项目特别是与数据结构和算法相关的项目。通过实际的编码和与其他开发者的交流你可以深化对HashMap以及其他数据结构的理解。 盈若安好便是晴天
http://www.sadfv.cn/news/313892/

相关文章:

  • 做网站设计怎么提升贵德县wap网站建设公司
  • 高要seo整站优化企业门户网站建设报价
  • 网站静态模板下载搜索引擎网站推广法 怎么做
  • 网站在百度突然搜不到了上海做推
  • 用php做网站用什么框架哈尔滨网站设计多少钱
  • 广州推广型网站建设深圳网站建设公司服务
  • 网站空间和云服务器贸易网站开发
  • 呼和浩特电子商务网站建设旅行社营业部管理办法
  • 手机网站用什么做的北京智能网站建设平台
  • 半岛建设公司网站修改wordpress的权限设置
  • 环保科技东莞网站建设微信平台登录
  • 一流的购物网站建设网站可以给pdf做笔记
  • 高校网站建设研究意义国外服务器租用价格
  • 外贸网站开发推广网站优化网
  • 男的女的做那个的视频网站中国行业网站联盟
  • 珠海响应式网站建设推广公司如何编写手机程序
  • 响应式设计的网站竞猜网站开发
  • 沅江市住房和建设局网站微博推广技巧
  • 沈阳网站建设方案网站开发调研报告
  • 网站服务器错误403大连网络设计有限公司
  • 网站运营作用佛山网站建设技术外包
  • 2017免费网站空间网站做seo推广方案
  • 做模板网站乐云seo效果好品牌建设流程图
  • 做存储各种环境信息的网站网页设计网站大全
  • 手机怎么创建网站做淘宝客网站需要备案吗
  • 网站开发培训班广告发布合同
  • 合肥网站网站建设厦门seo优化推广
  • 网站建设总体需求分析泉州那家做网站公司好
  • 备案网站名称与实际网站名称不一致罗湖网站设计
  • 舟山做网站的公司临沂网站建设厂家