最新要闻
- 每片5.5元!绿联苹果钢化膜促销:适用iPhone 14/13/12系列
- 每日视讯:河南发布电动车佩戴头盔规定草案:未戴拒不改正罚20元
- 全球观天下!苦等3年微软终于点头:苹果M1/M2 Mac正式支持运行Win11
- 天天短讯!李荣浩新歌《乌梅子酱》火了!乌梅子酱淘宝搜索量暴涨200倍
- 新资讯:1999元!小米米家智能除湿机50L发布:100平除湿 梅雨季不怕了
- 天天新消息丨本周盘点(2.13-2.17):永吉股份周跌2.30%,主力资金合计净流出1796.91万元
- 每日看点!三年之期已到 不换APP享受自由音乐的理想国还有多远?
- 诗与远方
- 天天快消息!男子初到上海见转弯扶梯被震撼 网友看呆:同没见过
- 6999元!爱玛联名雷神发布HERO限量款机车:2500W电机 一键弹射
- 张纪中回应张颂文沙漠捡垃圾被剧组丢下:请理性看待
- 天天最新:微凸铜底设计!微星CORELIQUID M360散热器图赏
- 当前视讯!美俄亥俄州居民嘴唇变色脸上起红斑 官方称空气水质安全:小溪全是死鱼 污染超百公里
- 全球快资讯丨iPhone 15 Pro外观无悬念了!关键是价格会更贵
- 《生化危机4:重制版》新细节:艾达王将扮演重要角色
- 蚌埠住了!iPhone 18 Pro背面设计来了 堪称行星发动机
广告
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
世界热点!3、TreeMap源码解析
- 1 TreeMap基本介绍
- 2 红黑树数据结构回顾
- 3 成员变量
- 4 内部类Entry
- 5 构造函数
- 6 重要方法分析
- 6.1 get方法分析
- 6.2 put方法分析
- 6.3 插入调整函数fixAfterInsertion()解析
- 6.4 删除方法remove()解析
- 6.5 删除调整函数fixAfterDeletion()解析
- 6.6 寻找后继函数successor()解析
- 7 解惑:
- 1 TreeMap支持key自定义排序,而红黑树对key的固定的排序规则,两者如何兼容的?
1 TreeMap基本介绍
- Java TreeMap实现了SortedMap接口,也就是说会按照key的大小顺序对Map中的元素进行排序
- key大小的评判可以通过其本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator)。
- TreeMap底层通过红黑树实现
- TreeMap是非同步的。可以通过如下方式将TreeMap包装成同步的:SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
- TreeMap 跟 HashMap是两种不同的结构,TreeMap没有使用hash相关概念
2 红黑树数据结构回顾
- 每个节点颜色不是黑色,就是红色
- 根节点是黑色的
- 红色节点不能连续
- 对于每个节点,从该节点到其树尾端的简单路径上,均包含相同数目的黑色节点
3 成员变量
private final Comparator super K> comparator; //排序器,如果空,按照key的字典顺序来排序(升序);comparator为空时用Comparable的排序接口 private transient Entry root; //根节点 private transient int size = 0; //树中entry个数 ,即红黑树大小 private transient int modCount = 0; //数结构被修改的次数的 /** * Fields initialized to contain an instance of the entry set view * the first time this view is requested. Views are stateless, so * there"s no reason to create more than one. */ private transient EntrySet entrySet; private transient KeySet navigableKeySet; private transient NavigableMap descendingMap; /** * Dummy value serving as unmatchable fence key for unbounded * SubMapIterators */ private static final Object UNBOUNDED = new Object(); private static final boolean RED = false; //红节点 默认false private static final boolean BLACK = true; // 黑节点 默认true
4 内部类Entry
它是组成树的节点的类型
(资料图片仅供参考)
static final class Entry implements Map.Entry { K key; // key V value; //value Entry left; //左孩子 Entry right; //右孩子 Entry parent; //父节点 boolean color = BLACK; //默认黑色 //根据 key value 父节点创建新节点 Entry(K key, V value, Entry parent) { this.key = key; this.value = value; this.parent = parent; } public K getKey() { return key; } public V getValue() { return value; } //替换value,返回旧value public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } // 重写equals方法:key 和 value的引用都相等 public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry,?> e = (Map.Entry,?>)o; return valEquals(key,e.getKey()) && valEquals(value,e.getValue()); } //重写hashCode方法,返回 key 和 value的hashCode 的异或运算结构 public int hashCode() { int keyHash = (key==null ? 0 : key.hashCode()); int valueHash = (value==null ? 0 : value.hashCode()); return keyHash ^ valueHash; } public String toString() { return key + "=" + value; } }
5 构造函数
// 构造函数一:不指定排序器。按照key的字典顺序来排序(升序) public TreeMap() { comparator = null; } // 构造函数二:指定排序器 public TreeMap(Comparator super K> comparator) { this.comparator = comparator; } //构造函数三:构造并返回跟参数m有相同键值映射结构的treeMap(m变为红黑树) public TreeMap(Map extends K, ? extends V> m) { comparator = null; putAll(m); } //构造函数四:构造并返回跟参数m(有序的)有相同键值映射结构的treeMap public TreeMap(SortedMap m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } }
Comparator Integer类型倒序排序
public static void main(String[] args) { TreeMap treeMap = new TreeMap(new ComparatorObj()); treeMap.put(2,"ss"); treeMap.put(3,"sss"); System.out.println(treeMap); } static class ComparatorObj implements Comparator{ @Override public int compare(Integer o1, Integer o2) { return o2-o1; //倒序排序 } }
输出结果:
2022-07-11 18:02:23,871 [INFO] main: {3=sss, 2=ss}
6 重要方法分析
6.1 get方法分析
(实际调用getEntry(Object key))
- get(Object key)方法是对接口Map的方法实现
- get(Object key)方法转为对getEntry(Object key)方法的实现分析:算法思想是根据key的自然顺序(或者比较器顺序)对二叉查找树进行查找,直到找到满足k.compareTo(p.key) == 0的entry,再返回entry的value。
源码分析如下:
public V get(Object key) { Entry p = getEntry(key); //根据key找到entry,再返回其value return (p==null ? null : p.value);}//重点分析该方法final Entry getEntry(Object key) { if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); //key非空校验 @SuppressWarnings("unchecked") Comparable super K> k = (Comparable super K>) key; //自然顺序,使用Comparable的排序接口 Entry p = root; while (p != null) { //从根节点开始 循环遍历 int cmp = k.compareTo(p.key); //compareTo:左边减去右边 if (cmp < 0) //参数key值小于父节点key p = p.left; //取左子节点 else if (cmp > 0) p = p.right; //参数key值大于父节点key,取右子节点 else return p; //key相等,则直接返回当前entry } return null;}
查询方法说明:
- 在while循环外,创建动态游标节点,游标首次指向root节点,以游标!=null作为循环条件
- 在while循环内,根据compareTo结果,取游标的左子节点或右子节点,作为新的游标
- 找到满足k.compareTo(p.key) == 0的entry,退出循环
getEntryUsingComparator源码:
//提供自定义排序器的查询找方法,原理类似final Entry getEntryUsingComparator(Object key) { @SuppressWarnings("unchecked") K k = (K) key; Comparator super K> cpr = comparator; if (cpr != null) { Entry p = root; while (p != null) { int cmp = cpr.compare(k, p.key); //cpr.compare 第一个参数减去第二个参数 if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } } return null; }
6.2 put方法分析
public V put(K key, V value) { Entry t = root; // 情况一:根节点为空,将当前key value作为root if (t == null) { compare(key, key); //key为null则抛异常 root = new Entry<>(key, value, null);//初始化root size = 1; //叶子个数+1 modCount++; // 结构修改次数自增 return null; //新叶子,所以old value 为空 } // 情况二:如果找到key相同的,则更新value ,过程类似get方法 int cmp; Entry parent; Comparator super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable super K> k = (Comparable super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } //情况三:没有相同的key,则添加新叶子。 //经过上面的两种遍历,完成了二分法查找,找到适合插入的地方:parent。 Entry e = new Entry<>(key, value, parent); //创建新的entry //确定新增叶子作为parent的左孩子还是右孩子,插入的动作完成 if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); //插入完成后,对二叉树进行调整 size++; modCount++; return null;}//这个方法实际上是对key做null检查,如果是null会抛出异常(测试代码验证过)public TreeMap(Comparator super K> comparator) { this.comparator = comparator; }
put方法说明:
- 如果root为空,则新增的entry作为root
- 遍历搜索是否存在相同的key,存在则替换value。这过程也是二叉排序树的二分查找法:找到了作为插入点的parent。
- 插入操作:找到parent,并将其left或者right指向新的entry。
- 如果是插入,则需要对红黑树进行结构调整。 (插入:节点默认为红色,root节点:设置为黑色,覆盖节点:颜色保持不变)
- 维护成员变量:size,modCount。
6.3 插入调整函数fixAfterInsertion()解析
/** From CLR */private void fixAfterInsertion(Entry x) { x.color = RED; //新增节点默认为红色,再进行规则判断 // 从树末端开始遍历:父节点是红色,则需要对树进行调整 while (x != null && x != root && x.parent.color == RED) { if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { Entry y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { Entry y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } root.color = BLACK; //在遍历外面,确保root一定是黑色}
方法说明:
- 新增的节点默认为红色,并从树末端往上遍历
- 如果新增节点的父亲是红色,则需要进行结构调整
- 结构调整这部分有点复杂,回头再深入理解todo
6.4 删除方法remove()解析
知识回顾:二叉排序树的删除过程:(情况一,treeMap用后继代替,其实用前驱或者后继是一样的)
源码如下:
// 调用getEntry(key)找到对应entry,调用deleteEntry 删除节点public V remove(Object key) { Entry p = getEntry(key); if (p == null) return null; V oldValue = p.value; deleteEntry(p); return oldValue;}//执行删除操作private void deleteEntry(Entry p) { //先对全局变量modCount、size 进行调整 modCount++; size--; //情况1:左右孩子都不为空:后继节点代替 if (p.left != null && p.right != null) { Entry s = successor(p); //寻找后继 (另外分析) //将删除点的key、value、引用分别更新为代替节点 p.key = s.key; p.value = s.value; p = s; // } //情况2:有1个孩子 Entry replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; //左孩子父亲更新删除节点的父亲 //父亲为root,则后继变为新的root if (p.parent == null) root = replacement; //左孩子顶上 else if (p == p.parent.left) p.parent.left = replacement; //右孩子顶上 else p.parent.right = replacement; // 删除节点的left、right、parent置空:被移除出树 p.left = p.right = p.parent = null; // 删除黑色节点:调整结构 if (p.color == BLACK) fixAfterDeletion(replacement); } else if (p.parent == null) { //删除root节点 root = null; } else { // 情况1:没孩子 if (p.color == BLACK) fixAfterDeletion(p); //将父亲的左孩子或者有孩子清空 if (p.parent != null) { if (p == p.parent.left) p.parent.left = null; else if (p == p.parent.right) p.parent.right = null; p.parent = null; } }}
删除说明:
- 删除过程遵从二叉排序树的删除特点(1、有一个孩子则孩子顶上;2两个孩子就用后继顶上,没有孩子则直接移除)
- 节点删除,即left、right、parent置空;删除后,需要更新父亲节点的的左孩子或右孩子
- 考虑是否需要更新全局变量root节点
- 只有删除点是BLACK的时候,才会触发调整函数,因为删除RED节点不会破坏红黑树的任何约束,而删除BLACK节点会破坏规则4。
6.5 删除调整函数fixAfterDeletion()解析
private void fixAfterDeletion(Entry x) { while (x != root && colorOf(x) == BLACK) { if (x == leftOf(parentOf(x))) { Entry sib = rightOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { // symmetric Entry sib = leftOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(leftOf(sib)) == BLACK) { setColor(rightOf(sib), BLACK); setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; } } } setColor(x, BLACK);}
说明:结构调整这部分有点复杂,回头再深入理解todo
6.6 寻找后继函数successor()解析
//寻找任意节点后继 static TreeMap.Entry successor(Entry t) { if (t == null) return null; //情况1:右孩子不为空,向后代遍历:找到右孩子中孙子最小的那个节点(不断寻找left,直至为空) else if (t.right != null) { Entry p = t.right; while (p.left != null) p = p.left; return p; } else { // 情况2:右孩子为空,向祖先遍历,当任意节点是它父亲的左孩子时,则该节点的父亲为t的后继 Entry p = t.parent; Entry ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } }
寻找后继的算法说明:对于任意k:
- 情况一:k的右孩子不为空,向后代遍历:找到右孩子的子孙子中辈分最低的左孩子(不断寻找left,直至为空)
- 情况二:key的右孩子为空,向祖先遍历:当任意节点是它父亲的左孩子时,则该节点的父亲为t的后继
右孩子不为空的后继寻找:
右孩子为空为空的后继寻找:
7 解惑:
1 TreeMap支持key自定义排序,而红黑树对key的固定的排序规则,两者如何兼容的?
- 支持key自定义排序:指通过自定义的排序器,计算出任意key相对其它key的大小关系
- 红黑树对key的固定的排序:指按照红黑树的数据结构(二叉排序树+红黑节点规则),来组织key的树状结构,其中二叉排序的大小关系是根据排序器的计算出来的
- 两者不冲突
世界热点!3、TreeMap源码解析
每片5.5元!绿联苹果钢化膜促销:适用iPhone 14/13/12系列
每日视讯:河南发布电动车佩戴头盔规定草案:未戴拒不改正罚20元
全球观天下!苦等3年微软终于点头:苹果M1/M2 Mac正式支持运行Win11
天天短讯!李荣浩新歌《乌梅子酱》火了!乌梅子酱淘宝搜索量暴涨200倍
新资讯:1999元!小米米家智能除湿机50L发布:100平除湿 梅雨季不怕了
当前信息:阿里一面:谈一下你对DDD的理解?2W字,帮你实现DDD自由
看热讯:关于ChatGPT,我们到底在担心什么?
【世界新要闻】在centos stream 9上搭建k8s最新版本(当前:v1.26.1)集群环境
【环球热闻】云原生学习作业8
天天新消息丨本周盘点(2.13-2.17):永吉股份周跌2.30%,主力资金合计净流出1796.91万元
每日看点!三年之期已到 不换APP享受自由音乐的理想国还有多远?
诗与远方
天天快消息!男子初到上海见转弯扶梯被震撼 网友看呆:同没见过
6999元!爱玛联名雷神发布HERO限量款机车:2500W电机 一键弹射
张纪中回应张颂文沙漠捡垃圾被剧组丢下:请理性看待
ESXi Args勒索病毒来袭,VMware ESXi用户需提高警惕
2、HashMap源码分析
观热点:使用springboot cache + redis缓存时使用gzip压缩以提升性能
天天最新:微凸铜底设计!微星CORELIQUID M360散热器图赏
当前视讯!美俄亥俄州居民嘴唇变色脸上起红斑 官方称空气水质安全:小溪全是死鱼 污染超百公里
全球快资讯丨iPhone 15 Pro外观无悬念了!关键是价格会更贵
《生化危机4:重制版》新细节:艾达王将扮演重要角色
蚌埠住了!iPhone 18 Pro背面设计来了 堪称行星发动机
世界今日报丨2023湖南新能源汽车置换促销补贴怎么领取
如何用chatGPT、代理IP和网络爬虫,打造一个智能有趣的聊天机器人?
环球新动态:数据结构刷题2023.02.17小记
分享7个刺激的老司机网站,别轻易点开
天天观察:乐维百科:什么是IT监控?为什么运维需要监控?
今日最新!正式抛弃 Feign!Spring 6 推出新特性:HTTP Interface,这波太秀了!
环球速看:2023年保定住房贷款利息个税专项附加扣除政策
《中国乒乓之绝地反击》今日正式上映:邓超、吴京演绎男乒至暗时刻
【世界聚看点】0脂无糖 舌里全麦面包14.9元2斤40片大促
你还会吃吗?女子用1元成本做出即食燕窝:呼吁别被智商税
今日热搜:车厂突然破产 几十万车主有点慌:修车配件全国找
真我GT Neo5 1TB版秒罄!徐起否认饥饿营销:需求量太大
天天微速讯:常见的个人博客搭建部署方案
焦点速递!SpringBoot
2023年跳槽,.NET工程师们,准备好了吗?
徐鸿平
快看点丨努比亚首款AR眼镜官宣:将亮相MWC 2023
全球动态:更耐摔?苹果上架iPhone 14 Pro屏幕保护膜:348元买吗
焦点快播:女车主买奇瑞QQ冰淇淋电动车一天坏3次!4S店承认故障 退车
天天速读:3月见!魅族20系列宣布首发无界生态系统Flyme 10
Reno时隔4年再度回归潜望长焦!OPPO Reno 10系列曝光
有个n字的鞋子是什么牌子?国产运动品牌有哪些?
贡菜是什么菜?贡菜的功效与作用是什么?
四王下山管天下是哪四个王?四王下山管天下是什么生肖?
春雨贵如油是什么节气?密密的春雨像什么比喻?
二十不惑人物关系是什么?二十不惑2演员表
iphone屏幕分辨率是多少?iphone屏幕分辨率怎么调?
天天热推荐:[django]钩子函数的一些细节(clean)
环球短讯!火山引擎入选《2022 爱分析 · DataOps 厂商全景报告》,旗下 DataLeap 产品能力获认可
快播:ETL的系统核心特征
环球热议:SQL Server创建Oracle链接服务器
电脑自动重启是什么原因?电脑自动重启频繁怎么解决?
CAD布局怎么使用?cad布局背景怎么调成黑色?
gta5怎么切换角色?GTA5怎么刷钱?
deb是什么意思?deb怎么安装?
世界热讯:BBA直呼内行!吉利“仰望银河”出圈:多家国产品牌跟风“望天”
天天时讯:首发1399元!小米小顽智能全自动猫砂盆开售:14天不用管
苹果iOS 16.4首个测试版发布 新增15个表情符号被吐槽太丑
今日热文:网传杭州取消限行 官方辟谣:假的、Chatgpt撰写生成
《狂野之心》IGN 8分:足以媲美《怪猎》狩猎大作
天天视讯!银河麒麟V10系统安装步骤
Java 文件上传
世界新动态:机器学习-集成学习GBDT
如何压缩图片大小?简单的图片压缩方法分享
世界视点!一降再降!一汽丰田首款纯电SUV大促:优惠6万到手13.98万起
祈福者放生2.5万斤鲇鱼被追责:属外来物种 危害极大
合资燃油车危险了!比亚迪秦PLUS DM-i冠军版上市7天订单超3.2万
当前动态:促销“狂飙”结束 特斯拉Model Y全系已上涨2000元
微软进一步拥抱AI:Win11新功能“记住”用户常用窗口布局
世界热点!《分布式技术原理与算法解析》学习笔记Day14
环球最新:Django Cannot assign "A1": "B1" must be a "C1" instance.
把选择语句和重复语句视为块
营收创6年最大跌幅!苹果被曝裁员外包员工 库克最后手段?
时隔3年半漫威重返内地!《蚁人与黄蜂女:量子狂潮》今日上映
天天热头条丨《星际争霸2》世界冠军李培楠回国:黄旭东等为其接风
当前视讯!三亚3米长搁浅抹香鲸救治无效不幸死亡:或被鲨鱼咬伤
全球看热讯:干货分享丨店长管理的五大核心你抓住了吗!
当前要闻:没买车的等等党们:赢了
每日看点!男子购买1200元年货32天快递仍未送达 邮政客服:车还在路上
每日动态!注意!特斯拉宣布召回超36万辆车 会突然停车发生车祸:马斯克火速回应
环球新动态:iPadOS 16:拖后腿了
读Java实战(第二版)笔记12_重构、测试和调试
【环球报资讯】2023开年火药味儿十足 “大内存”机型卷出了新高度
今日播报!期末复习——同步、互斥、死锁
男子卖出游戏账号后3次恶意找回 为何被判盗窃而不是诈骗?
世界热推荐:一年节省185亿度电!中车研制复合材料“超级铜”登上央视
魔兽国服重开遥遥无期 消息称暴雪找不到接盘方:能谈的都试了
每日播报!我想知道知乎现在是怎么了?
女子询问换杯 却被奈雪店员辱骂:拒绝出面道歉
即时看!70城新房价格指数时隔12个月首次止跌
全球观天下!苹果7喇叭灰尘怎么清理 苹果7喇叭灰尘清理的方法
世界微资讯!汉诺塔
观察:真正“搞”懂HTTPS协议19之HTTPS优化
pnpm的基本原理及快速使用
环球焦点!Windows 环境下安装与配置 Node.js
fusion app远程公告(微云)