你这是踩到矿坑了。
人生中,第一次,YDJSIR遇到了跨平台开发的困难。
众所周知,JAVA是一个跨平台的语言。各式各样的JVM,让JAVA顺利运行在10亿设备上。然而,其“跨平台性”真的是无可挑剔的吗?
由于本人才疏学浅,下面只能对现象加以描述,而无法分析具体原因。如果有人能够帮YDJSIR解释其原因,YDJSIR将万分感激。
下面举了一个例子。
相关链接:https://github.com/XZ-X/2020SE1-FAQ/issues/13
本问题的高级形态: https://zhuanlan.zhihu.com/p/46294360
问题描述
预期结果
当然是AC啦!YDJSIR都整了两天了!
实际结果
远端OJ永远返回运行超时。分数为0分
代码分析
让YDJSIR们先来看下这段代码。
问题代码
1 | //判断所给字符串是否是Linux下的合法目录相对路径:是否是由/连接起来的的只由数字、字母、下划线组成的字符串的集合(这里当然是理想化的情况,有一些额外限定,下同) |
似乎没什么问题。写到这里,YDJSIR还狠狠地嘲讽了面向用例编程的YDJSIR的舍友(别打YDJSIR)
测试结果
如果你觉得以下具体内容没有意思,可以直接到最后看表格。
本地测试结果-Windows 10 x64
YDJSIR愉快地在本地跑了一下测试。
Windows10版本
本地JDK版本
1 | openjdk version "1.8.0_242" |
IDEA测试结果
这是IDEA运行测试(Junit4)的结果。很好,94ms,全绿。
mvn test
运行结果
1 | Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.133 sec |
本处省略大量e.printbacktrace()
的内容。
1 | Results : |
YDJSIR还是不放心,找了另外一个大佬来测试(感谢czgdl)
Linux AdoptOpenJDK 1.8 测试结果
118ms,很好。YDJSIR大喜。
此实验在64位的Arch
下进行。
Oracle JDK8测试结果
JDK版本
1 | jdk1.8.0_251 |
IDEA测试结果
这个时间差距……
?好像有什么不对?
好像有点大。不管了。
mvn test
测试结果
1 | ------------------------------------------------------- |
很好。YDJSIR决定把YDJSIR的答案Push到OJ上。
于是,YDJSIR收获了这个。
服务器-CentOS x64测试结果
CentOS
JDK版本
1 | jdk-8u161-linux-x64 |
OJ-mvn test
运行结果
1 | ------------------------------------------------------- |
根据助教的解释,服务器上面给每一条OJ题的时间是给足1分钟。YDJSIR本地i5-9300H他再强,也和服务器的性能间差不到一个数量级的。94ms和1min,这明显不合理。问题是,这不是编译的错误,这就是这个问题的关键所在。如果是变异错误,那YDJSIR改就是了,问题是它不是!
于是YDJSIR提出了Issue。JVVM作业助教首先认为是Maven包的问题,让YDJSIR运行mvn test
。YDJSIR一开始由于网络问题缺了包,然而把包补齐之后,提交到OJ上面一样无法通过。
而后,平台助教的反馈是因为系统没有部署/构建成功,根本无法判断是哪个测试没有通过。贴心的平台助教给了YDJSIR下图。这是YDJSIR完成正则表达式相关判定前的提交内容,并据此推测是有关Composite
和Wildcard
有关文件通配符时的读写问题。
与此同时,还有两句极为现实的话。
有人已经能AC了,总不能为了你一个人改动OJ吧?
实在不行,只能让助教单独来跑一次然后给分了。
于是,此时的YDJSIR的心情是这样的。
于是YDJSIR在本地陷入无限的debug中,但是根本没问题。
MAC-测试结果
MAC环境
本地JDK环境
1 | jdk-8u251-macosx-x64 |
IDEA测试结果
MVN测试结果
足足过了20s都没有跑出结果,测试终止。
盲猜结局和上面的1分多钟是一样的。
YDJSIR此时的内心是这样的。
解决方案
初级版:放弃正则表达式
这段代码来自YDJSIR的舍友。
1 | String substring = classpath.substring(classpath.length() - 4, classpath.length()); |
你看着代码,就看下尾巴,多么不正式!要是有奇怪的字符混进来了呢?
后来,YDJSIR一怒之下换掉了YDJSIR辛辛苦苦写的正则模块,想着赌一把,然后本地当然过了,看着39ms这个时间,YDJSIR陷入沉思。
本地IDEA运行结果
管他呢,先提交完AC再说!分数重要!
下面是提交结果。
OJ运行结果
1 | T E S T S |
进阶版:修正正则表达式
AC之后,YDJSIR倒过头来反复研读南京大学软件所的那篇知乎专栏(链接放在开头),似乎发现了什么。于是,YDJSIR的正则表达式变成了下面这样。
1 | String patternDir = "^(\\w+\\/?)+$"; |
本地IDEA运行结果
好了,按照惯例,push到OJ上。
OJ运行结果
顺利AC。
1 | T E S T S |
问题出在何处?
. | 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。 |
---|---|
https://www.runoob.com/regexp/regexp-syntax.html |
这个时间上的差距……好吧还是很可怕。
问题总结
不同平台结果汇总
运行环境 | 修改前 | 修改后 |
---|---|---|
YDJSIR-IDEA-OpenJDK 1.8.0_242-b08 Win_x64(存疑) | 94ms | 58ms |
YDJSIR-MVN-OpenJDK 1.8.0_242-b08 Win_x64 | 133ms | 201ms |
YDJSIR-IDEA-OracleJDK jdk-1.8.0_251 Win_x64 | 81sec | 14sec |
YDJSIR-MVN-OracleJDK jdk-1.8.0_251 Win_x64 | 81sec | 15sec |
YDJSIR -IDEA-OracleJDK jdk-14.0.1 Win_x64 | 115ms | 106ms |
YDJSIR -IDEA-OracleJDK jdk-14.0.1 Win_x64 | 123ms | 73ms |
MAC-IDEA-OracleJDK jdk-1.8.0_251 Mac_x64 | ==超时== | 17sec |
CZG VM-MVN-OpenJDK 1.8.0_242-b08 Linux_x64(Arch) | 118ms | ==没有测试== |
OJ ECS-MVN-OracleJDK jdk-1.8.0_251 Linux_x64(CentOS 7.7) | ==超时== | 16s |
CX MAC-IDEA-OracleJDK jdk-11 Mac_x64 | 90ms | 40ms |
CX MAC-MVN-OracleJDK jdk-11 Mac_x64 | 180ms | 107ms |
此处的超时指的是运行时间超过1min。
- 不同JDK对于同样的语法的实现是不一样的,因而对语言特性的应用应极为谨慎。即使都是JDK 1.8,OpenJDK的实现与Oracle JDK的实现可能是完全不一样的。从58ms到16秒,将近两个数量级的差距足以造成巨大的混乱。然而,除非生产环境和实际环境的配置相同,这样的问题极难发现;
- 小的错误,不是Critical的错误可能会导致极大的混乱。一个没有转义的.,在OpenJDK中仅仅带来了从58ms到94ms的变化,然而在OracleJDK中,却带来了从16s到81s的巨大差异,其威力不可小觑。诚然,即使不用正则,剩下的业务逻辑也要消耗38ms,这样看来,前者耗时增加了近2.8倍,而而后者将近5倍。
- 在开发中尽量选择更新的技术是有意义的。JAVA 11同样是LTS版本,虽然旧的版本可能资料多些,踩过坑的人多些,但是新技术在底层做出的改进是应该被认可的。
本土来自英文维基
题外话
YDJSIR看到有些资料表示IDEA可能一开始使用了自带的JDK进行测试,而非mvn test
进行测试。YDJSIR认为这个说法是有道理的。因为事实上,YDJSIR一开始IDEA本地全过的时候,在IDEA里面调用Maven运行mvn test
甚至是会提示少了资源包的,需要YDJSIR用魔法解决。在某舍友的MAC上运行时,出现了IDEA全过,mvn test
提示不支持的情况。期间的共性,就是安装从IDEA开始,从未更改SDK相关设置,全部走默认。然而YDJSIR在IDEA官方文档得出的结果却是自带JDK仅仅用于IDE自身运行,并非用于开发,你需要自行设置JDK。
1 | Important notice |
YDJSIR不清楚该如何理解上面那段话。
这个链接里面同样提到类似的问题。
实验证明,IDEA会默认用自己的JDK(基于OpenJDK,叫做JBR)来编译并运行程序。YDJSIR设置了自己的JDK之后,没有找到只使用自带JDK的方法。现在YDJSIR为了减少类似情况,在大作业中选择Oracle JDK来完成YDJSIR的大作业(DDL啊啊啊啊)
1 | JetBrains Runtime is a runtime environment for running IntelliJ Platform based products on Windows, Mac OS X, and Linux. JetBrains Runtime is based on OpenJDK project with some modifications. These modifications include: Subpixel Anti-Aliasing, enhanced font rendering on Linux, HiDPI support, ligatures, some fixes for native crashes not presented in official builds, and other small enhancements. |
这段话摘自JBR官网。
YDJSIR才疏学浅,无力解决上述问题。为了辅助参考,这里给出本地IDEA版本相关信息。
结语
YDJSIR在这一系列的折腾后,苦思冥想,大致地得出了以下结论:
- 在编程中,任何键盘上能直接敲出来的字符都应该被小心对待!哪怕是小键盘,也可能有114514种奇怪的功能!
- 正则表达式是个大坑,一不小心就会掉进去;
- 面向用例编程有时候是刷分的好技巧。然而在可能的情况下,YDJSIR还是愿意折腾一下的;
- 学艺不精是很危险的。比如说打漏转义字符符号的YDJSIR直接导致了服务器上运行的超时。这要是在生产环境中,可能会造成灾难性的后果;
- YDJSIR学了这么久,感觉编程的能力没有多大提高
但是膜神卖弱吹水能力大增,但是Debug的经验的确变多了。对于断点的重要性,分部寻找出问题的点的能力也有所提高。YDJSIR要争取尽快摆脱全靠print这样的低级手段Debug,而是更多利用IDE的特性。