HashMap 源码解读(JDK1.8)

一、HashMap说明

基于哈希表的 Map 接口实现。此实现提供所有可选的map操作,并允许空值和空键。(HashMap 类大致等同于 Hashtable,只是它不支持同步并且允许空值。)此类不保证插入键值的顺序;特别是,它不保证顺序会随着时间的推移保持不变。

此实现为基本操作(获取和放置)提供恒定时间性能,假设哈希函数在存储桶中正确分散元素。对集合视图进行迭代所需的时间与 HashMap 实例的“容量”(存储桶数)加上其大小(键值映射数)成正比。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载系数太低),这一点非常重要。

HashMap 的实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的存储桶数,初始容量只是创建哈希表时的容量。负载系数是哈希表在容量自动增加之前允许达到的多满程度的度量。当哈希表中的数量超过负载因子与当前容量的乘积时,将重新哈希哈希表(即重建内部数据结构),使哈希表中的存储桶数扩容为之前两倍。作为一般规则,默认负载系数(0.75) 在时间和空间成本之间提供了很好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在 HashMap 类的大多数操作中,包括 get 和 put)。在设置其初始容量时,应考虑映射中的预期条目数及其负载系数,以便最大限度地减少重新散列操作的数量。如果初始容量大于最大条目数除以负载因子,则不会发生重新哈希操作。如果要将许多映射存储在 HashMap 实例中,则创建具有足够大容量的映射将允许更有效地存储映射,而不是让它根据需要执行自动重新哈希以增加表。请注意,存储具有相同 hashCode() 的许多键将使哈希表性能降低。为了减轻影响,当键具有可比性时,此类可能会使用键之间的比较顺序来帮助中断关系(树化)。

请注意,此实现类不支持同步,意味着它不是线程安全的。如果多个线程同时访问HashMap,并且至少有一个线程在结构上修改了映射,则必须在外部同步该map。(结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。这通常是通过在封装map的某些对象上进行同步来实现的。如果不存在这样的对象,则应使用 Collections.synchronizedMap()方法装饰map。这最好在创建时完成,以防止意外地不同步访问map:

Map m = Collections.synchronizedMap(new HashMap(…));

通过“集合方法”类返回的迭代器都是快速失败的:如果在创建迭代器后的任何时间对映射进行结构修改,则除了通过迭代器自己的 remove 方法之外,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器会快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。请注意,无法保证迭代器的快速故障行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。Fail-fast 迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写一个依赖于此异常的正确性的程序是错误的:迭代器的快速故障行为应仅用于检测错误。

二、成员变量

HashMap继承了Map抽象类,实现了Map、Cloneable、Serializable接口

HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable

几个重要参数:

默认初始化容量:16

DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

最大容量:2^30
HashMap的容量为2的幂次方,扩容机制为<<1,既扩容为原来的2倍。Integer类型数据占据4字节,除掉一个最高位的符号位后,最大为2^30。

MAXIMUM_CAPACITY = 1 << 30;

默认负载系数
当存储的数据数超过容器的长度*负载系数时,触发扩容

DEFAULT_LOAD_FACTOR = 0.75f;

扩容的阈值
容器存储的个数超过扩容的阈值就会触发扩容

// 容器的长度*负载系数
int threshold;

树化的阈值
使用树而不是列表的容器计数阈值。将元素添加到至少具有这么多节点的容器时,容器将转换为树。该值必须大于 2,并且应至少为 8,以符合取消树化时在收缩时转换回普通容器。

TREEIFY_THRESHOLD = 8

取消树化的阈值
在调整大小操作期间取消树化(拆分)的容器计数阈值。应小于TREEIFY_THRESHOLD,并且最多6个用于在去除元素时进行收缩检测。

UNTREEIFY_THRESHOLD = 6;

Nede数据节点
容器中存储数据的格式

static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;// 为链表时使用Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}

TreeNode数据节点
HashMap的内部类,转化为红黑树时存储数据

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent;  // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev;    // needed to unlink next upon deletionboolean red;TreeNode(int hash, K key, V val, Node<K,V> next) {super(hash, key, val, next);}
}

底层数据结构:
在这里插入图片描述
图片转载自网上:原出处链接

三、HashMap为何要树化

JDK1.8之前的HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了节解决哈希碰撞(两个对象调用的hashCode方法计算的哈希码值一致导致计算的数组索引值相同)而存在的(“拉链法”解决冲突)。

JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(或者红黑树的边界值,默认为8)并且当前数组的长度大于64时,此时此索引位置上的所有数据改为使用红黑树存储。

1. 树化的意义

当位于一个链表中的元素较多,即hash值相等但是内容不相等的元素较多时,通过key值依次查找的效率较低。而jdk1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度(阀值)超过 8 时且当前数组的长度 > 64时,将链表转换为红黑树,这样大大减少了查找时间。jdk8在哈希表中引入红黑树的原因只是为了查找效率更高。
hash 表的查找,更新的时间复杂度是 O(1),而红黑树的查找,更新的时间复杂度是 O(log 2n),TreeNode 占用空间也比普通 Node 的大,如非必要,尽量还是使用链表。
但是红黑树可以用来避免 DoS 攻击,防止链表超长时性能下降O(n),树化应当是偶然情况,是保底策略。

2. 树化规则

HashMap里面定义了一个常量TREEIFY_THRESHOLD = 8,当链表长度超过树化阈值 8 时,先尝试调用resize()方法进行扩容来减少链表长度,如果数组容量已经 >=64(MIN_TREEIFY_CAPACITY),才会进行树化,Node节点转为TreeNode节点(TreeNode也是HashMap中定义的内部类)。

TreeNode除了Node的基本属性,还保存了父节点parent, 左孩子left,右孩子right,还有红黑树用到的red属性。

四、扩容机制

1. 什么时候需要扩容?

一般情况下,当HashMap中的元素个数超过数组长度loadFactor(负载因子)时,就会进行数组扩容,loadFactor的默认值是0.75,这是一个折中的取值。较高的值会减少空间开销,但会增加查找成本;较低的值会加大空间消耗

2. 如何进行扩容?

HashMap在进行扩容时使用 resize() 方法,每次扩容的容量都是之前容量的2倍。HashMap的容量是有上限的,必须小于1<<30,即1073741824。如果容量超出了这个数,则不再增长,且扩容阈值会被设置为Integer.MAX_VALUE(2^31-1,即永远不会超出阈值触发扩容了)。

看看扩容源码:

final Node<K,V>[] resize() {// 定义变量存储map数组Node<K,V>[] oldTab = table;// 定义变量存储原数组长度int oldCap = (oldTab == null) ? 0 : oldTab.length;// 定义变量存储扩容阈值int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {// 如果原数组长度超过HashMap最大容量,设置扩容阈值为Integer.MAX_VALUE,并且不再扩容if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}// 否则扩容为原来的两倍// 如果扩容后的容器小于最大容量且扩容前容量大于等于默认初始化容量,则设置扩容阈值为原来的两倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}// 若扩容前容量为0,且扩容阈值不为0,则设置容器容量为扩容阈值else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaults// 如果为第一次创建时扩容,设置容量为默认初始化容量,扩容阈值为DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITYnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 若扩容阈值为0,则计算扩容阈值if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}// 设置HashMap扩容阈值为新计算的扩容阈值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) {// 设置原数据为空,减少gcoldTab[j] = null;// 若next为空,表示不是链表存储if (e.next == null)// e.hash & (newCap - 1)计算新容器的存储索引位置newTab[e.hash & (newCap - 1)] = e;// 若e属于TreeNode,既红黑树存储else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);// 链表存储,获取链表下的每个节点else { // preserve order// 扩容后索引位置保持不变:loHead:头节点,loTail:尾节点Node<K,V> loHead = null, loTail = null;// 扩容后索引位置变成原索引位置+oldCap:hiHead:头节点,hiTail:尾节点Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;// 循环读取链表节点do {next = e.next;// e.hash & oldCap==0,索引位置保持不变if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}// 否则,索引位置为原索引位置+oldCapelse {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;
}

3. 容量发生变化的几种情况?

  • 空参数的构造函数:实例化的HashMap默认内部数组是null,即没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度为16。
  • 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让阈值=容量X负载因子(因此并不是我们手动指定了容量就一定不会触发扩容,超过阈值后一样会扩容!!)
  • 如果不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。

五、HashMap的索引是如何计算的?

  • 首先,计算对象的 hashCode(),得到原始hash值
  • 再进行调用 HashMap 的 hash() 方法进行二次哈希,得到二次hash值
  • 最后 & (capacity – 1) 得到索引(使用二次hash值和数组容量 - 1进行位与运算)。一般获取索引位置是通过取模运算(hash%(capcity-1)),源码做了优化使用hash&(capcity-1),hash%(capcity-1)等于hash&(capcity-1)前提是capcity是2的幂次方。
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

扩展:

数组容量为何是 2 的 n 次幂?

  • 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模
  • 扩容时重新计算索引效率更高:hash(原始hash) & oldCap(原始容量) == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap(原始容量)

为什么要进行二次 hash?

  • 二次 hash() 是为了综合高位数据,让哈希分布更为均匀
  • 二次 hash 是为了配合 容量是 2 的 n 次幂 这一设计前提,如果 hash 表的容量不是 2 的 n 次幂,则不必二次 hash
  • 容量是 2 的 n 次幂 这一设计计算索引效率更好,但 hash 的散列性就不好,需要二次 hash 来作为补偿,没有采用这一设计的典型例子是 Hashtable。

六、插入数据源码分析

HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Node[] table。
..可能已经执行过多次put...
map.put(key1 value1);

首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Node数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。----情况1

如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表或红黑树形式存在),比较key1 和已经存在的一个或多个数据的哈希值:

如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2

如果key1的哈希值和已经存在的某一个数(key2-value2)的哈希值相同,继续比: 调用key1所在类的equals(key2)

如果equals()返回false: 此时key1-value1添加成功。---- 情况3

如果equals()返true: 使用value1替换value2。

在这里插入图片描述
图片转载自网上:原出处链接

现在来看看源码:

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}/*** Implements Map.put and related methods** @param hash hash for key // hash值* @param key the key // key值* @param value the value to put // value值* @param onlyIfAbsent if true, don't change existing value // 是否更改原来存在的值,true代表不更改* @param evict if false, the table is in creation mode. // 如果为false,则表示表处于创建模式* @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;// 判断table是否为空,并赋值给tab。若数组为空或数组长度为零,调用resize()初始化if ((tab = table) == null || (n = tab.length) == 0)// 调用扩容方法,返回扩容后的数组赋值给tab// 若为空参构造,第一次扩容默认生成容量为16的数组容器n = (tab = resize()).length;// 计算key的索引位置,若索引位置为空,则直接放入key-value的Node// 将当前索引的值赋值给pif ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);// 若索引位置存在数据,说明hash相同,则比较两个数据的key是否相等else {Node<K,V> e; K k;// 判断key是否相等,若相等则将p赋值给e, 最终会替换原来的value为新的valueif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 若key不相等,则使用拉链法或红黑树进行追加// 判断节点p是否属于红黑树else if (p instanceof TreeNode)// 红黑树的插入方法e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 说明节点是链表,进行链表插入else {// 循环遍历,遍历到尾节点插入,binCount计算链表的长度for (int binCount = 0; ; ++binCount) {// 将当前节点的next赋值给e,若e为空代表到了末尾if ((e = p.next) == null) {// 插入数据p.next = newNode(hash, key, value, null);// 若链表长度大于等于TREEIFY_THRESHOLD,转换为红黑树if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}// 判断hash以及key是否相等,若相等结束循环最终会替换原来的value为新的valueif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 若e不为空,则代表存在着相同key的Node// 若onlyIfAbsent为false或为空,则替换value为新的value,返回旧的值if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;// 插入后的回调方法afterNodeAccess(e);return oldValue;}}// map修改次数更新++modCount;// 若插入后容量大于扩容阈值,则调用扩容方法if (++size > threshold)resize();// 插入后的回调方法afterNodeInsertion(evict);return null;
}// 替换给定哈希的容器索引中的所有链接节点为红黑树,除非表太小,在这种情况下会调整大小。
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 {// 将Node转化为TreeNodeTreeNode<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);}
}

文章参考:

详细理解HashMap数据结构,太齐全了!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mendix中的依赖管理:npm和Maven的应用

序言 在传统java开发项目中&#xff0c;我们可以利用maven来管理jar包依赖&#xff0c;但在mendix项目开发Custom Java Action时&#xff0c;由于目录结构有一些差异&#xff0c;我们需要自行配置。同样的&#xff0c;在mendix项目开发Custom JavaScript Action时&#xff0c;…

48v转24v 3A 48v转12v 48v转5v电源芯片AH7691X

AH7691X是一款高-效-率、高-压降压型DC-DC转换器&#xff0c;采用固定110KHz的开关频率&#xff0c;具备3A的输出电流能力&#xff0c;低纹波&#xff0c;并且具备***软启动功能、过压保护功能和温度保护。该器件还集成了峰值限流功能&#xff0c;简化了电路设计。 AH7691X内部…

MFC 绘图

效果图&#xff1a;三张bmp图 字 竖线 组成 在OnPaint()函数中 CPaintDC dc(this);CRect rect;GetClientRect(&rect); //获取客户区矩形CDC dcBmp; //定义并创建一个内存设备环境dcBmp.CreateCompatibleDC(&dc); //创建兼容性DCCBitmap …

【kafka实战】01 3分钟在Linux上安装kafka

本节采用docker安装Kafka。采用的是bitnami的镜像。Bitnami是一个提供各种流行应用的Docker镜像和软件包的公司。采用docker的方式3分钟就可以把我们想安装的程序运行起来&#xff0c;不得不说真的很方便啊&#xff0c;好了&#xff0c;开搞。使用前提&#xff1a;Linux虚拟机&…

AKKA 互相调用

SpringBoot 集成 AKKA 可以参考此文&#xff1a;SpringBoot 集成 AKKA 场景1&#xff1a;bossActor 收到信息&#xff0c;然后发给 worker1Actor 和 worker2Actor controller 入口&#xff0c;初次调用 ActorRef.noSender() Tag(name "test") RestController Req…

【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现【更新中】

【MATLAB第77期】基于MATLAB代理模型算法的降维/特征排序/数据处理回归/分类问题MATLAB代码实现 本文介绍基于libsvm代理模型算法的特征排序方法合集&#xff0c;包括&#xff1a; 1.基于每个特征预测精度进行排序&#xff08;libsvm代理模型&#xff09; 2.基于相关系数corr的…

前端技术社区总目录

前端技术社区欢迎您的订阅。订阅后&#xff0c;您将可以查看以下所有博客内容。 注&#xff1a;专栏内容主要面向新手 注&#xff1a;每个示例都有相对应的完整代码 注&#xff1a;该专栏博客内容将会逐步迁移至https://blog.csdn.net/m0_60387551/article/details/128017725 …

用于自然语言处理的 Python:理解文本数据

一、说明 Python是一种功能强大的编程语言&#xff0c;在自然语言处理&#xff08;NLP&#xff09;领域获得了极大的普及。凭借其丰富的库集&#xff0c;Python 为处理和分析文本数据提供了一个全面的生态系统。在本文中&#xff0c;我们将介绍 Python for NLP 的一些基础知识&…

Sound/播放提示音, Haptics/触觉反馈, LocalNotification/本地通知 的使用

1. Sound 播放提示音 1.1 音频文件: tada.mp3&#xff0c; badum.mp3 1.2 文件位置截图: 1.3 实现 import AVKit/// 音频管理器 class SoundManager{// 单例对象 Singletonstatic let instance SoundManager()// 音频播放var player: AVAudioPlayer?enum SoundOption: Stri…

【C++】stack queue

stack & queue 一、容器适配器二、deque&#xff08;了解&#xff09;三、stack1. stack 的介绍2. 模拟实现 stack 四、queue1. queue 的使用2. 模拟实现 queue3. priority_queue&#xff08;1&#xff09;priority_queue 的介绍&#xff08;2&#xff09;priority_queue 的…

从零开始—【Mac系统】MacOS配置Java环境变量

系统环境说明 Apple M1 macOS Ventura 版本13.5.2 1.下载JDK安装包 Oracle官网下载地址 JDK下载【注&#xff1a;推荐下载JDK8 Oracle官网JDK8下载】 关于JDK、JRE、JVM的关系说明 JDK(Java Development Kit&#xff0c;Java开发工具包) &#xff0c;是整个JAVA的核心&#…

系统学习Mysql

1.select语句 关键字执行顺序&#xff1a; 1.from 2.where 3.group by 4.select 5.having 6.order by 7.limit SQL 语句执行顺序如下&#xff1a; FROM: 指定要查询的表或子查询&#xff0c;可以包含 JOIN、WHERE 子句过滤等。 WHERE: 对 FROM 子句指定的表或子查询进行限制和…

快速使用Spring Cache

哈喽~大家好&#xff0c;这篇我们来看看快速使用Spring Cache。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【日常学习上的分享】 &#x1f949;与这篇相关的文章&#xff1a; R…

【优测云服务平台】打造承载百倍级增长后台背后的力量-性能优化

项目介绍&#xff1a; 腾讯课堂是腾讯推出的专业在线教育平台&#xff0c;凭借技术优势&#xff0c;实现在线即时互动教学&#xff0c;打破地域的限制&#xff0c;让每个人都能接受优秀老师的指导和教学。 一、背景 2020年初&#xff0c;新冠病毒肆虐&#xff0c;疫情大面积爆…

从裸机开始安装操作系统

目录 一、预置知识 电脑裸机 win10版本 官方镜像 V.S. 正版系统 二、下载微软官方原版系统镜像 三、使用微PE系统维护U盘 四、安装操作系统 五、总结 一、预置知识 电脑裸机 ●只有硬件部分&#xff0c;还未安装任何软件系统的电脑叫做裸机。 ●主板、硬盘、显卡等必…

LLM - 通俗理解位置编码与 RoPE

目录 一.引言 二.Why 位置编码? 三.What 绝对位置编码? 1.绝对位置编码 A.Embedding Table B.公式计算 2.外推性 四.How 位置编码&#xff1f; 1.直接编号 2.乘法表示 3.严格的乘法表示 4.距离衰减 五.Thats RoPE! 1.Self-Attention 2.RoPE 的复数形式 3.RoP…

前端就业宝典---目录

工作时候扭螺丝,面试时候造火箭,现状就是如此。不管是背八股文,还是掌握了知识的精华,对答如流才是硬道理。本专栏就是要集具前端精华,规范、算法、架构、封装、原理等一并汇集,让前端的小伙伴有个思路。 大剑师的微信 gis-dajianshi, 欢迎一起交流,并非常期望您能够提…

Spring Cloud Alibaba Ribbon负载均衡器

文章目录 Ribbon 负载均衡器环境搭建1.依赖2.配置3.修改其默认的负载均衡策略3.1 验证 4.创建自定义的Rule4.1 MyRule&#xff08;&#xff09;4.2 在配置config类中配置 5.饥饿加载6.我只想访问不想被别的访问 Ribbon 负载均衡器 背景 Ribbon 是一个用于客户端负载均衡的开源…

提高接口自动化测试效率:使用 JMESPath 实现断言和数据提取!

前言 做接口自动化&#xff0c;断言是比不可少的。如何快速巧妙的提取断言数据就成了关键&#xff0c;当然也可以提高用例的编写效率。笔者在工作中接触到了JMESPath&#xff0c;那到底该如何使用呢&#xff1f;带着疑惑一起往下看。 JMESPath是啥&#xff1f; JMESPath 是一…

ARM Soc内部总线

由于soc架构&#xff0c;把常用外设&#xff08;控制器&#xff09;集成到芯片内部&#xff0c;所以需要一种总线协调ARMcore与这些内部外设的通信&#xff0c;于是有了APB and AHB以及AXi这种片上总线。 同时要注意与常说的PC时代总线区分开&#xff1a; CPU总线&#xff08;…