首页 文章 精选 留言 我的

精选列表

搜索[动态代码分析],共10000篇文章
优秀的个人博客,低调大师

阿里云授权服务中心分析轻量服务器和ECS的区别

如果说到轻量应用服务器和ECS的区别,估计很多人会说就是一个产品,没啥区别,但其实这两者并不是完全相同的,下面就由阿里云湖北授权服务中心的捷讯技术的小编跟大家一起来学习下两者对比有什么区别吧。 首先,我们来看一下这两种服务器的官方定义吧。 ECS:一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。 轻量服务器:是可快速搭建且易于管理的轻量级云服务器;提供基于单台服务器的应用部署,安全管理,运维监控等服务,一站式提升您的服务器使用体验和效率。 从定义我们大概就能了解两者还是有区别的,其实轻量服务器就是简单版的ECS,它的轻量的意思只是配置操作简单而已,单并代表它性能不如ECS,相反ECS在某些层面是对ECS的升级。比如说: 1)轻量应用服务器 是 IaaS 产品但是提供定制化应用镜像且有可视化控制台可以操作,新手也能使用; 2)轻量应用服务器 在提供定制化应用镜像的同时,用户依旧可以拥有 root 权限,对系统进行个性化操作; 3)精简了一些 ECS 的功能,比如说安骑士。把安全组的操作也做了适当减法更适合用户操作。 下面我们来通过一下各个方面来对比一下吧。1、控制台轻量应用服务器ECS服务器 从控制台的界面来看,我们会发现轻量应用服务器界面简洁,清晰,对于不懂阿里云服务器的新手来说,轻量服务器就真的很适用了,当然如果大家懂那些配置,或者配置要求比较复杂,还是可以选择ECS的。 2、应用镜像 我们都知道建站最复杂的可能就是部署运行环境,所以阿里云服务器ECS有云市场镜像的功能,轻量应用服务器也借鉴了这一功能,精选了一些特别优秀的镜像提供给用户,可以满足多种需求。主要分为系统镜像和应用镜像两种: 系统镜像包括Centos、Debian、Ubuntu、windows 2008 R2、windows 2012 R2 这五种应用镜像比较多,分为:wordpress :可能是最适合建立博客的程序之一,而且通过安装插件,可以完成很多其他功能,比如商城、企业内容展示站、图片站、下载站、电影站、小说站;Drupal : 支持从个人博客到大型社区等各种不同应用的网站项目;ECShop : 一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店、购物商城,可能是外贸电商的首选;Phpwind :采用PHP + MySQL方式运行的开源社区程序,轻架构,高效易开发,使用户可快速搭建并轻松管理;宝塔Linux面板 :面板集成一键配置:LAMP/LNMP/Tomcat/Node.js、网站、数据库、FTP、SSL,可以通过Web端轻松管理服务器;LAMP : 预装了Apache,MySQL和PHP及相关组件支持,是最常见的搭建Web应用所需的环境;Node.js : 一个基于Chrome JavaScript runtime建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用;ASP/.NET 4.0 : Windows 2008 中文版 ASP/.NET运行环境支持多种ASP、ASP.NET 2.0/3.5/4. 0 解析,默认安装SQL Server2008 数据库; 直接看图吧,这个是不是很方便呀,个别镜像还额外提供了网站业务报表、php环境管理和配置等功能,对于新手来说简直就是福音呀。 ECS 就是空白的系统镜像,任何环境都是需要用户自行安装的,这在一定程度上增加了用户使用的学习成本。而 ECS 更多体现的就是专业性了,虽然复杂但是有十分强大的安全组、弹性IP、均衡负载等等。 3、防火墙 轻量的防火墙设置同 ECS 的安全组相比简单、直观更适合新手的使用,没有一些非常复杂的设置,新手看到 udp、tcp 就会不知所措,如果新手看教程的话,等到实际操作,估计也是一头雾水。 4、功能 轻量应用服务器在功能上基本是配套的,安装应用镜像基本上能做到开箱即用,三大金刚 Apache Httpd、PHP、MySQL 也默认是安装好的,而且添加域名、设置 HTTPS 等操作都是一键式的,极大的降低了 HTTPS 入门门槛,而且 HTTPS 的配置跑分是 A 。 5、基础运维 比如说 ECS 控制台操作起来也更加的方便,点击一下就能远程连接了,操作的过程中也支持命令的右键复制和粘贴,也大大降低了用户使用终端控制的学习成本,不然还要下Putty 或者 Xshell 也比较麻烦。 然后默认只支持密钥登录 Linux,不用密码当然更安全。 同样不考虑使用 Putty 或者 Xshell 等 OpenSSH 连接工具的话,ECS控制台的远程终端控制比较复杂,可以借助 DMS 产品扩展使用的灵活性。6、服务器款式 轻量应用服务器款式比较简单,只有四款 Windows系统linux系统 从上图我们可以看出Windows系统和linux系统盘的系统盘容量不同,所以价格也不同,不管是哪种系统,每种配置最主要的是固定的,磁盘无法增减容量和个数,带宽也无法升降,不像ECS可以自由选择,这也是有好有坏的。 看完小编的介绍,大家也都了解了轻量应用服务器的诸多优势,但是他肯定也有一些劣势的。比如说它的轻量,在操作方面是比较简单的,但是其无法持续进行高负载运行(即 CPU 长时间高负载占用),如果我们要利用 轻量应用服务器 来进行持续的渲染、转码、机器学习等持续高负载操作那么就不可以了,也不支持 Nvme 这样的超高性能的 SSD,这对于读写敏感的操作就不好了。 另外Apache Httpd、PHP、MySQL 是会持续更新的,而且它们也均有爆发过大规模严重漏洞的历史,不过目前还没有看到应用镜像中的三大金刚如何升级版本号的姿势。像 WordPress 这样的程序完全可以适应高版本的 PHP,使用 PHP7.0 或者 7.1 可以拥有更高的性能还可以降低负载,但是默认只有 php5.6 可以选,既然使用 php-fpm 方式驱动其实完全可以提供多版本 PHP 的选择。 而且轻量应用服务器的配置是固定的,这也是根据大数据计算出来的适合大部分用户配置的,而且这种配置是标准化的量产,成本较低,也比较稳定,但是它一到要使用 ECS 的企业型来发展性能、功能了,轻量应用服务器的其实也就没有很大的优势了。 总结 轻量应用服务器 是专门针对云计算入门新手或者只有轻量计算需求的用户,其对标的产品只会是 ECS 的共享型,如果你只是轻度使用云计算产品例如搭建一个博客、官网,如果你需要一个更快启动的轻量服务器,如果你需要一个纯粹的虚拟专用机,如果你并没有阿里云内网的需求,那么轻量应用服务器绝对是你最好的选择。当然如果你有更高的服务器要求,就选择ECS吧,毕竟它可以满足绝大部分企业的弹性需求。 好了,分享到此结束。如果大家对轻量应用服务器想要进一步了解的话,可以查阅捷讯技术的官网,在线咨询我们的客服,或者直接拨打027-87498031

优秀的个人博客,低调大师

【Java入门提高篇】Day24 Java容器类详解(七)HashMap源码分析(下)

前两篇对HashMap这家伙的主要方法,主要算法做了一个详细的介绍,本篇主要介绍HashMap中默默无闻地工作着的集合们,包括KeySet,values,EntrySet,以及对应的迭代器:HashIterator,KeyIterator,ValueIterator,EntryIterator和fast-fail 机制。会介绍三个集合的作用以及它们中隐藏的惊人秘密。 KeySet 我们先来看看KeySet,HashMap中的成员变量keySet保存了所有的Key集合,事实上,这是继承自它的父类AbstractMap的成员变量: transient Set<K> keySet; 而keySet方法,也是覆盖了父类的方法: //AbstractMap 中的keySet方法 public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new AbstractSet<K>() { public Iterator<K> iterator() { return new Iterator<K>() { private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public K next() { return i.next().getKey(); } public void remove() { i.remove(); } }; } public int size() { return AbstractMap.this.size(); } public boolean isEmpty() { return AbstractMap.this.isEmpty(); } public void clear() { AbstractMap.this.clear(); } public boolean contains(Object k) { return AbstractMap.this.containsKey(k); } }; keySet = ks; } return ks; } //HashMap 中的keySet方法 /** * 返回一个键值的集合视图,该集合由map支持,因此对map的更改会反映在集合中,反之亦然。 * 如果在对集合进行迭代的过程中修改了map中的映射(除了通过迭代器的删除操作),迭代的结果是未定义的。 * 该集合支持元素删除,通过Iterator.remove,Set.remove,removeAll,retainAll和clear操作 * 从映射中删除相应的映射。 它不支持add或addAll操作。 */ public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } 可以看到,AbstractMap中keySet是一个AbstractSet类型,而覆盖后的keySet方法中,keySet被赋值为KeySet类型。翻翻构造器可以发现,在构造器中并没有初始化keySet,而是在KeySet方法中对keySet进行的初始化(HashMap中都是使用类似的懒加载机制),KeySet是HashMap中的一个内部类,让我们再来看看这个KeySet类型的全貌: final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } } 其实KeySet就是继承自AbstractSet,并覆盖了其中的大部分方法,遍历KeySet时,会使用其中的KeyIterator,至于Spliterator,是为并行遍历设计的,一般是用于Stream的并行操作。forEach方法则是用于遍历操作,将函数式接口操作action应用于每一个元素,我们来看一个小栗子: public class Test { public static void main(String[] args) { Map<String, Integer> map = new HashMap(); map.put("小明", 66); map.put("小李", 77); map.put("小红", 88); map.put("小刚", 89); map.put("小力", 90); map.put("小王", 91); map.put("小黄", 92); map.put("小青", 93); map.put("小绿", 94); map.put("小黑", 95); map.put("小蓝", 96); map.put("小紫", 97); map.put("小橙", 98); map.put("小赤", 99); map.put("Frank", 100); Set<String> ks = map.keySet(); System.out.printf("keySet:%s,keySet的大小:%d,keySet中是否包含Frank:%s", ks, ks.size(), ks.contains("Frank")); System.out.println(); ks.forEach((item) -> System.out.println(item)); } } 输出如下: keySet:[小刚, 小橙, 小蓝, 小力, 小青, 小黑, 小明, 小李, 小王, 小紫, 小红, 小绿, Frank, 小黄, 小赤],keySet的大小:15,keySet中是否包含Frank:true 小刚 小橙 小蓝 小力 小青 小黑 小明 小李 小王 小紫 小红 小绿 Frank 小黄 小赤 如果不记得这个AbstractMap和AbstractSet在容器框架中是什么地位,可以往前翻翻这系列文章的第一篇,看看容器家族的族谱。 但是说了这么多,这个keySet。里面的元素是什么时候放进去的呢?我们自然会想到,大概就是调用put方法往里添加元素的时候,顺便把key放进keySet中,完美!让我们再回顾一下putVal方法,来看看是不是这样的: 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未初始化,则先重新调整大小至初始容量 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //(n-1)& hash 这个地方即根据hash求序号,想了解更多散列相关内容可以查看下一篇 if ((p = tab[i = (n - 1) & hash]) == null) //不存在,则新建节点 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; //先找到对应的node if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) //如果是树节点,则调用相应的putVal方法,这部分放在第三篇内容里 //todo putTreeVal e = ((TreeNode<K,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) // -1 for 1st //如果链表长度达到树化的最大长度,则进行树化,该函数内容也放在第三篇 //todo treeifyBin treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //如果已存在该key的映射,则将值进行替换 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } //修改次数加一 ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } emmmmm,好像没找到?你也许会想,会不会是在TreeNode的putTreeVal方法或者在treeifyBin方法中对key进行插入?好了好了,不要再翻了,其实这个奥秘隐藏在KeySet的迭代器中,再回头看看,它的迭代器返回的是一个KeyIterator,而KeyIterator也是HashMap中的一个内部类,继承自HashMap中的另一个内部类HashIterator。 HashIterator 让我们带着这个疑问,来看看这个HashIterator类里到底有什么玄机: abstract class HashIterator { //指向下一个节点 Node<K,V> next; //当前节点 Node<K,V> current; //为实现 fast-fail 机制而设置的期望修改数 int expectedModCount; //当前遍历到的序号 int index; HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // 移动到第一个非null节点 do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; // fast-fail 机制的实现 即在迭代器往后遍历时,每次都检测expectedModCount是否和modCount相等 // 不相等则抛出ConcurrentModificationException异常 if (modCount != expectedModCount) throw new ConcurrentModificationException(); //如果遍历越界,则抛出NoSuchElementException异常 if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { //如果遍历到末尾,则跳到table中下一个不为null的节点处 do {} while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; //移除节点 removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } } 可以发现,在迭代器中,使用nextNode进行遍历时,先把next引用赋值给current,然后把next.next赋值给next,再获取了外部类HashMap中的table引用(t = table),这样就直接通过遍历table的方式来实现对key,value和entry的读取。 if ((next = (current = e).next) == null && (t = table) != null) { //如果遍历到末尾,则跳到table中下一个不为null的节点处 do {} while (index < t.length && (next = t[index++]) == null); } KeyIterator,ValueIterator,EntryIterator都是HashIterator的子类,实现也很简单,仅仅修改了泛型类型: final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } } final class ValueIterator extends HashIterator implements Iterator<V> { public final V next() { return nextNode().value; } } final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } } 这样keySet在遍历的时候,就可以通过它的迭代器去遍历访问外部类HashMap中的table,类似的,values和entrySet也是使用相似的方式进行遍历。 public Collection<V> values() { Collection<V> vs = values; if (vs == null) { vs = new Values(); values = vs; } return vs; } final class Values extends AbstractCollection<V> { public final int size() { return size; } public final void clear() { this.clear(); } public final Iterator<V> iterator() { return new ValueIterator(); } public final boolean contains(Object o) { return containsValue(o); } public final Spliterator<V> spliterator() { return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super V> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.value); } if (modCount != mc) throw new ConcurrentModificationException(); } } } public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; } final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final int size() { return size; } public final void clear() { this.clear(); } public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator<Map.Entry<K,V>> spliterator() { return new EntrySpliterator<K,V>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super Map.Entry<K,V>> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e); } if (modCount != mc) throw new ConcurrentModificationException(); } } } 至此,这个未解之谜算是告一段落了。 transient 但是,细心的同学可能会发现,HashMap中的table,entrySet,keySet,value等成员变量,都是用transient修饰的,为什么要这样做呢? 首先,我们还是先说说这个transient是干嘛用的,这就要涉及Java中的序列化了,序列化是什么东西呢? Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息。一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输,一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。 当然,就像数据存储是为了读取那样,序列化后的最终目的是为了恢复成原先的Java对象,要不然序列化后干嘛呢,这个过程就叫做反序列化。 当我们使用实现Serializable接口的方式来进行序列化时,所有字段都会被序列化,那如果不想让某个字段被序列化(比如出于安全考虑,不将敏感字段序列化传输),便可以使用transient关键字来标志,表示不想让这个字段被序列化。 那么问题来了,存储节点信息的table用transient修饰了,那么序列化和反序列化的时候,数据还怎么传输??? emmmm,这又涉及到一个蛋疼的操作,序列化并没有那么简单,实现了Serializable接口后,在序列化时,会先检测这个类是否存在writeObject和readObject方法,如果存在,则调用相应的方法: /** * 将HashMap的实例状态保存到一个流中 */ private void writeObject(java.io.ObjectOutputStream s) throws IOException { int buckets = capacity(); // 写出threshold,loadfactor和所有隐藏的成员 s.defaultWriteObject(); s.writeInt(buckets); s.writeInt(size); internalWriteEntries(s); } /** * 从流中重构HashMap实例 */ private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // 读取threshold,loadfactor和所有隐藏的成员 s.defaultReadObject(); reinitialize(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new InvalidObjectException("Illegal load factor: " + loadFactor); // 读取并忽略桶的数量 s.readInt(); // 读取映射的数量 int mappings = s.readInt(); if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + mappings); else if (mappings > 0) { // (如果是0,则使用默认值) // Size the table using given load factor only if within // range of 0.25...4.0 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); float fc = (float)mappings / lf + 1.0f; int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (fc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)fc)); float ft = (float)cap * lf; threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int)ft : Integer.MAX_VALUE); SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap); @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] tab = (Node<K,V>[])new Node[cap]; table = tab; // 读取键值对信息,然后把映射插入HashMap实例中 for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } } 这确实是一个极其糟糕的设计。。。而且这里还是一个private方法。 那么直接使用默认的序列化不好吗?非要大费周章的骚操作一波?一部分原因是为了解决效率问题,因为HashMap中很多桶是空的,将其序列化没有任何意义,所以需要手动使用 writeObject() 方法,只序列化实际存储元素的数组。另一个很重要的原因便是,HashMap的存储是依赖于对象的hashCode的,而Object.hashCode()方法是依赖于具体虚拟机的,所以同一个对象,在不同虚拟机中的HashCode可能不同,那这样映射到的HashMap中的位置也不一样,这样序列化和反序列化的对象就不一样了。引用大神的一段话: For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn't even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt. 蹩脚翻译一下: 例如,考虑散列表的情况。 它的物理存储是一系列包含键值条目的散列桶。 条目驻留的存储区是其密钥的哈希码的函数,通常,JVM的实现不保证相同。 事实上,它甚至不能保证每次运行都是一样的。 因此,接受哈希表的默认序列化形式将构成严重的错误。 对哈希表进行序列化和反序列化可能会产生不变性被严重损毁的对象。 好了,到此为止,这部分内容算是over了,后面会继续介绍HashMap中最麻烦的一部分,TreeNode让我们师母已呆 记得动动小手点个赞或者点个关注哦,如果觉得不错的话,也欢迎分享给你的朋友,让bug传播的更远一些,呸,说错了,让知识传播的更远一些如果写的有误的地方,欢迎大家及时指出,我会第一时间予以修正,也欢迎提出改进建议,之后还会继续更新,欢迎继续关注! 真正重要的东西,用眼睛是看不见的。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册