#10 StackOverflow 上被复制最多的代码
开发者 Java 2023-11-28根据《The most copied StackOverflow snippet of all time is flawed!》,StackOverflow 上被复制最多的一段代码来自问题 How can I convert byte size into a human-readable format in Java?,也就是将字节数转换成可读格式,比如 1024 -> 1KB 这样。
coding in a complicated world
根据《The most copied StackOverflow snippet of all time is flawed!》,StackOverflow 上被复制最多的一段代码来自问题 How can I convert byte size into a human-readable format in Java?,也就是将字节数转换成可读格式,比如 1024 -> 1KB 这样。
遇到一个这样的问题,总数是 t,由 a,b,c 三部分组成。计算 a 的比例,结果算出来一个负数。
原来是 a * 100
这一步溢出了。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
double a = (double) (22890851 * 100) / 26487012;
System.out.println(a);
BigDecimal b = new BigDecimal(a);
System.out.println(b);
// 警告: [deprecation] BigDecimal中的ROUND_HALF_UP已过时
// [deprecation] BigDecimal中的setScale(int,int)已过时
// ROUND_HALF_UP is int
BigDecimal c = b.setScale(2, RoundingMode.HALF_UP);
System.out.println(c);
double d = c.doubleValue();
System.out.println(d);
BigDecimal result = new BigDecimal(22890951)
.multiply(new BigDecimal(100))
.divide(new BigDecimal(26487012), 2, RoundingMode.HALF_UP);
System.out.println(result);
System.out.println("HELLO WORLD");
}
}
OUTPUT:
86.42
-75.73078443125257
-75.7307844312525730856577865779399871826171875
-75.73
-75.73
HELLO WORLD
不要用 xx != null && xx.length() > 0
来判空,可以使用 apache 的 commons 包。
今天,25 岁的 Java 仍然是最具有统治力的编程语言,长期占据编程语言排行榜的首位,拥有一千二百万的庞大开发者群体,全世界有四百五十亿部物理设备使用着 Java 技术,同时,在云端数据中心的虚拟化环境里,还运行着超过两百五十亿个 Java 虚拟机的进程实例 (数据来自Oracle的WebCast)。
以上这些数据是 Java 过去 25 年巨大成就的功勋佐证,更是 Java 技术体系维持自己“天下第一”编程语言的坚实壁垒。Java 与其他语言竞争,底气从来不在于语法、类库有多么先进好用,而是来自它庞大的用户群和极其成熟的软件生态,这在朝夕之间难以撼动。然而,这个现在看起来仍然坚不可摧的 Java 帝国,其统治地位的稳固程度不仅没有高枕无忧,反而说是危机四伏也不为过。目前已经有了可预见的、足以威胁动摇其根基的潜在可能性正在酝酿,并随云原生时代而降临。
这篇文章讲到了绝对值计算的问题:One does not simply calculate the absolute value。
三个特殊值:
class Test {
public static double abs(double value) {
if (value < 0) {
return -value;
}
return value;
}
public static void main(String[] args) {
double x = -0.0;
if (1 / abs(x) < 0) {
System.out.println("oops");
}
}
}
if
中加上条件:value == -0.0
是行不通的,因为 +0.0 == -0.0
,可以使用 JDK 中的 Double.compare
:
public static double abs(double value) {
if (value < 0 || Double.compare(value, -0.0) == 0) {
return -value;
}
return value;
}
这样确实有效,不过效率上可能会受到影响,abs
的复杂性就上了一个台阶。
public static int compare(double d1, double d2) {
if (d1 < d2)
return -1; // Neither val is NaN, thisVal is smaller
if (d1 > d2)
return 1; // Neither val is NaN, thisVal is larger
// Cannot use doubleToRawLongBits because of possibility of NaNs.
long thisBits = Double.doubleToLongBits(d1);
long anotherBits = Double.doubleToLongBits(d2);
return (thisBits == anotherBits ? 0 : // Values are equal
(thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
1)); // (0.0, -0.0) or (NaN, !NaN)
}
参考 JDK 中的实现,重写 abs
:
private static final long MINUS_ZERO_LONG_BITS = Double.doubleToLongBits(-0.0);
public static double abs(double value) {
if (value < 0 || Double.doubleToLongBits(value) == MINUS_ZERO_LONG_BITS) {
return -value;
}
return value;
}
新的问题:NaN 的处理,处理方法:把 doubleToLongBits
改成 doubleToRawLongBits
。
private static final long MINUS_ZERO_LONG_BITS = Double.doubleToRawLongBits(-0.0);
public static double abs(double value) {
if (value < 0 || Double.doubleToRawLongBits(value) == MINUS_ZERO_LONG_BITS) {
return -value;
}
return value;
}
JVM 的 JIT 会替换这次调用为底层的 CPU 寄存器操作,效率非常可观。
PS:如果可以省去这个分支的判断逻辑,JVM 可以给我们更好的性能优化?
We know that branches are bad. If the CPU branch predictor guesses incorrectly, they can be very expensive.
doubleToRawLongBits
)会导致浮点数寄存器转换到通用集成器。Although there are rumors saying that this call may still lead to a transfer from a floating-point register to a general-purpose register. Still it's very fast.
采用 0 减负数等于正数,并且 0 - -0 = 0
的规则:
public static double abs(double value) {
if (value <= 0) {
return 0.0 - value;
}
return value;
}
这就是长期以来(直到最新的 Java 17),JDK 使用的方法(return (a <= 0.0D) ? 0.0D - a : a;
)。
参考:JDK 17 中的的实现:java/lang/Math.java
有人提出了意见,认为目前官方的实现 too slow(6506405: Math.abs(float) is slow #4711)。
这就是 jdk-18+6 中引入的新方案(java/lang/Math.java#L1600~L1604):
public static double abs(double a) {
return Double.longBitsToDouble(Double.doubleToRawLongBits(a) & DoubleConsts.MAG_BIT_MASK);
}
DoubleConsts.MAG_BIT_MASK
就是 0x7fffffffffffffffL
, 0 + 63 个 1。
原理就是,通过位运算,清除符号位(使之为 0)。
Oracle 宣布从 Java 17 开始采用新的 NFTC 协议,总的来说,就是允许免费商用了。
树上有只熊:看完后我感觉 java 水平又提升了一大截,已经向微软谷歌发简历了,希望它们不要不识抬举。
Release | GA Date | Premier Support Until | Extended Support Until |
---|---|---|---|
7 (LTS) | July 2011 | July 2019 | July 2022 |
8 (LTS) | March 2014 | March 2022 | December 2030 |
9 | September 2017 | March 2018 | - |
10 | March 2018 | September 2018 | - |
11 (LTS) | September 2018 | September 2023 | September 2026 |
12 | March 2019 | September 2019 | - |
13 | September 2019 | March 2020 | - |
14 | March 2020 | September 2020 | - |
15 | September 2020 | March 2021 | - |
16 | March 2021 | September 2021 | - |
17 (LTS) | September 2021 | September 2026 | September 2029 |
18 | March 2022 | September 2022 | - |
19 | September 2022 | March 2023 | - |
20 | March 2023 | September 2023 | - |
21 (LTS) | September 2023 | September 2028 | September 2031 |
PS:Java 9 开始引入了新的模块机制,标准库结构。
PS: 2021 年 Java 17 发布时,Oracle 宣布以后每两年一个 LTS 版本,也就是说下一个 LTS 版本是 21 而非 23。
# 安装
curl -s "https://get.sdkman.io" | bash
# 查看当前版本
sdk version
# 更新到最新版本
sdk update
# 列出可用 Java 版本
sdk list java
# 安装指定 Java 版本
sdk install java 11.0.16-albba
# 切换到另一个已安装 Java 版本
sdk use java 8.8.9-albba
除了管理 Java 版本,还可以用来管理其他开发工具,比如 Gradle,Maven,Scale 等。
突然用到了一下 JDK,感觉以前学过的东西都忘光了,所以这次记个笔记。