-
请问各位大佬:javaSDK如何一次性删除不为空(里面有很多对象)的路径。使用deleteObject一直不成功,返回状态码是204 感谢
-
变量是用于存储信息的"容器"。实例var x=5;var y=6;var z=x+y;尝试一下 » 就像代数那样x=5 y=6 z=x+y在代数中,我们使用字母(比如 x)来保存值(比如 5)。通过上面的表达式 z=x+y,我们能够计算出 z 的值为 11。在 JavaScript 中,这些字母被称为变量。lamp您可以把变量看做存储数据的容器。 JavaScript 变量与代数一样,JavaScript 变量可用于存放值(比如 x=5)和表达式(比如 z=x+y)。变量可以使用短名称(比如 x 和 y),也可以使用描述性更好的名称(比如 age, sum, totalvolume)。变量必须以字母开头变量也能以 $ 和 _ 符号开头(不过我们不推荐这么做)变量名称对大小写敏感(y 和 Y 是不同的变量)lampJavaScript 语句和 JavaScript 变量都对大小写敏感。 JavaScript 数据类型JavaScript 变量还能保存其他数据类型,比如文本值 (name="Bill Gates")。在 JavaScript 中,类似 "Bill Gates" 这样一条文本被称为字符串。JavaScript 变量有很多种类型,但是现在,我们只关注数字和字符串。当您向变量分配文本值时,应该用双引号或单引号包围这个值。当您向变量赋的值是数值时,不要使用引号。如果您用引号包围数值,该值会被作为文本来处理。实例var pi=3.14; // 如果你熟悉 ES6,pi 可以使用 const 关键字,表示一个常量// const pi = 3.14;var person="John Doe";var answer='Yes I am!';尝试一下 »声明(创建) JavaScript 变量在 JavaScript 中创建变量通常称为"声明"变量。我们使用 var 关键词来声明变量:var carname;变量声明之后,该变量是空的(它没有值)。如需向变量赋值,请使用等号:carname="Volvo";不过,您也可以在声明变量时对其赋值:var carname="Volvo";在下面的例子中,我们创建了名为 carname 的变量,并向其赋值 "Volvo",然后把它放入 id="demo" 的 HTML 段落中:实例var carname="Volvo";document.getElementById("demo").innerHTML=carname;尝试一下 »lamp一个好的编程习惯是,在代码开始处,统一对需要的变量进行声明。 一条语句,多个变量您可以在一条语句中声明很多变量。该语句以 var 开头,并使用逗号分隔变量即可:var lastname="Doe", age=30, job="carpenter";声明也可横跨多行:var lastname="Doe", age=30, job="carpenter";一条语句中声明的多个变量不可以同时赋同一个值:var x,y,z=1;x,y 为 undefined, z 为 1。Value = undefined在计算机程序中,经常会声明无值的变量。未使用值来声明的变量,其值实际上是 undefined。在执行过以下语句后,变量 carname 的值将是 undefined:var carname;重新声明 JavaScript 变量如果重新声明 JavaScript 变量,该变量的值不会丢失:在以下两条语句执行后,变量 carname 的值依然是 "Volvo":var carname="Volvo"; var carname;
-
Java中有 8 种基本数据类型,分别为:6 种数字类型 (四个整数形,两个浮点型):byte、short、int、long、float、double1 种字符类型:char1 种布尔型:boolean。byte:byte 数据类型是8位、有符号的,以二进制补码表示的整数;最小值是 -128(-2^7);最大值是 127(2^7-1);默认值是 0;byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;例子:byte a = 100,byte b = -50。short:short 数据类型是 16 位、有符号的以二进制补码表示的整数最小值是 -32768(-2^15);最大值是 32767(2^15 - 1);Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;默认值是 0;例子:short s = 1000,short r = -20000。int:int 数据类型是32位、有符号的以二进制补码表示的整数;最小值是 -2,147,483,648(-2^31);最大值是 2,147,483,647(2^31 - 1);一般地整型变量默认为 int 类型;默认值是 0 ;例子:int a = 100000, int b = -200000。long:注意:Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析long 数据类型是 64 位、有符号的以二进制补码表示的整数;最小值是 -9,223,372,036,854,775,808(-2^63);最大值是 9,223,372,036,854,775,807(2^63 -1);这种类型主要使用在需要比较大整数的系统上;默认值是 0L;例子: long a = 100000L,Long b = -200000L。"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。float:float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;float 在储存大型浮点数组的时候可节省内存空间;默认值是 0.0f;浮点数不能用来表示精确的值,如货币;例子:float f1 = 234.5f。double:double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;浮点数的默认类型为double类型;double类型同样不能表示精确的值,如货币;默认值是 0.0d;例子:double d1 = 123.4。char:char类型是一个单一的 16 位 Unicode 字符;最小值是 \u0000(即为 0);最大值是 \uffff(即为 65535);char 数据类型可以储存任何字符;例子:char letter = 'A';(单引号)boolean:boolean数据类型表示一位的信息;只有两个取值:true 和 false;这种类型只作为一种标志来记录 true/false 情况;默认值是 false;例子:boolean one = true。这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean
-
【操作步骤&问题现象】1、添加采样分析报错,且采样状态卡死【截图信息】
-
【操作步骤&问题现象】1、连接目标环境 非root账户 报联系管理2、具体需要那个写文件权限【截图信息】
-
【操作步骤&问题现象】1、工具与目标环境为一个服务器2、连接超时3、部署分析辅助软件什么意思【截图信息】
-
1 调优概述华为鲲鹏性能优化工具是一款针对鲲鹏平台的性能调优工具,包含系统性能优化工具和Java性能优化工具以及诊断调试工具。本文使用Java性能优化工具对运行中的Java程序进行在线分析和采样分析,找到程序问题,并根据分析结果进行优化修改,从而实现Java程序最佳运行。2 环境要求项目说明服务器TaiShan 200 服务器(型号2280)CPU鲲鹏920 4826处理器OSCentOS 7.6调优工具华为鲲鹏性能优化工具 V2.2.13 前提条件服务器和操作系统正常运行。PC端已经安装SSH远程登录工具需要优化的Java程序4 调优思路 使用Java性能优化工具对Java进程进行在线分析; 使用Java性能优化工具对Java进程进行采样分析 针对性能的瓶颈点进行性能优化; 观察优化后的Java程序,判断问题是否解决 5 调优过程5.1 对程序进行在线分析,在概览页签下发现程序的CPU负载一直比较高这时候想知道程序具体在做什么事情,或者是否有热点函数在执行。 5.2 对程序进行采样分析,采样时长可以设置长一点5.3 打开采样分析报告->概览页签从CPU的利用率上可以看出,JVM用户态占大头,说明cpu主要执行java代码。如果用户态占大头,说明cpu更多的执行系统相关的操作5.4 切换到CPU->方法采样从java方法采样的火焰图可以看出在采样期间,程序的方法采样主要落在两个函数调用处,可以间接反映出cpu消耗在这两个函数的时间会比较多继续点开火焰图查看具体信息,通过函数的全限定名称可以辨别发现的这两个函数是用户的自研代码。图类型切换到调用树,可以发现跟火焰图一样的结果,采样主要落在两个函数。5.5 发现问题并解决根据火焰图和调用树提供的热点函数的类的全限定名称,去代码中搜索对应的函数,进而分析业务逻辑。实际用户程序的方法火焰图会比较复杂,需要用户去一步一步找出自己想要的目标函数。6 实践总结Java中的热点函数定位,主要通过函数火焰图去定位,然后通过火焰图提供的类的全限定名去搜索业务代码,进而进行代码的优化。在进行其他程序调优时,需要根据华为鲲鹏性能优化工具采集分析的实际结果和对应的优化建议进行调优操作。具体的调优思路可以参考本次实践。
-
1 调优概述华为鲲鹏性能优化工具是一款针对鲲鹏平台的性能调优工具,包含系统性能优化工具和Java性能优化工具以及诊断调试工具。本文使用Java性能优化工具对运行中的Java程序进行在线分析和采样分析,找到程序问题,并根据分析结果进行优化修改,从而实现Java程序最佳运行。2 环境要求项目说明服务器TaiShan 200 服务器(型号2280)CPU鲲鹏920 4826处理器OSCentOS 7.6调优工具华为鲲鹏性能优化工具 V2.2.13 前提条件服务器和操作系统正常运行。PC端已经安装SSH远程登录工具需要优化的Java程序4 调优思路 使用Java性能优化工具对Java进程进行在线分析; 使用Java性能优化工具对Java进程进行采样分析 针对性能的瓶颈点进行性能优化; 观察优化后的Java程序,判断问题是否解决5 调优过程5.1 对程序进行在线分析,在概览页签下观察每种状态的线程数量发现有大量线程处于阻塞状态,有锁竞争的可能 发现有大量线程处于等待状态,有锁竞争的可能5.2 切换到CPU->线程列表,执行多次线程转储操作 5.3 切换到线程转储页签观察多个时间点线程转储的锁分析图,发现有多个线程同时竞争同一把锁,的确出现了锁竞争的情况,还可以发现这把锁不是JUC提供的工具锁,程序中使用的是 synchronized,如果锁竞争比较激烈,该类锁就会升级成重量级,会导致上下文切换比较严重,从而影响性能和关键业务的阻塞。 如果程序使用的是JUC提供的锁,锁分析图如下 5.4 切换到线程转储->原始数据根据从锁分析图中找到的对应锁的地址,在原始数据中进行搜索,可以看出有多个线程在获取这把锁,这把锁的类型以及竞争这把锁的代码位置 5.5 发现问题并解决根据线程转储中提供的锁的类型和位置,咱们就可以分析代码的业务逻辑,从而找到调优方案。下面介绍两种锁竞争的调优方法,仅供参考。情况一:使用synchronized,但在业务上竞争比较激烈将 synchronized 替换成 JUC 提供的工具锁(ReentrantLock等)情况二:使用JUC 提供的工具锁,但在业务上竞争比较激烈这种情况需要加入其他策略去提高性能,如加入线程池,限制竞争锁的线程数;如果是读写分离的场景,可以考虑使用ReadWriteLock。从业务的实际情况去考虑。6 实践总结Java中的锁竞争问题一旦发生很难定位具体的代码位置,因为程序干扰因素比较多,但是可以根据线程转储去定位,其中的栈追踪信息提供了发生锁竞争的代码位置。 如果发现多个线程都试图锁住相同的锁地址,说明应用正面临锁竞争,在进行其他程序调优时,需要根据华为鲲鹏性能优化工具采集分析的实际结果和对应的优化建议进行调优操作。具体的调优思路可以参考本次实践。
-
1 调优概述华为鲲鹏性能优化工具是一款针对鲲鹏平台的性能调优工具,包含系统性能优化工具和Java性能优化工具。本文使用Java性能优化工具对运行中的Java程序进行在线分析和采样分析,找到程序问题,并根据分析结果进行优化修改,从而实现Java程序最佳运行。2 环境要求项目说明服务器TaiShan 200 服务器(型号2280)CPU鲲鹏920 4826处理器OSCentOS 7.6调优工具华为鲲鹏性能优化工具 V2.3.T103 前提条件服务器和操作系统正常运行。PC端已经安装SSH远程登录工具。需要优化的Java程序且程序开启GC日志打印。4 调优思路 使用Java性能优化工具对Java进程GC日志进行在线采集或离线上传,解析获取的GC日志。 针对解析后得到的优化建议项进行调优。 观察优化后的Java程序日志,判断对应问题是否改善。 5 调优过程5.1 获取GC日志5.1.1 对程序进行在线分析,在GC页签下GC日志页面点击执行GC日志采集,获取日志并进行解析。5.2 查看优化建议项当前GC日志共有五项优化建议:依次查看对应优化建议,并根据建议项进行信息修改。5.2.1 元空间扩容导致的GC优化jdk8以后移除永久区,使用本地内存来存储类元数据信息并称之为元空间(Metaspace)。持续的元空间垃圾回收说明:可能存在类、类加载器导致的内存泄漏或是大小设置不合适。根据优化建议对代码进行排查,发现代码中存在每次调用循环动态创建并加载类。针对本例:可减循环次数,减少类的加载;将classes对象设为单例模式避免重复调用。对于无法进行代码优化的案例结合成因分析,查看因元数据产生的GC次数及时间占比是否偏高,若想降低可调高-XX:MetaspaceSize,少因扩容造成的GC。5.2.2 疏散失败当给出疏散失败时,表示存在GC幸存区或Old区空间不足,可增大响应空间避免出现疏散失败。当空间无法增加时可尝试调整如下选项:1、调大-XX:G1ReservePercent(默认值10),设置堆老年代预留内存晋升,以降低提升失败的可能性;降低-XX:InitiatingHeapOccupancyPercent阈值(默认45);3、增加并行标记线程的数目,调大-XX:ParallelGCThreads参数(默认值随运行平台不同而不同)。可在优化后重新执行GC日志采集,查看疏散失败优化建议是否触发。或在日志中统计单位时间内疏散失败的比例。5.2.3 发生巨型对象导致的GC当发生巨型对象分配时(大于region size的50%),G1会找出一个连续的可用分区集合,这样就能汇总出足够的内存来容纳巨型对象。如果没有足够的连续可用空间,G1就会启动一次Full GC 来压缩Java堆空间。巨对象分配后对应region不再存储其它对象造成了一定的空间浪费。通过调大-XX:G1HeapRegionSize可减少因巨对象空间不足导致的Full GC且能提升内存利用率。可通过GC成因分析查看G1 Humongous Allocation触发的GC占比统计看巨对象触发的GC是否由改善。5.2.4 发生Full GC当空间即将耗尽或分配速度无法追上回收速度时就会触发Full GC,当有Full GC发生代表性能可能不足。除关注程序本身是否可优化或提升机器性能(增加内存,提升cpu性能)外,还可以尝试调整相关参数提高GC效率来降低Full GC发生的可能性:1、增加堆内存(Xmx)大小,让G1有更多的时间去完成 Concurrent Marking。2、适当加大 -XX:ConcGCThreads 选项的值,增加并发标记的线程数。3、通过增加 -XX:G1ReservePercent(默认10%)的选项值,增加 G1 在 IHOP 分析过程中所需要的内存空间。4、通过设置 -XX:-G1UseAdaptiveIHOP 的选项值禁用自适应 IHOP 分析机制,通过调小 -XX:InitiatingHeapOccupancyPercent 的选项(默认值为 45)值达到提前触发GC标记周期的目的。可对比优化前与优化后的GC活动细化分析数据下的Full GC统计数据,通过对Full GC平均间隔时间对比可确定Full GC频率是否降低。5.2.5 发生System.GC()导致的GC程序中不建议显示调用System.GC()强制触发Full GC。建议排查代码将相关代码移除。或使用-XX:+DisableExplicitGC忽略System.GC()调用。若为系统级别或框架级别的调用无法禁用,可开启-XX:+ExplicitGCInvokesConcurrent,Full GC 会变成 initial-mark。检验优化效果可对优化后程序进行日志采集查看优化建议中是否还有system.GC()的优化建议。或查看GC日志看system.GC()触发日志是否存在,若存在看是否为initial-mark阶段6 实践总结Java中通过查看GC日志较难直接定位问题,故平台提供的GC日志解析后优化建议,通过优化建议可较为便捷的对代码进行排查或修改启动参数。重启进程并在运作一段时间后再次采集解析GC日志,通过对比前后日志吞吐量、GC平均暂停时间、活动细化分析-平均间隔时间等数据的变化,从而确定GC效率是否已经改善。针对GC日志的调优不建议作为优先使用方案,可作为在程序自身调优已达瓶且无更多调优手段时的尝试。据GC优化建议对程序进行优化后无明显改善的,建议:升级机器配置以适应业务需求!
-
rmic功能说明: rmic 为远程对象生成 stub 和 skeleton。 语法: rmic [ options ] package-qualified-class-name(s)补充说明: rmic 编译器根据编译后的 Java 类(含有远程对象实现)名,为远程对象生成 stub 和 skeleton(远程对象是指实现 java.rmi.Remote 接口的对象)。在 rmic 命令中所给的类必须是经 javac 命令成功编译且是完全包限定的类。 命令选项 -classpath[路径] 指定 rmic 用于查询类的路径。如果设置了该选项,它将覆盖缺省值或 CLASSPATH 环境变量。目录用冒号分隔。 -d[目录] 指定类层次的根目录。此选项可用来指定 stub 和 skeleton 文件的目标目录。 -depend 使编译器考虑重新编译从其它类引用的类。 一般来说,它只重新编译从源代码引用的遗漏或过期的类。 -g 允许生成调试表格。调试表格含有行号和局部变量的有关信息,即 Java 调试工具所使用的信息。缺省情况下,只生成行号。 -J 与 -D 选项联用,它将紧跟其后的选项( -J 与 -D 之间无空格)传给 java 解释器。 -keepgenerated 为 stub 和 skeleton 文件保留所生成的 .java 源文件,并将这些源文件写到与 .class 文件相同的目录中,如果要指定目录,则使用 -d 选项。 -nowarn 关闭警告。如果使用该选项,则编译器不输出任何警告信息。 -show 显示 rmic 编译器的 GUI(图形用户界面)。输入一个或多个包限定类名(以空格分隔),并按回车键或“显示”按钮,创建 stub 和 skeleton。 -vcompat (缺省值)创建与 JDK 1.1 和 1.2 stub 协议版本都兼容的 stub 和 skeleton。 -verbose 使编译器和链接器输出关于正在编译哪些类和正在加载哪些类文件的信息。 -v1.1 创建 JDK 1.1 stub 协议版本的 stub 和 skeleton。 -v1.2 只创建 JDK 1.2 stub 协议版本的 stub。rmid功能说明: rmid 启动激活系统守护进程,以便能够在 Java 虚拟机上注册和激活对象。语法: rmid [-port port] [-log dir]补充说明: rmid 工具启动激活系统守护进程。必须先启动激活系统守护进程,才能向激活系统注册可被激活的对象或在 Java 虚拟机上激活可被激活的对象。命令选项 -C<某些命令行选项> 指定一个选项,在创建每个 rmid 的子守护进程(激活组)时,该选项以命令行参数的形式传给该子守护进程。 -log[目录] 指定目录的名称,激活系统守护进程在该目录中写入其数据库及相关信息。缺省状态下,将在执行 rmid 命令的目录中创建一个 log 目录。 -port[端口] 指定 rmid 的注册服务程序所使用的端口。激活系统守护进程将 ActivationSystem 与该注册服务程序中的名称java.rmi.activation.ActivationSystem 捆绑在一起。 -stop 停止 -port 选项所指定端口上的当前 rmid 调用。若未指定端口,则将停止在端口 1098 上运行的 rmid。rmiregistry功能说明: rmiregistry 命令可在当前主机的指定端口上启动远程对象注册服务程序。语法: rmiregistry [port]补充说明: rmiregistry 命令在当前主机的指定 port 上创建并启动远程对象注册服务程序。如果省略 port,则注册服务程序将在 1099 端口上启动。rmiregistry 命令不产生任何输出而且一般在后台运行。远程对象注册服务程序是自举命名服务。主机上的 RMI 服务器将利用它将远程对象绑定到名字上。客户机即可查询远程对象并进行远程方法调用。注册服务程序一般用于定位应用程序需调用其方法的第一个远程对象。该对象反过来对各应用程序提供相应的支持,用于查找其它对象。
-
为了巩固大家的学习成果,紧跟学习进度为大家提供学习交流的平台,推出问答官排位赛活动按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦打卡时间2021.07.06-2021.08.09 23:59 参与方式:用户在本帖里发布自己在Java学习中产生的疑惑或实践问题,其他用户可通过在楼层下评论参与回答。 参与规则:1. 同一ID不可自问自答;2. 同一ID可回复其他同一ID问题数量需≤3次,如,A最多可回答B的三个问题,但A可回答B、C、D、E、F等多人次问题。 活动奖励:◎每个有效提问可获得5积分。上限为2个问题,最高10分。◎每个有效回答可获得2积分,上限为2个回答,最高4分。不得灌水,不得与他人显示,问题具有意义,占楼无效。有效提问示例:无效提问示例:◎最终会全部每阶段参与互动的用户中,抽取3位幸运用户,送出旅行本套装*1活动奖励点击了解Java编程创造营专属福利活动注意事项1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成提交问答官问题,可获得的积分上限为5积分/每有效问题(最高10分)2积分/每有答复(最高4分);3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761&page=1&extra=#pid1161471
-
为了巩固大家的学习成果,紧跟学习进度请将需要完成的相关技术文章内容按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~打卡时间2021.07.06-2021.08.09 23:59征集要求1.本学习阶段任意时间内,在【华为云】-【博客】发表与Java相关的任意博客内容,可以是开发技术技巧,可以是开发心得体会,不可以发布课程笔记作为博文,将文章链接回复至本帖内2.文章需为原创,在发布博客时,需要勾选文章创作类型:原创文章一项。用户承诺,发表的文章不存在任何知识产权问题并自行承担全部责任3.文章字数≥600字,图文并茂,不得抄袭网站课程,需写自己的理解和技术分享4.回复格式:华为云ID+博客文章链接5.如果曾在外部博客发表过的文章,可将文章迁移到华为云博客即可6.技术文章由华为技术专家评审,评审通过会对应的积分,不过专家会给出对应的评语且积分为0奖励方式每阶段每篇有效博客文章可获得5积分,每阶段获取上限10分,每阶段评选1篇最佳博文,奖励定制机械键盘~ 全系列活动奖励本次活动不仅免费跟大牛学习技术,更有积分大礼等着大家~每次有效提交的学习任务都会得到对应积分,赢取智能硬件,还有华为无线耳机、机械键盘等你拿!点击了解Java编程创造营专属福利活动注意事项1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成客观题打卡任务,可获得的积分上限为5分/每篇;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
-
为了巩固大家的学习成果,紧跟学习进度请按需要完成的每周读书笔记内容按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~打卡时间2021.07.06-2021.08.09 23:59打卡要求在本帖中,回复每周读书笔记和心得 ↓回复格式:华为云ID+第几周笔记+笔记内容,并分享些读书心得(字数≥200字)回复示例如下:奖励方式每周每次有效打卡可获得2积分阶段学习将以积分排行榜的形式,对符合条件的小伙伴进行奖励!点击了解Java编程创造营专属福利活动注意事项 由于本帖回复内容为随堂测试结果,已设置回帖仅版主可见1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成提交学习笔记任务,可获得的积分上限为2分/每周;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
-
【Java编程创造营】第二阶段正在进行中为了巩固大家的学习成果,紧跟学习进度请将需要完成的每章随堂测验内容按要求回复到本帖下方按要求格式回复即可获得积分累计阶段奖品,还能有机会获得附加幸运奖哦~打卡时间2021.07.06-2021.08.09 23:59打卡要求在本帖中,回复对应章节的随堂测验并提交截图 ↓回复格式:自己的华为云ID+课堂章节序号+课程完成截图(露出右上角华为云ID,随堂测验为绿色对勾完成状态)回复示例:华为云帐号:grandmaster+第一章 测试题截图奖励方式每章节每次有效打卡可获得2积分阶段学习将以积分排行榜的形式,对符合条件的小伙伴进行奖励!附加福利:每周在本周内提交随堂测试的用户中,抽取1名幸运奖励华为云定制鼠标*1点击了解Java编程创造营专属福利活动注意事项 由于本帖回复内容为随堂测试结果,已设置回帖仅版主可见1. 学习任务提交后,小助手会在本阶段学习周期内,按序完成审核,并增加活动积分;2. 本次活动通过完成每章习题打卡任务,可获得的积分上限为2分/每章节;3. 请务必按照上述要求提交内容,以免影响积分增加;4. 若积分值相同则以完成学习任务的时间先后排序,其中任务完成时间的判定优先级为:相关微认证>阶段沙箱实验>每周学习笔记>问答官排位赛>每章随堂测验打卡5. 其他积分获取方式请查看活动社群公告。想了解更多关于课程内容请移步主帖:https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=125761
-
【摘要】 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。前言从1995年GoF提出23种设计模式到现在,25年过去了,设计模式依旧是软件领域的热门话题。在当下,如果你不会一点设计模式,都不好意思说自己是一个合格的程序员。设计模式通常被定义为:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。从定义上看,设计模式其实是一种经验的总结,是针对特定问题的简洁而优雅的解决方案。既然是经验总结,那么学习设计模式最直接的好处就在于可以站在巨人的肩膀上解决软件开发过程中的一些特定问题。然而,学习设计模式的最高境界是习得其中解决问题所用到的思想,当你把它们的本质思想吃透了,也就能做到即使已经忘掉某个设计模式的名称和结构,也能在解决特定问题时信手拈来。好的东西有人吹捧,当然也会招黑。设计模式被抨击主要因为以下两点:1、设计模式会增加代码量,把程序逻辑变得复杂。这一点是不可避免的,但是我们并不能仅仅只考虑开发阶段的成本。最简单的程序当然是一个函数从头写到尾,但是这样后期的维护成本会变得非常大;而设计模式虽然增加了一点开发成本,但是能让人们写出可复用、可维护性高的程序。引用《软件设计的哲学》里的概念,前者就是战术编程,后者就是战略编程,我们应该对战术编程Say No!(请移步《专家教你系列<三>:写出的代码复杂度太高?看下专家怎么说》)2、滥用设计模式。这是初学者最容易犯的错误,当学到一个模式时,恨不得在所有的代码都用上,从而在不该使用模式的地方刻意地使用了模式,导致了程序变得异常复杂。其实每个设计模式都有几个关键要素:适用场景、解决方法、优缺点。模式并不是万能药,它只有在特定的问题上才能显现出效果。所以,在使用一个模式前,先问问自己,当前的这个场景适用这个模式吗?《设计模式》一书的副标题是“可复用面向对象软件的基础”,但并不意味着只有面向对象语言才能使用设计模式。模式只是一种解决特定问题的思想,跟语言无关。就像Go语言一样,它并非是像C++和Java一样的面向对象语言,但是设计模式同样适用。本系列文章将使用Go语言来实现GoF提出的23种设计模式,按照创建型模式(Creational Pattern)、结构型模式(Structural Pattern)和行为型模式(Behavioral Pattern)三种类别进行组织,文本主要介绍其中的创建型模式。单例模式(Singleton Pattern)简述单例模式算是23中设计模式里最简单的一个了,它主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。在程序设计中,有一些对象通常我们只需要一个共享的实例,比如线程池、全局缓存、对象池等,这种场景下就适合使用单例模式。但是,并非所有全局唯一的场景都适合使用单例模式。比如,考虑需要统计一个API调用的情况,有两个指标,成功调用次数和失败调用次数。这两个指标都是全局唯一的,所以有人可能会将其建模成两个单例SuccessApiMetric和FailApiMetric。按照这个思路,随着指标数量的增多,你会发现代码里类的定义会越来越多,也越来越臃肿。这也是单例模式最常见的误用场景,更好的方法是将两个指标设计成一个对象ApiMetric下的两个实例ApiMetic success和ApiMetic fail。如何判断一个对象是否应该被建模成单例?通常,被建模成单例的对象都有“中心点”的含义,比如线程池就是管理所有线程的中心。所以,在判断一个对象是否适合单例模式时,先思考下,这个对象是一个中心点吗?Go实现在对某个对象实现单例模式时,有两个点必须要注意:(1)限制调用者直接实例化该对象;(2)为该对象的单例提供一个全局唯一的访问方法。对于C++/Java而言,只需把类的构造函数设计成私有的,并提供一个static方法去访问该类点唯一实例即可。但对于Go语言来说,即没有构造函数的概念,也没有static方法,所以需要另寻出路。我们可以利用Go语言package的访问规则来实现,将单例结构体设计成首字母小写,就能限定其访问范围只在当前package下,模拟了C++/Java中的私有构造函数;再在当前package下实现一个首字母大写的访问函数,就相当于static方法的作用了。在实际开发中,我们经常会遇到需要频繁创建和销毁的对象。频繁的创建和销毁一则消耗CPU,二则内存的利用率也不高,通常我们都会使用对象池技术来进行优化。考虑我们需要实现一个消息对象池,因为是全局的中心点,管理所有的Message实例,所以将其实现成单例,实现代码如下: package msgpool ... // 消息池 type messagePool struct { pool *sync.Pool } // 消息池单例 var msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } // 访问消息池单例的唯一方法 func Instance() *messagePool { return msgPool } // 往消息池里添加消息 func (m *messagePool) AddMsg(msg *Message) { m.pool.Put(msg) } // 从消息池里获取消息 func (m *messagePool) GetMsg() *Message { return m.pool.Get().(*Message) } ...测试代码如下: package test ... func TestMessagePool(t *testing.T) { msg0 := msgpool.Instance().GetMsg() if msg0.Count != 0 { t.Errorf("expect msg count %d, but actual %d.", 0, msg0.Count) } msg0.Count = 1 msgpool.Instance().AddMsg(msg0) msg1 := msgpool.Instance().GetMsg() if msg1.Count != 1 { t.Errorf("expect msg count %d, but actual %d.", 1, msg1.Count) } } // 运行结果 === RUN TestMessagePool --- PASS: TestMessagePool (0.00s) PASS以上的单例模式就是典型的“饿汉模式”,实例在系统加载的时候就已经完成了初始化。对应地,还有一种“懒汉模式”,只有等到对象被使用的时候,才会去初始化它,从而一定程度上节省了内存。众所周知,“懒汉模式”会带来线程安全问题,可以通过普通加锁,或者更高效的双重检验锁来优化。对于“懒汉模式”,Go语言有一个更优雅的实现方式,那就是利用sync.Once,它有一个Do方法,其入参是一个方法,Go语言会保证仅仅只调用一次该方法。 // 单例模式的“懒汉模式”实现 package msgpool ... var once = &sync.Once{} // 消息池单例,在首次调用时初始化 var msgPool *messagePool // 全局唯一获取消息池pool到方法 func Instance() *messagePool { // 在匿名函数中实现初始化逻辑,Go语言保证只会调用一次 once.Do(func() { msgPool = &messagePool{ // 如果消息池里没有消息,则新建一个Count值为0的Message实例 pool: &sync.Pool{New: func() interface{} { return &Message{Count: 0} }}, } }) return msgPool } ...建造者模式(Builder Pattern)简述在程序设计中,我们会经常遇到一些复杂的对象,其中有很多成员属性,甚至嵌套着多个复杂的对象。这种情况下,创建这个复杂对象就会变得很繁琐。对于C++/Java而言,最常见的表现就是构造函数有着长长的参数列表: MyObject obj = new MyObject(param1, param2, param3, param4, param5, param6, ...)而对于Go语言来说,最常见的表现就是多层的嵌套实例化: obj := &MyObject{ Field1: &Field1 { Param1: &Param1 { Val: 0, }, Param2: &Param2 { Val: 1, }, ... }, Field2: &Field2 { Param3: &Param3 { Val: 2, }, ... }, ... }上述的对象创建方法有两个明显的缺点:(1)对对象使用者不友好,使用者在创建对象时需要知道的细节太多;(2)代码可读性很差。针对这种对象成员较多,创建对象逻辑较为繁琐的场景,就适合使用建造者模式来进行优化。建造者模式的作用有如下几个:1、封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑。2、可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建。3、对多个对象复用同样的对象创建逻辑。其中,第1和第2点比较常用,下面对建造者模式的实现也主要是针对这两点进行示例。Go实现考虑如下的一个Message结构体,其主要有Header和Body组成: package msg ... type Message struct { Header *Header Body *Body } type Header struct { SrcAddr string SrcPort uint64 DestAddr string DestPort uint64 Items map[string]string } type Body struct { Items []string } ...如果按照直接的对象创建方式,创建逻辑应该是这样的: // 多层的嵌套实例化 message := msg.Message{ Header: &msg.Header{ SrcAddr: "192.168.0.1", SrcPort: 1234, DestAddr: "192.168.0.2", DestPort: 8080, Items: make(map[string]string), }, Body: &msg.Body{ Items: make([]string, 0), }, } // 需要知道对象的实现细节 message.Header.Items["contents"] = "application/json" message.Body.Items = append(message.Body.Items, "record1") message.Body.Items = append(message.Body.Items, "record2")虽然Message结构体嵌套的层次不多,但是从其创建的代码来看,确实存在对对象使用者不友好和代码可读性差的缺点。下面我们引入建造者模式对代码进行重构: package msg ... // Message对象的Builder对象 type builder struct { once *sync.Once msg *Message } // 返回Builder对象 func Builder() *builder { return &builder{ once: &sync.Once{}, msg: &Message{Header: &Header{}, Body: &Body{}}, } } // 以下是对Message成员对构建方法 func (b *builder) WithSrcAddr(srcAddr string) *builder { b.msg.Header.SrcAddr = srcAddr return b } func (b *builder) WithSrcPort(srcPort uint64) *builder { b.msg.Header.SrcPort = srcPort return b } func (b *builder) WithDestAddr(destAddr string) *builder { b.msg.Header.DestAddr = destAddr return b } func (b *builder) WithDestPort(destPort uint64) *builder { b.msg.Header.DestPort = destPort return b } func (b *builder) WithHeaderItem(key, value string) *builder { // 保证map只初始化一次 b.once.Do(func() { b.msg.Header.Items = make(map[string]string) }) b.msg.Header.Items[key] = value return b } func (b *builder) WithBodyItem(record string) *builder { b.msg.Body.Items = append(b.msg.Body.Items, record) return b } // 创建Message对象,在最后一步调用 func (b *builder) Build() *Message { return b.msg }测试代码如下: package test ... func TestMessageBuilder(t *testing.T) { // 使用消息建造者进行对象创建 message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() if message.Header.SrcAddr != "192.168.0.1" { t.Errorf("expect src address 192.168.0.1, but actual %s.", message.Header.SrcAddr) } if message.Body.Items[0] != "record1" { t.Errorf("expect body item0 record1, but actual %s.", message.Body.Items[0]) } } // 运行结果 === RUN TestMessageBuilder --- PASS: TestMessageBuilder (0.00s) PASS从测试代码可知,使用建造者模式来进行对象创建,使用者不再需要知道对象具体的实现细节,代码可读性也更好。工厂方法模式(Factory Method Pattern)简述工厂方法模式跟上一节讨论的建造者模式类似,都是将对象创建的逻辑封装起来,为使用者提供一个简单易用的对象创建接口。两者在应用场景上稍有区别,建造者模式更常用于需要传递多个参数来进行实例化的场景。使用工厂方法来创建对象主要有两个好处:1、代码可读性更好。相比于使用C++/Java中的构造函数,或者Go中的{}来创建对象,工厂方法因为可以通过函数名来表达代码含义,从而具备更好的可读性。比如,使用工厂方法productA := CreateProductA()创建一个ProductA对象,比直接使用productA := ProductA{}的可读性要好。2、与使用者代码解耦。很多情况下,对象的创建往往是一个容易变化的点,通过工厂方法来封装对象的创建过程,可以在创建逻辑变更时,避免霰弹式修改。工厂方法模式也有两种实现方式:(1)提供一个工厂对象,通过调用工厂对象的工厂方法来创建产品对象;(2)将工厂方法集成到产品对象中(C++/Java中对象的static方法,Go中同一package下的函数)Go实现考虑有一个事件对象Event,分别有两种有效的时间类型Start和End: package event ... type Type uint8 // 事件类型定义 const ( Start Type = iota End ) // 事件抽象接口 type Event interface { EventType() Type Content() string } // 开始事件,实现了Event接口 type StartEvent struct{ content string } ... // 结束事件,实现了Event接口 type EndEvent struct{ content string } ...1、按照第一种实现方式,为Event提供一个工厂对象,具体代码如下: package event ... // 事件工厂对象 type Factory struct{} // 更具事件类型创建具体事件 func (e *Factory) Create(etype Type) Event { switch etype { case Start: return &StartEvent{ content: "this is start event", } case End: return &EndEvent{ content: "this is end event", } default: return nil } }测试代码如下: package test ... func TestEventFactory(t *testing.T) { factory := event.Factory{} e := factory.Create(event.Start) if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = factory.Create(event.End) if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEventFactory --- PASS: TestEventFactory (0.00s) PASS2、按照第二种实现方式,分别给Start和End类型的Event单独提供一个工厂方法,代码如下: package event ... // Start类型Event的工厂方法 func OfStart() Event { return &StartEvent{ content: "this is start event", } } // End类型Event的工厂方法 func OfEnd() Event { return &EndEvent{ content: "this is end event", } }测试代码如下: package event ... func TestEvent(t *testing.T) { e := event.OfStart() if e.EventType() != event.Start { t.Errorf("expect event.Start, but actual %v.", e.EventType()) } e = event.OfEnd() if e.EventType() != event.End { t.Errorf("expect event.End, but actual %v.", e.EventType()) } } // 运行结果 === RUN TestEvent --- PASS: TestEvent (0.00s) PASS抽象工厂模式(Abstract Factory Pattern)简述在工厂方法模式中,我们通过一个工厂对象来创建一个产品族,具体创建哪个产品,则通过swtich-case的方式去判断。这也意味着该产品组上,每新增一类产品对象,都必须修改原来工厂对象的代码;而且随着产品的不断增多,工厂对象的职责也越来越重,违反了单一职责原则。抽象工厂模式通过给工厂类新增一个抽象层解决了该问题,如上图所示,FactoryA和FactoryB都实现·抽象工厂接口,分别用于创建ProductA和ProductB。如果后续新增了ProductC,只需新增一个FactoryC即可,无需修改原有的代码;因为每个工厂只负责创建一个产品,因此也遵循了单一职责原则。Go实现考虑需要如下一个插件架构风格的消息处理系统,pipeline是消息处理的管道,其中包含了input、filter和output三个插件。我们需要实现根据配置来创建pipeline ,加载插件过程的实现非常适合使用工厂模式,其中input、filter和output三类插件的创建使用抽象工厂模式,而pipeline的创建则使用工厂方法模式。各类插件和pipeline的接口定义如下: package plugin ... // 插件抽象接口定义 type Plugin interface {} // 输入插件,用于接收消息 type Input interface { Plugin Receive() string } // 过滤插件,用于处理消息 type Filter interface { Plugin Process(msg string) string } // 输出插件,用于发送消息 type Output interface { Plugin Send(msg string) } package pipeline ... // 消息管道的定义 type Pipeline struct { input plugin.Input filter plugin.Filter output plugin.Output } // 一个消息的处理流程为 input -> filter -> output func (p *Pipeline) Exec() { msg := p.input.Receive() msg = p.filter.Process(msg) p.output.Send(msg) }接着,我们定义input、filter、output三类插件接口的具体实现: package plugin ... // input插件名称与类型的映射关系,主要用于通过反射创建input对象 var inputNames = make(map[string]reflect.Type) // Hello input插件,接收“Hello World”消息 type HelloInput struct {} func (h *HelloInput) Receive() string { return "Hello World" } // 初始化input插件映射关系表 func init() { inputNames["hello"] = reflect.TypeOf(HelloInput{}) } package plugin ... // filter插件名称与类型的映射关系,主要用于通过反射创建filter对象 var filterNames = make(map[string]reflect.Type) // Upper filter插件,将消息全部字母转成大写 type UpperFilter struct {} func (u *UpperFilter) Process(msg string) string { return strings.ToUpper(msg) } // 初始化filter插件映射关系表 func init() { filterNames["upper"] = reflect.TypeOf(UpperFilter{}) } package plugin ... // output插件名称与类型的映射关系,主要用于通过反射创建output对象 var outputNames = make(map[string]reflect.Type) // Console output插件,将消息输出到控制台上 type ConsoleOutput struct {} func (c *ConsoleOutput) Send(msg string) { fmt.Println(msg) } // 初始化output插件映射关系表 func init() { outputNames["console"] = reflect.TypeOf(ConsoleOutput{}) }然后,我们定义插件抽象工厂接口,以及对应插件的工厂实现: package plugin ... // 插件抽象工厂接口 type Factory interface { Create(conf Config) Plugin } // input插件工厂对象,实现Factory接口 type InputFactory struct{} // 读取配置,通过反射机制进行对象实例化 func (i *InputFactory) Create(conf Config) Plugin { t, _ := inputNames[conf.Name] return reflect.New(t).Interface().(Plugin) } // filter和output插件工厂实现类似 type FilterFactory struct{} func (f *FilterFactory) Create(conf Config) Plugin { t, _ := filterNames[conf.Name] return reflect.New(t).Interface().(Plugin) } type OutputFactory struct{} func (o *OutputFactory) Create(conf Config) Plugin { t, _ := outputNames[conf.Name] return reflect.New(t).Interface().(Plugin) }最后定义pipeline的工厂方法,调用plugin.Factory抽象工厂完成pipelien对象的实例化: package pipeline ... // 保存用于创建Plugin的工厂实例,其中map的key为插件类型,value为抽象工厂接口 var pluginFactories = make(map[plugin.Type]plugin.Factory) // 根据plugin.Type返回对应Plugin类型的工厂实例 func factoryOf(t plugin.Type) plugin.Factory { factory, _ := pluginFactories[t] return factory } // pipeline工厂方法,根据配置创建一个Pipeline实例 func Of(conf Config) *Pipeline { p := &Pipeline{} p.input = factoryOf(plugin.InputType).Create(conf.Input).(plugin.Input) p.filter = factoryOf(plugin.FilterType).Create(conf.Filter).(plugin.Filter) p.output = factoryOf(plugin.OutputType).Create(conf.Output).(plugin.Output) return p } // 初始化插件工厂对象 func init() { pluginFactories[plugin.InputType] = &plugin.InputFactory{} pluginFactories[plugin.FilterType] = &plugin.FilterFactory{} pluginFactories[plugin.OutputType] = &plugin.OutputFactory{} }测试代码如下: package test ... func TestPipeline(t *testing.T) { // 其中pipeline.DefaultConfig()的配置内容见【抽象工厂模式示例图】 // 消息处理流程为 HelloInput -> UpperFilter -> ConsoleOutput p := pipeline.Of(pipeline.DefaultConfig()) p.Exec() } // 运行结果 === RUN TestPipeline HELLO WORLD --- PASS: TestPipeline (0.00s) PASS原型模式(Prototype Pattern)简述原型模式主要解决对象复制的问题,它的核心就是clone()方法,返回Prototype对象的复制品。在程序设计过程中,往往会遇到有一些场景需要大量相同的对象,如果不使用原型模式,那么我们可能会这样进行对象的创建:新创建一个相同对象的实例,然后遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。这种方法的缺点很明显,那就是使用者必须知道对象的实现细节,导致代码之间的耦合。另外,对象很有可能存在除了对象本身以外不可见的变量,这种情况下该方法就行不通了。对于这种情况,更好的方法就是使用原型模式,将复制逻辑委托给对象本身,这样,上述两个问题也都迎刃而解了。Go实现还是以建造者模式一节中的Message作为例子,现在设计一个Prototype抽象接口: package prototype ... // 原型复制抽象接口 type Prototype interface { clone() Prototype } type Message struct { Header *Header Body *Body } func (m *Message) clone() Prototype { msg := *m return &msg }测试代码如下: package test ... func TestPrototype(t *testing.T) { message := msg.Builder(). WithSrcAddr("192.168.0.1"). WithSrcPort(1234). WithDestAddr("192.168.0.2"). WithDestPort(8080). WithHeaderItem("contents", "application/json"). WithBodyItem("record1"). WithBodyItem("record2"). Build() // 复制一份消息 newMessage := message.Clone().(*msg.Message) if newMessage.Header.SrcAddr != message.Header.SrcAddr { t.Errorf("Clone Message failed.") } if newMessage.Body.Items[0] != message.Body.Items[0] { t.Errorf("Clone Message failed.") } } // 运行结果 === RUN TestPrototype --- PASS: TestPrototype (0.00s) PASS总结本文主要介绍了GoF的23种设计模式中的5种创建型模式,创建型模式的目的都是提供一个简单的接口,让对象的创建过程与使用者解耦。其中,单例模式主要用于保证一个类仅有一个实例,并提供一个访问它的全局访问点;建造者模式主要解决需要创建对象时需要传入多个参数,或者对初始化顺序有要求的场景;工厂方法模式通过提供一个工厂对象或者工厂方法,为使用者隐藏了对象创建的细节;抽象工厂模式是对工厂方法模式的优化,通过为工厂对象新增一个抽象层,让工厂对象遵循单一职责原则,也避免了霰弹式修改;原型模式则让对象复制更加简单。下一篇文章,将介绍23种设计模式中的7种结构型模式(Structural Pattern),及其Go语言的实现。
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签