分布式系统弹性设计(二):Resilience4j实现限流与重试

核心内容摘要

信息论与编码篇---MSSIM
轻量如月,强大随行——Lua脚本全方位解析(作用+优缺点深度剖析)

Rancher 使用手册详解

堆①堆的概念:如果有一个关键码的集合K {k0k1 k2…kn-1}把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中并满足Ki K2i1 且 Ki K2i2 (Ki K2i1 且 Ki K2i

i 012…则称为 小堆(或大堆)。

将根节点最大的堆叫做最大堆或大根堆根节点最小的堆叫做最小堆或小根堆这里堆的存储结构就是上一个树的文章提到的二叉树顺序存储堆的性质堆中某个节点的值总是不大于或不小于其父节点的值堆总是一棵完全二叉树虽然堆是一个二叉树结构但是存储的时候用顺序表/数组但是利用了根和子类2k1,2k2的下标关系构成了二叉树的结构小根堆示例(a) 逻辑结构plaintext10 / \ 15 56 / \ \ 25 30 70(b) 存储结构数组形式[10, 15, 56, 25, 30, 70]大根堆示例(a) 逻辑结构plaintext70 / \ 56 30 / \ \ 25 15 10(b) 存储结构数组形式[70, 56, 30, 25, 15, 10]②堆的存储方式:从堆的概念可知堆是一棵完全二叉树因此可以层序的规则采用顺序的方式来高效存储将元素存储到数组中后可以根据二叉树的性质对树进行还原。

假设i为节点在数组中的下标则有·如果i为0则i表示的节点为根节点否则i节点的双亲节点为 (i -

/2·如果2 * i 1 小于节点个数则节点i的左孩子下标为2 * i 1否则没有左孩子·如果2 * i 2 小于节点个数则节点i的右孩子下标为2 * i 2否则没有右孩子③堆的两个调整方式:堆的向下调整siftDown作用当堆顶元素被替换例如取出堆顶后用最后一个元素替代新的堆顶可能比子节点小大根堆或大小根堆需要向下 “沉” 到正确位置以维护堆的性质。

适用场景构建初始堆、取出堆顶元素后、堆排序过程中。

核心逻辑从当前父节点开始先找到它的左右子节点中的最优子节点大根堆找较大的子节点小根堆找较小的子节点。

如果父节点破坏了堆的性质例如大根堆中父节点值 最优子节点值就交换父子节点的位置。

然后将当前节点更新为子节点继续向下比较直到到达叶子节点或调整到位。

堆的向上调整siftUp作用当新元素插入到堆的末尾时该元素可能比父节点大大根堆或小小根堆需要向上 “浮” 到正确位置以维护堆的性质。

适用场景向堆中插入元素时。

核心逻辑从新插入的子节点开始不断与父节点比较。

如果子节点破坏了堆的性质例如大根堆中子节点值 父节点值就交换父子节点的位置。

然后将当前节点更新为父节点继续向上比较直到到达堆顶或调整到位。

核心区别

总结维度向上调整siftUp向下调整siftDown触发场景插入新元素构建堆、取出堆顶、堆排序调整方向从下往上子节点 → 父节点从上往下父节点 → 子节点比较对象仅与父节点比较与左右子节点比较先选出最优子节点解决问题子节点 “过大 / 过小” 破坏堆性质父节点 “过小 / 过大” 破坏堆性质④堆的创建首先明确一个规律两个向上调整和向下调整的代码在大根堆和小跟堆只有比较符号的区别所以下面都以大根堆为例import java.util.Arrays; public class TestHeap { public int elem[]new int [10]; public int usedsize;堆类成员变量有用来储存的数组和已使用容量不是数组最大容量public void initelem(int arr[]){ for (int i 0; i arr.length; i) { this.elem[i]arr[i]; this.usedsize; } }初始化堆默认数组容量 10并从外部数组导入数据如果默认数组容量10不够需要手动修改public boolean isfull(){ return usedsizeelem.length; }判断是否满了直接返回已使用大小是否等于堆数组最大容量public boolean isEmpty(){ return usedsize0; }判断是否为空public void createheap(){ for(int p(this.usedsize-1-

/2;p0;p--){ //总共十个那最后一个编号是9父亲是4那就需要总长度(10-

-

/2得到最后一个非叶子节点(有子节点的节点) siftdown(p,this.usedsize) ; //才能得到父的下标 }//这里是个循环所以不会有处理不完的情况 } public void siftdown(int p,int usedsize){ //从上到下大根堆 p一般是0开始 int child2*p1; while(childusedsize){ //找到p的左右孩子的最大值且保证下标合法 if(child1usedsizeelem[child]elem[child1]){ child;//如果左子叶比右子叶小(合法情况下)那就把child标到右子叶然后再跟root比大小 } if(elem[child]elem[p]){ int tempelem[child]; elem[child]elem[p]; elem[p]temp; pchild; //

交换后原父节点p的值可能现在在child的位置上 child2*p1; // 它可能比它新的子节点还要小因此需要继续向下调整 } else{ break;//

如果父节点p的值已经大于等于它的最大子节点 } // 说明以p为根的子树已经是最大堆了调整结束 } }构建大根堆构建方法 → 控制向下调整createheap()负责组织循环从最后一个非叶子节点往前遍历逐个调用siftdown()而siftdown()负责对单个父节点对应的子树进行「从上到下」调整使其成为大根堆。

两者配合最终将一个无序数组转换成符合大根堆特性的数组。

createheap()关键细节说明循环起始下标(this.usedsize-1-

/2最后一个非叶子节点第一步this.usedsize - 1是堆中「最后一个有效元素」的下标记为lastChild这个元素是叶子节点。

第二步根据完全二叉树的数组存储特性任意子节点的父节点下标 (子节点下标 -

/ 2。

第三步代入lastChild得到(lastChild -

/2 (usedsize-1-

/2这就是「最后一个非叶子节点」的下标。

为什么选它这个节点的子节点都是叶子节点天然符合大根堆无需调整是调整的「最优起点」不会出现调整结果被下层数据破坏的情况。

循环方向p 0从后往前遍历循环变量p从「最后一个非叶子节点」逐步递减到0根节点遍历所有非叶子节点。

为什么从后往前保证每调用一次siftdown()当前节点的下层子树已经是合法的大根堆siftdown()能正常生效最终从底层到顶层整棵树成为大根堆。

循环体siftdown(p, this.usedsize)传入两个参数当前要调整的父节点p堆的有效元素个数this.usedsize限制调整范围避免越界。

作用对当前p对应的子树做调整使其成为大根堆为上层节点的调整打下基础。

siftdown()关键细节说明子节点初始化child 2*p 1利用完全二叉树的数组存储特性左子节点下标 2*父节点下标 1右子节点下标 2*父节点下标 2即child1。

先默认左子节点为候选后续再通过判断切换为右子节点最大值。

找最大子节点的逻辑child1usedsizeelem[child]elem[child1]这是大根堆调整的核心优化必须先判断右子节点是否合法child1 usedsize再比较大小否则会出现数组越界异常。

最终child一定指向「左右子节点中值更大的那个」保证交换后父节点是子树的最大值符合大根堆特性。

循环终止的两种情况情况 1child usedsize循环条件不满足当前节点已经是叶子节点没有子节点无需调整。

情况 2elem[child] elem[p]执行break当前父节点 最大子节点子树符合大根堆特性调整到位。

两个方法的配合流程通俗举例假设usedsize6数组为[3,1,4,1,5,9]完整流程如下createheap()计算起始下标(

-

/22对应元素4。

循环开始p2→ 调用siftdown(2,

调整后数组变为[3,1,9,1,5,4]以 2 为根的子树成大根堆。

p1→ 调用siftdown(1,

调整后数组变为[3,5,9,1,1,4]以 1 为根的子树成大根堆。

p0→ 调用siftdown(0,

调整后数组变为[9,5,4,1,1,3]以 0 为根的整棵树成大根堆。

循环结束大根堆构建完成。

总结createheap()是「组织者」负责从最后一个非叶子节点往前遍历批量调用siftdown()。

siftdown()是「执行者」负责单个子树的从上到下调整核心是找最大子节点、比较交换、循环下沉。

两者配合的核心逻辑从底层到顶层逐步将每个子树调整为大根堆最终构建出完整的大根堆。

关键公式最后一个非叶子节点下标(usedsize-1-

/2子节点下标2*p1左、2*p2右。

public void siftup(int child){ //向上调整|配合pushchild在push里 int p(child-

/2; while(p

{ if (elem[child] elem[p]) { int temp elem[child]; elem[child] elem[p]; elem[p] temp; child p; p (child -

/ 2; } else { break; } } } public void push(int val){//插入数组 if(isfull()){//如果满了就扩容数组 elem Arrays.copyOf(elem,2*elem.length); } elem[usedsize]val; siftup(usedsize); usedsize;//加入调整完后在计数器 }这两段代码是大根堆的「元素插入 维护堆特性」完整逻辑push()负责将新元素插入到堆的末尾并处理扩容siftup()负责将新插入的元素向上调整到正确位置最终保证插入后依然是一个合法的大根堆。

其中push()是插入的「入口方法」siftup()是插入后的「辅助调整方法」两者紧密配合下面先分别解析再讲整体流程(向上调整siftup()走的就是「一条单一的垂直线路」不会分叉也不会涉及其他兄弟节点。

)。

siftup()这个方法我们之前单独解析过这里结合push()方法重点讲它和push()的配合逻辑以及核心细节回顾。

siftup()与push()配合的关键细节参数child的来源push()中传入的usedsize也就是新元素的插入下标两者完全对应保证调整的是刚插入的新元素。

调整的目的新元素插入末尾后可能比父节点大破坏大根堆通过siftup()让它 “上浮” 到合适位置最终保证插入后整个堆依然是合法的大根堆。

无需关注兄弟节点和siftdown()不同siftup()只需要和直接父节点比较不需要关注兄弟节点因为父节点本身已经符合大根堆特性大于等于其他子节点交换后依然能保证堆特性push()关键细节补充插入位置的选择elem[usedsize]堆的底层是完全二叉树插入元素只能在完全二叉树的末尾对应数组的usedsize下标不能随意插入其他位置否则会破坏完全二叉树的结构后续无法正常维护堆特性。

例如当前usedsize5有效元素下标0~4新元素插入下标5这是完全二叉树的下一个可用节点插入后仍保持完全二叉树结构。

扩容的时机与规则时机插入前先判断isfull()usedsize elem.length堆满时才扩容避免无效扩容。

规则2*elem.length容量翻倍这样可以保证扩容的时间复杂度平均为O(

是工业级代码的常用选择。

注意Arrays.copyOf()会创建一个新数组复制原数组的所有元素然后返回新数组这里直接赋值给elem完成堆底层数组的替换。

usedsize的位置调整后自增为什么要放在siftup()之后因为siftup()调整时需要传入新元素的下标usedsize如果先自增usedsize就会变成新值传入的下标就错了对应不到新元素。

例如新元素插入下标5siftup(

调整完成后usedsize自增为6表示堆的有效元素个数变为6逻辑正确。

public int poll(){ int valelem[0]; elem[0]elem[usedsize-1]; elem[usedsize-1]val; siftdown(0,usedsize-

; usedsize--; return val; }删除堆顶元素因为堆顶元素都是最大值/最小值(排序后的)所以删除就直接让它跟最后一个元素交换位置然后向下调整(从0到usedsize-

最后usedsize-1此时就相当于这个元素消失public int peek(){ if(isEmpty()){ return -1; } else{ return elem[0];//堆顶 } }获取堆顶元素:不为空的话直接获取堆数组的0下标元素public void heapSort(){ int endusedsize-1; while(end

{ int tempelem[0]; elem[0]elem[end]; elem[end]temp; siftdown(0,end); end--; } }这段代码是基于大根堆实现的升序排序算法堆排序核心逻辑是「反复提取堆顶最大值放到数组末尾再调整剩余元素为大根堆」最终将无序的堆数组转换为有序的升序数组。

end变量的核心作用end是「当前堆的有效范围边界」有两个核心功能功能 1作为「已归档最大值的存放位置」每次交换elem[0]和elem[end]就是把最大值放到end下标归档。

功能 2作为「剩余堆的有效范围上限」调用siftdown(0, end)时end限制了堆的有效范围为0~end-1避免调整已归档的元素。

举例初始end5有效元素0~5交换后最大值归档到5再调用siftdown(0,

只调整0~4的元素5不再参与。

siftdown(0, end)的关键作用交换堆顶和末尾元素后新的堆顶原末尾元素大概率是较小值会破坏大根堆特性。

调用siftdown(0, end)可以将0~end-1的剩余元素重新调整为大根堆保证下一次循环能提取到剩余元素的最大值。

注意这里传入的第二个参数是end而不是usedsize目的是「排除已归档的元素」只处理剩余未排序的部分。

循环终止条件end0当end0时数组中只剩下标0的元素它本身就是有序的无需继续循环。

整个循环执行usedsize-1次从usedsize-1到1每次归档一个最大值最终完成排序。

排序结果的特性基于大根堆的堆排序结果是升序数组。

基于小根堆的堆排序结果是降序数组逻辑类似只是每次归档最小值。

注意堆排序完成后原有的大根堆结构会被完全破坏数组变为有序数组后续无法再正常使用push()、poll()等堆操作除非重新调用createheap()构建堆。

为什么不用siftup()堆排序中调整剩余元素时用siftdown()效率更高时间复杂度O(log n)。

新的堆顶需要 “下沉” 到正确位置siftdown()直接从上到下调整而siftup()是从下到上不适合这个场景新堆顶是从末尾交换过来的大概率需要向下调整。

为什么进行一次siftdown就能确保交换首位后是大根堆了siftdown(0, end)看似是「一次方法调用」但方法内部的while循环已经完成了「完整的从上到下调整」—— 它不是只比较一次而是沿着最优子节点的线路把新堆顶「下沉」到正确位置最终让整个剩余堆恢复大根堆特性。

而且执行这次siftdown()有个重要前提交换前剩余元素0~end-1本身就是一个合法的大根堆只是堆顶被替换成了较小值其他子树依然保持大根堆特性这是堆排序循环的关键每一轮都能保证这个前提。

[3, 1, 4, 1, 5, 9]执行 heapSort() 排序 第 1 轮循环end5 交换 elem [0]9和 elem [5]3数组变为 [3, 5, 4, 1, 1, 9]9 已归档。

调用 siftdown (0,

调整 0~4 元素为大根堆数组变为 [5, 3, 4, 1, 1, 9]。

end--end4。

第 2 轮循环end4 交换 elem [0]5和 elem [4]1数组变为 [1, 3, 4, 1, 5, 9]5 已归档。

调用 siftdown (0,

调整 0~3 元素为大根堆数组变为 [4, 3, 1, 1, 5, 9]。

end--end3。

第 3 轮循环end3 交换 elem [0]4和 elem [3]1数组变为 [1, 3, 1, 4, 5, 9]4 已归档。

调用 siftdown (0,

调整 0~2 元素为大根堆数组变为 [3, 1, 1, 4, 5, 9]。

end--end2。

第 4 轮循环end2 交换 elem [0]3和 elem [2]1数组变为 [1, 1, 3, 4, 5, 9]3 已归档。

调用 siftdown (0,

调整 0~1 元素为大根堆数组变为 [1, 1, 3, 4, 5, 9]。

end--end1。

第 5 轮循环end1 交换 elem [0]1和 elem [1]1数组变为 [1, 1, 3, 4, 5, 9]1 已归档。

调用 siftdown (0,

调整 0~0 元素为大根堆数组变为 [1, 1, 3, 4, 5, 9]。

end--end0。

⑤建堆的时间复杂度因为堆是完全二叉树而满二叉树也是完全二叉树此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值多几个节点不影响最终结果)前提假设假设树的高度为h。

第 1 层2^0个节点需要向下移动h-1层第 2 层2^1个节点需要向下移动h-2层第 3 层2^2个节点需要向下移动h-3层第 4 层2^3个节点需要向下移动h-4层……第h-1层2^{h-2}个节点需要向下移动1层总移动步数推导需要移动节点的总移动步数为①两边乘以 2②用② - ①进行错位相减T(n)1−h21222324⋯2h−22h−1T(n)2021222324⋯2h−22h−1−hT(n)2h−1−h代入节点总数关系因为满二叉树的节点总数n 2^h - 1所以h \log_2(n

代入得T(n)n−log2​(n

≈n结论因此建堆的时间复杂度为O(N)。

PriorityQueue①概念是 Java 集合框架中一种特殊的队列它的核心特点是「队头元素始终是队列中优先级最高的元素」而非遵循普通队列的 FIFO先进先出规则。

其实就是java自带的无限容量(满了自动扩容)的小根堆定义方法PriorityQueueInteger q1 new PriorityQueue(); PriorityQueueInteger q2 new PriorityQueue(

;//初始容量可以自己定但是定不定无影响②常用方法函数名功能介绍boolean offer(E e)插入元素 e插入成功返回 true如果 e 对象为空抛出NullPointerException异常时间复杂度O(log₂N)注意空间不够时会进行扩容E peek()获取优先级最高的元素如果优先队列为空返回nullE poll()移除优先级最高的元素并返回如果优先队列为空返回nullint size()获取有效元素的个数void clear()清空boolean isEmpty()检测优先级队列是否为空空返回truePriorityQueueInteger arrnew PriorityQueue(); arr.offer(

; arr.offer(

; arr.offer(

; arr.offer(

; System.out.println(arr);//输出[1, 3, 6, 4]即二叉树层序遍历 System.out.println(arr.peek());// 1 arr.poll();//int tarr.poll(); System.out.println(arr.size());//3③常见题目(

top-k问题找到第k大/小的元素 or 找到前k大/小的元素2现在要找到第k小的元素第一种做法是 整体排序选出前k个第二种做法是整体建立一个大小为N的小跟堆逐步提取堆顶元素第三种做法是把前K个元素创建为大根堆遍历剩下的N-K个元素和堆顶元素比较如果比堆顶元素小则堆顶元素删除当前元素入堆前两个做法都是排序然后提取很简单的思路但是第三个做法很特别要找第k小用容量为k的大根堆反过来如果是要找第k大用容量为k的小根堆需求选择的堆类型堆的核心特性求「前 K 个最大元素」小根堆堆顶是当前堆中「最小值」求「前 K 个最小元素」大根堆堆顶是当前堆中「最大值首先我们不需要有序数组先把数组的前k个放进大根堆然后大根堆会自动排序出这三个最大的在堆顶然后我们就遍历剩下的n-k个数如果有比堆顶还小的那就把堆顶元素删除然后插入这个元素然后自动排序进行下一次对比最后就会发现大根堆中的k个就是前k小的元素而堆顶就是第k小的元素但是如果你想用大根堆PriorityQueue是小根堆你又不想手动实现大根堆怎么办↓④元素的比较:在Java中基本类型的对象可以直接比较大小。

但是如果引用类型的变量直接用/的话会编译失败因为对于用户实现自定义类型都默认继承自Object类而Object类中提供了equal方法而默认情况下调用的就是equal方法但是该方法的比较规则是没有比较引用变量引用对象的内容而是直接比较引用变量的地址但有些情况下该种比较就不符合题意那么如果我想向优先级队列中插入某个对象时需要对按照对象中内容来调整堆那该如何处理呢(

覆写基类的equalspublic class Person { // 核心属性用于判断逻辑相等 private int id; private String name; // 非核心属性不参与 equals 判断 private int age; // 构造器、getter/setter 省略自行补充 public Person(int id, String name, int age) { this.id id; this.name name; this.age age; } Override public boolean equals(Object obj) { // 步骤 1判断引用是否相等 if (this obj) return true; // 步骤 2判断 null 类型是否一致 if (obj null) return false; if (getClass() ! obj.getClass()) return false; // 步骤 3强制类型转换 Person other (Person) obj; //下面就是判断内容一样但是地址不一样的Person // 步骤 4逐一比较核心属性 // 基本类型id 比较 if (this.id ! other.id) return false; // 引用类型name处理 null equals() 比较 if (this.name null) { if (other.name ! null) return false; } else if (!this.name.equals(other.name)) return false; // 所有核心属性相等返回 true return true; } }这里的this指的是引用equals方法的比如 a.equals(b) 这里的a就是thisb就是obj一般覆写 equals 的套路就是上面演示的

如果指向同一个对象返回 true

如果传入的为 null返回 false

如果传入的对象类型不是 目标类返回 false

按照类的实现目标完成比较例如这里只要id名字一样就认为是相同的人按你的需求选择什么情况可以相等

总结不用继承直接重写equals方法(

基于Comparble接口类的比较Comparble是JDK提供的泛型的比较接口类源码实现具体如下public interface ComparableE { // 返回值: // 0: 表示 this 指向的对象小于 o 指向的对象 // 0: 表示 this 指向的对象等于 o 指向的对象 // 0: 表示 this 指向的对象大于 o 指向的对象 int compareTo(E o); }对用用户自定义类型如果要想按照大小与方式进行比较时在定义类时继承Comparble接口即可然后在类中重写compareTo方法。

compareTo(T o)方法的返回值规则关键必须遵循返回正数表示当前对象this大于参数对象o。

返回0表示当前对象this等于参数对象o。

返回负数表示当前对象this小于参数对象o。

// 实现 ComparableStudent 泛型接口指定比较的对象类型是 Student public class Student implements ComparableStudent { // 成员属性 private String name; // 姓名 private int score; // 成绩核心排序字段1 private int age; // 年龄核心排序字段2 // 构造器 public Student(String name, int score, int age) { this.name name; this.score score; this.age age; } // 覆写 compareTo() 方法定义自然排序规则 Override public int compareTo(Student other) { // 规则1先按成绩降序排列当前对象成绩 其他对象成绩 → 返回负数因为降序和默认升序相反 // 成绩降序other.score - this.score如果 this.score other.score结果为负符合 compareTo 返回负数表示 this 小于 other降序效果 int scoreCompare other.score - this.score; if (scoreCompare !

{ return scoreCompare; // 成绩不同直接返回成绩比较结果 } // 规则2成绩相同按年龄升序排列当前对象年龄 其他对象年龄 → 返回正数符合升序规则 // 年龄升序this.age - other.age默认升序规则 return this.age - other.age; } // 重写 toString() 方法方便打印输出 Override public String toString() { return Student{name name , score score , age age }; } // getter/setter 可选此处省略 }简单版public class Stu implements ComparableStu{ int score1; int score2; String name; public Stu(int score1,int score2,String name){ this.score1score1; this.score2score2; this.namename; } Override public int compareTo(Stu o){ int res this.score1-o.score1; if(res!

{ return res; } return this.score2-o.score2; } }注意点

implements ComparableStudent要写类名

返回一定是返回一个int数值如果返回值0,那么就说明调用者括号内者同理

总结在自定义类里继承接口后重写方法(

基于比较器比较(继承Comparator接口)public class Stu { int score1; String name; // 构造器 public Stu(int score1, String name) { this.score1 score1; this.name name; } // 覆写 toString()方便打印结果 Override public String toString() { return Stu{score1 score1 , name name }; } }import java.util.Comparator; // 显式实现 ComparatorStu 接口指定泛型类型为 Stu public class StuComparator implements ComparatorStu { // 覆写 compare() 方法这是接口唯一的抽象方法 Override public int compare(Stu s1, Stu s

{ // 简单规则按 score1 升序排列核心逻辑极简 // 返回值规则0 → s1排在s2前面0 → 逻辑相等0 → s1排在s2后面 return s

score1 - s

score1; } }public class TestComparatorWithoutSort { public static void main(String[] args) { //

创建两个待比较的 Stu 对象 Stu stu1 new Stu(85, 李

; Stu stu2 new Stu(90, 张

; //

创建自定义 Comparator 实例比较工具 StuComparator stuComparator new StuComparator(); //

手动调用 compare() 方法比较 stu1 和 stu2接收返回值 int compareResult stuComparator.compare(stu1, stu

; //

根据返回值判断两个对象的大小关系核心解读返回值 if (compareResult

{ System.out.println(结论stu1 stu2按 score1 升序stu1 应排在 stu2 前面); } else if (compareResult

{ System.out.println(结论stu1 stu2按 score1 比较两者逻辑相等); } else { System.out.println(结论stu1 stu2按 score1 升序stu1 应排在 stu2 后面); } } }

总结要写两个类。

一个是自定义Stu类(不需要继承)另一个是继承Comparator 接口(里面依旧要写自定义类)用来比较的类。

然后实例化比较类然后就可以调用里面的比较方法获取返回值根据返回值 0来执行对应操作覆写的方法说明Object.equals因为所有类都是继承自Object的所以直接覆写即可不过只能比较相等与否Comparable.compareTo需要手动实现接口侵入性比较强但一旦实现每次用该类都有顺序属于内部顺序Comparator.compare需要实现一个比较器对象对待比较类的侵入性弱但对算法代码实现侵入性强因此PriorityQueue想从小根堆变成大根堆PriorityQueueInteger maxHeap new PriorityQueue(Comparator.reverseOrder()); 加上一个重写的接口即可这里的Comparator.reverseOrder()是java自带的取反接口当然也有手动重写接口// 第一步自己手动实现 Comparator 接口定义大根堆的比较规则降序 class MyMaxHeapComparator implements ComparatorInteger { // 覆写 compare() 方法实现降序逻辑对应大根堆 Override public int compare(Integer a, Integer b) { // 大根堆需要数值大的元素优先级高堆顶为最大值 // 降序逻辑b - a返回正数表示 a b负数表示 a b0 表示相等 return b - a; } } // 第二步使用自定义比较器构建大根堆 public class CustomMaxHeapDemo { public static void main(String[] args) { // 传入自己实现的 MyMaxHeapComparator构建大根堆 PriorityQueueInteger maxHeap new PriorityQueue(new MyMaxHeapComparator());最后一步这样也可以 MyMaxHeapComparator myMaxHeapComparatornew MyMaxHeapComparator() PriorityQueueInteger maxHeap new PriorityQueue(myMaxHeapComparator); 总之要实例化/匿名类

爱液-爱液应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123