不要再用main方法测试代码性能了,用这款JDK自带工具

前言

作为软件开发人员,我们通常会写一些测试程序用来对比不同算法、不同工具的性能问题。而最常见的做法是写一个main方法,构造模拟场景进行并发测试。

如果细心的朋友可能已经发现,每次测试结果误差很大,有时候测试出的结果甚至与事实相反。当然,这不排除是因为软硬件环境因素导致,但更多的可能是因为所使用测试方法自身有问题。

比如,不同需要性能比较方法放到一个虚拟机里调用,有可能会互相影响,缺少预热的过程等。

本文给大家推荐一款JDK9及以后自带的一款可用于软件基准测试的工具JMH(Java Microbenchmark Harness)。

JMH简介

JMH是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。

何谓Micro Benchmark呢?简单的来说就是基于方法层面的基准测试,精度可以达到微秒级。当你定位到热点方法,希望进一步优化方法性能的时候,就可以使用JMH对优化的结果进行量化的分析。

这款工具是由Oracle内部实现JIT的作者所写。我们知道JIT(Java即时编译器)是将JVM优化的所有高效手段和技术都使用上的地方。可想而知,开发者比任何人都更加了解JVM和JIT对基准测试的影响。

因此,这款工具是值得我们信赖和在实践中进行使用的。而且使用起来也非常方便。

使用场景

JMH不仅能帮我们测试一些常见类的性能,比如对比StringBuffer和StringBuilder的性能、对比不同算法的在不同数据量的性能等,还能够帮助我们对系统中发现的热点代码进行量化分析。

JMH通常用于以下应用场景:

  • 测试某个方法在稳定执行的情况下所需时间,以及执行时间和问题规模的相关性;

  • 对比接口不同实现在给定条件下的吞吐量

  • 查看多少百分比的请求在多长时间内完成

使用实例

依赖引入

如果你使用的是JDK9或以上版本,则JDK中已经自带了该工具,直接使用即可。如果你使用的是其他版本则可以通过maven直接引入以下依赖:

<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.27</version>
</dependency>
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.27</version>
</dependency>

其中1.27是当前的最新版本,可根据实际需要更新或降低版本。

测试案例

下面以StringBuffer和StringBuilder的性能测试对比为例来进行基准测试。

//使用模式 默认是Mode.Throughput
@BenchmarkMode(Mode.AverageTime)
// 配置预热次数,默认是每次运行1秒,运行10次,这里设置为3次
@Warmup(iterations = 3, time = 1)
// 本例是一次运行4秒,总共运行3次,在性能对比时候,采用默认1秒即可
@Measurement(iterations = 3, time = 4)
// 配置同时起多少个线程执行
@Threads(1)
//代表启动多个单独的进程分别测试每个方法,这里指定为每个方法启动一个进程
@Fork(1)
// 定义类实例的生命周期,Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能
@State(value = Scope.Benchmark)
// 统计结果的时间单元
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JmhTest {@Param(value = {"10", "50", "100"})private int length;public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(JmhTest.class.getSimpleName()).result("result.json").resultFormat(ResultFormatType.JSON).build();new Runner(opt).run();}@Benchmarkpublic void testStringBufferAdd(Blackhole blackhole) {StringBuffer sb = new StringBuffer();for (int i = 0; i < length; i++) {sb.append(i);}blackhole.consume(sb.toString());}@Benchmarkpublic void testStringBuilderAdd(Blackhole blackhole) {StringBuilder sb = new StringBuilder();for (int i = 0; i < length; i++) {sb.append(i);}blackhole.consume(sb.toString());}
}

上面介绍概念时已经提到Benchmark为基准测试,在使用中只需对要测试的方法添加@Benchmark注解即可。而在测试类JmhTest指定测试的预热、线程、测试维度等信息。

main方法中通过OptionsBuilder构造测试配置对象Options,并传入Runner,启动测试。这里指定测试结果为json格式,同时会将结果存储在result.json文件当中。

执行测试

执行main方法,控制台首先会打印出如下信息:

# JMH version: 1.27
# VM version: JDK 1.8.0_271, Java HotSpot(TM) 64-Bit Server VM, 25.271-b09
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/jre/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=56800:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# JMH blackhole mode: full blackhole + dont-inline hint
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 4 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.choupangxia.strings.JmhTest.testStringBufferAdd
# Parameters: (length = 10)

这些信息主要用来展示测试的基本信息,包括jdk、JVM、预热配置、执行轮次、执行时间、执行线程、测试的统计单位等。

# Warmup Iteration   1: 76.124 ns/op
# Warmup Iteration   2: 77.703 ns/op
# Warmup Iteration   3: 249.515 ns/op

这是对待测试方法的预热处理,这部分不会记入测试结果。预热主要让JVM对被测代码进行足够多的优化,比如JIT编译器的优化。

Iteration   1: 921.191 ns/op
Iteration   2: 897.729 ns/op
Iteration   3: 890.245 ns/opResult "com.choupangxia.strings.JmhTest.testStringBuilderAdd":903.055 ±(99.9%) 294.557 ns/op [Average](min, avg, max) = (890.245, 903.055, 921.191), stdev = 16.146CI (99.9%): [608.498, 1197.612] (assumes normal distribution)

显示每次(共3次)迭代执行速率,最后进行统计。这里是对testStringBuilderAdd方法执行length为100的测试,通过 (min, avg, max) 三项可以看出最小时间、平均时间、最大时间的值,单位为ns。stdev显示的是误差时间。

通常情况下,我们只用看最后的结果即可:

Benchmark                     (length)  Mode  Cnt     Score      Error  Units
JmhTest.testStringBufferAdd               10  avgt    3    92.599 ±  105.019  ns/op
JmhTest.testStringBufferAdd               50  avgt    3   582.974 ±  580.536  ns/op
JmhTest.testStringBufferAdd              100  avgt    3  1131.460 ± 1109.380  ns/op
JmhTest.testStringBuilderAdd        10  avgt    3    76.072 ±    2.824  ns/op
JmhTest.testStringBuilderAdd        50  avgt    3   450.325 ±   14.271  ns/op
JmhTest.testStringBuilderAdd       100  avgt    3   903.055 ±  294.557  ns/op

看到上述结果我们可能会很吃惊,我们知道StringBuffer要比StringBuilder的性能低一些,但结果发现它们的之间的差别并不是很大。这是因为JIT编译器进行了优化,比如当JVM发现在测试当中StringBuffer并没有发生逃逸,于是就进行了锁消除操作。

常用注解

下面对JHM当中常用的注解进行说明,以便大家可以更精确的使用。

@BenchmarkMode

配置Mode选项,作用于类或者方法上,其value属性为Mode数组,可同时支持多种Mode,如:@BenchmarkMode({Mode.SampleTime, Mode.AverageTime}),也可设为Mode.All,即全部执行一遍。

org.openjdk.jmh.annotations.Mode为枚举类,对应的源代码如下:

public enum Mode {Throughput("thrpt", "Throughput, ops/time"),AverageTime("avgt", "Average time, time/op"),SampleTime("sample", "Sampling time"),SingleShotTime("ss", "Single shot invocation time"),All("all", "All benchmark modes");// 省略其他内容
}

不同模式之间,测量的维度或测量的方式不同。目前JMH共有四种模式:

  • Throughput:整体吞吐量,例如“1秒内可以执行多少次调用”,单位为ops/time;

  • AverageTime:调用的平均时间,例如“每次调用平均耗时xxx毫秒”,单位为time/op;

  • SampleTime:随机取样,最后输出取样结果的分布,,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”;

  • SingleShotTime:以上模式都是默认一次iteration是1s,只有SingleShotTime是只运行一次。往往同时把warmup次数设为0,用于测试冷启动时的性能;

  • All:上面的所有模式都执行一次;

@Warmup

在执行@Benchmark之前进行预热操作,确保测试的准确性,可用于类或者方法上。默认是每次运行1秒,运行10次。

其中@Warmup有以下属性:

  • iterations:预热的次数;Iteration是JMH进行测试的最小单位,在大部分模式下,一次iteration代表的是一秒,JMH会在这一秒内不断调用需要benchmark的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。

  • time:每次预热的时间;

  • timeUnit:时间的单位,默认秒;

  • batchSize:批处理大小,每次操作调用几次方法;

JIT在执行的过程中会将热点代码编译为机器码,并进行各种优化,从而提高执行效率。预热的主要目的是让JVM的JIT机制生效,让结果更接近真实效果。

@State

类注解,JMH测试类必须使用@State注解,不然会提示无法运行。

State定义了一个类实例的生命周期(作用范围),可以类比Spring Bean的Scope。因为很多benchmark会需要一些表示状态的类,JMH会根据scope来进行实例化和共享操作。

@State可以被继承使用,如果父类定义了该注解,子类则无需定义。

由于JMH允许多线程同时执行测试,不同的选项含义如下:

  • Scope.Thread:默认的State,该状态为每个线程独享,每个测试线程分配一个实例;

  • Scope.Benchmark:该状态在所有线程间共享,所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;

  • Scope.Group:该状态为同一个组里面所有线程共享。

@OutputTimeUnit

benchmark统计结果所使用的时间单位,可用于类或者方法注解,使用java.util.concurrent.TimeUnit中的标准时间单位。

@Measurement

度量,其实就是实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上。配置属性项目和作用与@Warmup相同。

一般比较重的程序可以进行大量的测试,放到服务器上运行。在性能对比时,采用默认1秒即可,如果用jvisualvm做性能监控,可以指定一个较长时间运行。

@Threads

每个进程中同时起多少个线程执行,可用于类或者方法上。默认值是Runtime.getRuntime().availableProcessors(),根据具体情况选择,一般为cpu乘以2。

@Fork

代表启动多个单独的进程分别测试每个方法,可用于类或者方法上。如果fork数是2的话,则JMH会fork出两个进程来进行测试。

JVM因为使用了profile-guided optimization而“臭名昭著”,这对于微基准测试来说十分不友好,因为不同测试方法的profile混杂在一起,“互相伤害”彼此的测试结果。对于每个@Benchmark方法使用一个独立的进程可以解决这个问题,这也是JMH的默认选项。注意不要设置为0,设置为n则会启动n个进程执行测试(似乎也没有太大意义)。fork选项也可以通过方法注解以及启动参数来设置。

@Param

属性级注解,指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,使用该注解必须定义@State注解。

@Param注解接收一个String数组,在@Setup方法执行前转化为对应的数据类型。多个@Param注解的成员之间是乘积关系,譬如有两个用@Param注解的字段,第一个有5个值,第二个字段有2个值,那么每个测试方法会跑5*2=10次。

@Benchmark

方法注解,表示该方法是需要进行benchmark的对象,用法和JUnit的@Test类似。

@Setup

方法注解,这个注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的。

@TearDown

方法注解,与@Setup相对的,会在所有benchmark执行结束以后执行,比如关闭线程池,数据库连接等的,主要用于资源的回收等。

Threads

每个fork进程使用多少个线程去执行测试方法,默认值是Runtime.getRuntime().availableProcessors()。

@Group

方法注解,可以把多个benchmark定义为同一个group,则它们会被同时执行,譬如用来模拟生产者-消费者读写速度不一致情况下的表现。

@Level

用于控制@Setup,@TearDown的调用时机,默认是Level.Trial。

  • Trial:每个benchmark方法前后;

  • Iteration:每个benchmark方法每次迭代前后;

  • Invocation:每个benchmark方法每次调用前后,谨慎使用,需留意javadoc注释;

JMH注意事项

无用代码消除(Dead Code Elimination)

现代编译器是十分聪明的,它们会对代码进行推导分析,判定哪些代码是无用的然后进行去除,这种行为对微基准测试是致命的,它会使你无法准确测试出你的方法性能。

JMH本身已经对这种情况做了处理,要记住:1.永远不要写void方法;2.在方法结束返回计算结果。有时候如果需要返回多于一个结果,可以考虑自行合并计算结果,或者使用JMH提供的BlackHole对象:

/** This demonstrates Option A:** Merge multiple results into one and return it.* This is OK when is computation is relatively heavyweight, and merging* the results does not offset the results much.*/
@Benchmark
public double measureRight_1() {return Math.log(x1) + Math.log(x2);
}
/** This demonstrates Option B:** Use explicit Blackhole objects, and sink the values there.* (Background: Blackhole is just another @State object, bundled with JMH).*/
@Benchmark
public void measureRight_2(Blackhole bh) {bh.consume(Math.log(x1));bh.consume(Math.log(x2));
}

再比如下面代码:

@Benchmark
public void testStringAdd(Blackhole blackhole) {String a = "";for (int i = 0; i < length; i++) {a += i;}
}

JVM可能会认为变量a从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。

JMH提供了两种方式避免这种问题,一种是将这个变量作为方法返回值return a,一种是通过Blackhole的consume来避免JIT 的优化消除。

常量折叠(Constant Folding)

常量折叠是一种现代编译器优化策略,例如,i = 320 * 200 * 32,多数的现代编译器不会真的产生两个乘法的指令再将结果储存下来,取而代之的,它们会辨识出语句的结构,并在编译时期将数值计算出来(i = 2,048,000)。

在微基准测试中,如果你的计算输入是可预测的,也不是一个@State实例变量,那么很可能会被JIT给优化掉。对此,JMH的建议是:1.永远从@State实例中读取你的方法输入;2.返回你的计算结果;3.或者考虑使用BlackHole对象;

见如下官方例子:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JMHSample_10_ConstantFold {private double x = Math.PI;private final double wrongX = Math.PI;@Benchmarkpublic double baseline() {// simply return the value, this is a baselinereturn Math.PI;}@Benchmarkpublic double measureWrong_1() {// This is wrong: the source is predictable, and computation is foldable.return Math.log(Math.PI);}@Benchmarkpublic double measureWrong_2() {// This is wrong: the source is predictable, and computation is foldable.return Math.log(wrongX);}@Benchmarkpublic double measureRight() {// This is correct: the source is not predictable.return Math.log(x);}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(JMHSample_10_ConstantFold.class.getSimpleName()).warmupIterations(5).measurementIterations(5).forks(1).build();new Runner(opt).run();}
}

循环展开(Loop Unwinding)

循环展开最常用来降低循环开销,为具有多个功能单元的处理器提供指令级并行。也有利于指令流水线的调度。例如:

for (i = 1; i <= 60; i++) a[i] = a[i] * b + c;

可以展开成:

for (i = 1; i <= 60; i+=3){a[i] = a[i] * b + c;a[i+1] = a[i+1] * b + c;a[i+2] = a[i+2] * b + c;
}

由于编译器可能会对你的代码进行循环展开,因此JMH建议不要在你的测试方法中写任何循环。如果确实需要执行循环计算,可以结合@BenchmarkMode(Mode.SingleShotTime)和@Measurement(batchSize = N)来达到同样的效果。参考如下例子:

/** Suppose we want to measure how much it takes to sum two integers:*/
int x = 1;
int y = 2;
/** This is what you do with JMH.*/
@Benchmark
@OperationsPerInvocation(100)
public int measureRight() {return (x + y);
}

JMH可视化

在示例的main方法中指定了生成测试结果的输出文件result.json,其中的内容就是控制台输出的相关内容以json格式存储。

针对json格式的内容,可以在其他网站上以图表的形式可视化展示。

对应网站,JMH Visual Chart(http://deepoove.com/jmh-visual-chart/)、JMH Visualizer(https://jmh.morethan.io/)。

展示效果如下图:

img

生成jar包执行

对于大型的测试,一般会放在Linux服务器里去执行。JMH官方提供了生成jar包的方式来执行,在maven里增加如下插件:

<plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>2.4.1</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><finalName>jmh-demo</finalName><transformers><transformerimplementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"><mainClass>org.openjdk.jmh.Main</mainClass></transformer></transformers></configuration></execution></executions></plugin>
</plugins>

执行maven的命令生成可执行jar包,并执行:

mvn clean package
java -jar target/jmh-demo.jar JmhTest

总结

一篇文章几乎涵盖了JMH各方面的知识点,如果实践中还没运用,赶紧用起来吧,你的专业水平将又提升那么一点。当然,也可以收藏起来,以备不时不需。

参考文章:

https://www.zhihu.com/question/276455629/answer/1259967560 https://www.cnblogs.com/silyvin/p/11736696.html https://blog.csdn.net/wangxuelei036/article/details/105240522 https://www.cnblogs.com/xiang--liu/p/9710143.html


往期推荐

6种快速统计代码执行时间的方法,真香!(史上最全)


Oracle官方推荐的性能测试工具!简单、精准又直观!


链表竟然比数组慢了1000多倍?(动图+性能评测)


关注我,每天陪你进步一点点!

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

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

相关文章

Java中Properties类的操作

http://www.cnblogs.com/bakari/p/3562244.html Java中Properties类的操作 知识学而不用&#xff0c;就等于没用&#xff0c;到真正用到的时候还得重新再学。最近在看几款开源模拟器的源码&#xff0c;里面涉及到了很多关于Properties类的引用&#xff0c;由于Java已经好久没用…

复盘线上的一次OOM和性能优化!

来源&#xff1a;r6d.cn/ZazN上周五&#xff0c;发布前一周的服务器小动荡????事情回顾上周五&#xff0c;通过Grafana监控&#xff0c;线上环境突然出现CPU和内存飙升的情况&#xff1a;但是看到网络输入和输入流量都不是很高&#xff0c;所以网站被别人攻击的概率不高&am…

阅读源码的 4 个绝技,我必须分享给你!

为什么要阅读源码&#xff1f;1.在通用型基础技术中提高技术能力在 JAVA 领域中包含 JAVA 集合、Java并发(JUC)等&#xff0c; 它们是项目中使用的高频技术&#xff0c;在各种复杂的场景中选用合适的数据结构、线程并发模型&#xff0c;合理控制锁粒度等都能显著提高应用程序的…

innerHTML、innerText和outerHTML、outerText的区别

1、区别描述如下&#xff1a; innerHTML 设置或获取位于对象起始和结束标签内的 HTMLouterHTML 设置或获取对象及其内容的 HTML 形式innerText 设置或获取位于对象起始和结束标签内的文本outerText 设置(包括标签)或获取(不包括标签)对象的文本innerText和outerText在获取时是相…

Socket粘包问题终极解决方案—Netty版(2W字)!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;上一篇我们写了《Socket粘包问题的3种解决方案》&#xff0c;但没想到评论区竟然炸了。介于大家的热情讨论&#xff0c;以及…

Java高质量代码之 — 泛型与反射

在Java5后推出了泛型,使我们在编译期间操作集合或类时更加的安全,更方便代码的阅读,而让身为编译性语言的Java提供动态性的反射技术,更是在框架开发中大行其道,从而让Java活起来,下面看一下在使用泛型和反射需要注意和了解的事情 1.Java的泛型是类型擦除的 Java中的泛型是…

Redis 消息队列的三种方案(List、Streams、Pub/Sub)

现如今的互联网应用大都是采用 分布式系统架构 设计的&#xff0c;所以 消息队列 已经逐渐成为企业应用系统 内部通信 的核心手段&#xff0c;它具有 低耦合、可靠投递、广播、流量控制、最终一致性 等一系列功能。当前使用较多的 消息队列 有 RabbitMQ、RocketMQ、ActiveMQ、K…

c struct 对齐_C中的struct大小| 填充,结构对齐

c struct 对齐What we know is that size of a struct is the sum of all the data members. Like for the following struct, 我们知道的是&#xff0c; 结构的大小是所有数据成员的总和 。 对于以下结构&#xff0c; struct A{int a;int* b;char c;char *d;};Size of the st…

超3000岗位!腾讯产业互联网新年大扩招!

虽然离春节仅剩 1 个月的时间&#xff0c;大厂依旧没有停止招人。就在上周&#xff0c;腾讯官宣新年大扩招&#xff0c;放出 3000 多个岗位需求&#xff01;我们查看了腾讯的招聘数据发现&#xff0c;除了大量招聘运营人员&#xff0c;你猜&#xff0c;他们还在批量招聘什么岗位…

骚操作,IDEA防止写代码沉迷插件 !

当初年少懵懂&#xff0c;那年夏天填志愿选专业&#xff0c;父母听其他长辈说选择计算机专业好。从那以后&#xff0c;我的身上就有了计院深深的烙印。从寝室到机房&#xff0c;从机房到图书馆&#xff0c;C、C、Java、只要是想写点自己感兴趣的东西&#xff0c;一坐就是几个小…

css属性 content

对css一直没有很系统得学习过,练习得也不是很多,纯小白.今天在写一个页面的时候,遇到一个问题,就是如何让外面的盒子适应里面的盒子大小,完美地把小盒子包在里面. 由于里面是一个列表 ul,为了让元素横排,我使用了float:right这个属性,所以列表悬浮了.如图: 其实当然可以直接给外…

一文汇总 JDK 5 到 JDK 15 中的牛逼功能!

前言JDK 16 马上就要发布啦&#xff08;预计 2021.3.16 日发布&#xff09;&#xff0c;所以在发布之前&#xff0c;让我们先来回顾一下 JDK 5-15 的新特性吧&#xff0c;大家一起学起来~Java 5 新特性1. 泛型泛型本质是参数化类型&#xff0c;解决不确定具体对象类型的问题。L…

Tomcat 6.0 简介

本片翻译来自&#xff1a;http://tomcat.apache.org/tomcat-6.0-doc/introduction.html 介绍 无论是开发者还是tomcat管理员在使用前都需要了解一些必要的信息&#xff0c;本篇简单的介绍tomcat中的一些术语和概念。比如context是web应用的意思。CATALINA_HOME 在文档中&#x…

Docker部署SpringBoot的两种方法,后一种一键部署超好用!

作者 | LemonSquash来源 | cnblogs.com/npeng/p/14267007.html1.手工方式1.1.准备Springboot jar项目将项目打包成jar1.2.编写DockerfileFROM java:8 VOLUME /tmp ADD elk-web-1.0-SNAPSHOT.jar elk.jar EXPOSE 8080 ENTRYPOINT ["java","-Djava.security.egdfi…

UISwitch 添加 标签

给UISwitch添加一个标签。左右滑动时候出现开关标签内容。 代码&#xff1a; // // UISwitchJGLabel.h // JGSwitch // // Created by sl on 15/4/11. // Copyright (c) 2015年 Madordie. All rights reserved. // // // 说明&#xff1a; // 1.给UISwitch添加开关标…

爱了!蚂蚁开源的“SpringBoot”框架,新增了这6项功能...

SOFABoot 是蚂蚁金服开源的基于 Spring Boot 的研发框架&#xff0c;它在 Spring Boot 的基础上&#xff0c;提供了诸如 Readiness Check&#xff0c;类隔离&#xff0c;日志空间隔离等等能力。在增强了 Spring Boot 的同时&#xff0c;SOFABoot 提供了让用户可以在 Spring Boo…

PUC的完整形式是什么?

PUC&#xff1a;大学预科/污染控制/个人解锁码 (PUC: Pre University Course / Pollution Under Control / Personal Unlock Code) 1)PUC&#xff1a;大学预科课程 (1) PUC: Pre University Course) PUC is an abbreviation of the Pre University Course. It alludes to an in…

过滤器VS拦截器的4个区别,看完豁然开朗!

Spring的拦截器与Servlet的Filter有相似之处&#xff0c;比如二者都是AOP编程思想的体现&#xff0c;都能实现权限检查、日志记录等。但它们之间又有很大区别&#xff0c;所以本文磊哥就带大家全面了解一下什么是过滤器&#xff1f;什么是拦截器&#xff1f;以及二者有什么区别…

分布式ID生成的9种方法,特好用!

前言业务量小于500W或数据容量小于2G的时候单独一个mysql即可提供服务&#xff0c;再大点的时候就进行读写分离也可以应付过来。但当主从同步也扛不住的是就需要分表分库了&#xff0c;但分库分表后需要有一个唯一ID来标识一条数据&#xff0c;数据库的自增ID显然不能满足需求&…

8051 管脚定义_8051微控制器的引脚说明

8051 管脚定义8051微控制器的引脚说明 (Pin Description of 8051 Microcontroller) Pins from 1-8 1-8针 Port 1: The pins in this port are bi-directional and can be used for input and output. The pins are individually controlled; some are used for input while ot…