面试姊妹篇3:常见的Java计算错误

目录

系列文章


1 、 关 于 三 目 运 算 符 \color{7f1A8A}1、关于三目运算符 1

  • 下面的运行结果应该输出什么?
public static void main(String[] args) {
   Integer a = 1;
   Integer b = 2;
   Integer c = null;
   Integer d = a > b ? a + b : c;
   System.out.println(d);
}
  • 你期待的:
null
  • 实际上:
Exception in thread "main" java.lang.NullPointerException
	at DesignPattern.single.dfghj.main(dfghj.java:16)

Process finished with exit code 1
  • 原因

    • 报错的第16行是这一句:Integer d = a > b ? a + b : c;
    • 首先我们明确:条件运算符是右结合的,也就是说,从右向左分组计算。例如,a?b:c?d:e将按 a?b:(c?d:e) 执行。
    • 基本类型跟包装类型在做转换的时候,有个自动包装和解包装的过程,会调用.xxxxValue(),比如.booleanValue(),.intValue(),.StringValue()。
    • 三目运算符的“:”左右两边会自动保持一致,那么如果类型不一致,会发生解包装的过程。
    • 如果两边类型不一致,比如题目中的:左边计算出来是int3,而不是Integer3,右边的也会解包装,那么null本身再次吊用.xxxxValue(),会发生NPE。
  • 解决办法:保持“:”两边一致,

    • 要么修改 a+b:Integer d = a > b ? Integer.valueOf(a + b) :c;
    • 要么修改 c :Integer d = a > b ? a + b :null;
  • 参考博客:三目运算符的空指针问题


2 、 精 度 问 题 \color{7f1A8A}2、精度问题 2

  • 题目:下列输出结果是什么?
BigDecimal bd1 = new BigDecimal("2.0");
BigDecimal bd2 = new BigDecimal("2.00");

System.out.println("equals: " + bd1.equals(bd2));
System.out.println("compareTo: " + bd1.compareTo(bd2));
  • 结果:
equals: false
compareTo: 0
  • 原因:
    • 因为 JDK 认为 2.0 和 2.00 的精度不一样,所以不能 equals,但值确实是相等的。

2 、 0.1 + 0.2 等 于 几 ? f l o a t 0.7 与 d o u b l e 0.7 谁 更 大 ? \color{7f1A8A}2、0.1+0.2等于几?float 0.7与double 0.7谁更大? 20.1+0.2float0.7double0.7

  • 下面的运行结果应该输出什么?
double a = 0.1;
double b = 0.2;
System.out.println(a+b);
  • 输出结果
0.30000000000000004
  • 做错了没关系,我们再看看这道题
float a = 0.7f, a1 = 2.7f;
double b = 0.7, b1 = 2.7;
if (b > a) {
    System.out.println("double 0.7 > float 0.7");
} else {
    System.out.println("double 0.7 < float 0.7");
}

if (b1 > a1) {
    System.out.println("double 2.7 > float 2.7");
} else {
    System.out.println("double 2.7 < float 2.7");
}
  • 输出结果
double 0.7 > float 0.7
double 2.7 < float 2.7
  • 原因分析:
    • 这跟我们大学学的十进制转换二进制有关,一定存在某些数,不是完全匹配的,就像1/3表示成小数,是无限循环,而我们存储是有限的,会发生截取,因此就不完全想等了,具体细节分析见我的另一篇博客:float类型与double类型数谁更大?
  • 建议
    • 如果像银行业等对精度要求比较高的行业,建议使用BigInteger和BigDecimal,它两分别表示大整数类和大浮点数类
    • 但是BigDecimal的构造函数选取不当,也会造成精度缺失,具体见这篇博客:BigDecimal一定不会丢失精度吗
    • 精度不缺失的原理是使用字符串相加相乘,具体见我的另外一篇博客:大整数乘法其实很简单(Java)

4 、 交 换 a 和 b 的 值 \color{7f1A8A}4、交换a和b的值 4ab

  • 题目:不使用其他变量的情况下,如何交换a和b的值
常规写法:
public static void main(String[] args) {
    int a = 5;
    int b = 6;
    a = a + b;
    b = a - b;
    a = a - b;
    System.out.println("a:" + a + "b:" + b);
}
异或写法
public static void main(String[] args) {
    int a = 8;
    int b = 8;
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    System.out.println("new--->" + "a:" + a + ",b:" + b);

}
  • 针对有博客指出常规写法有内存溢出问题,理论上是有的,但是可能jdk1.8以后做了优化,亲测没有溢出的。
  • 针对有博客指出异或写法对两个相同的数异或有bug,但是亲测也没有。
  • 异或写法可能相对常规写法,会减少计算的累加次数。

5 、 计 时 攻 击 \color{7f1A8A}5、计时攻击 5

  • 写一段判断字符串相等的代码
常规写法
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
  • 上面这一段选自JDK的equals方法,大致先判断
    • 是否引用地址一样
    • 是否属于String类型
    • 长度是否一致
    • 挨个对比每个字符是否一样
  • 写成这样就可以了,但是不完美,这会引发安全问题,暴露一种叫做计时攻击的漏洞(请自行百度计时攻击)。
  • 按照下面这种写法,会牺牲效率,达到防计时攻击。
防计时攻击写法
public boolean equals(String s1, String s2) {
    if (s1 == null || s2 == null) {
        return false;
    }
    if (s1 == s2) {
        return true;
    }
    if (s1.length() != s2.length()) {
        return false;
    }
    int res = 0;
    for (int i = 0; i < s1.length(); i++) {
        res |= s1.charAt(i) ^ s2.charAt(i);
    }
    return res == 0;
}


7 、 计 算 1 + 2 + . . . + n 的 值 \color{7f1A8A}7、计算1+2+...+n的值 71+2+...+n

  • 题目描述:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
  • 解题思路及代码:
public class Solution {
    private static int[] res = {0};
    public int Sum_Solution(int n) {
        /* 1、循环的写法
        int sum = 0;
        for(int i = 1;i <= n; i++){
            sum += i;
        }
        return sum; */
        
        /* 2、数学规律
        return (1 + n) * n / 2; */
        
        /* 3、递归
        return n == 0 ? 0 : n + Sum_Solution(n-1); */
        
        // 4、递归-满足题意
        try{
            return res[n];
        }catch(Exception e) {
            return n + Sum_Solution(n-1);
        }
    }
}


9 、 100 G 数 据 在 4 G 内 存 中 排 序 \color{7f1A8A}9、100G数据在4G内存中排序 9100G4G

  • 方法一
    • 使用bitmap,将数据转成二进制的某一位,那么4个字节可以表示:2^32个数字,
  • 方法二
    • 使用多路归并:
      • 第一步:每次读取4G数据,进行排序,写回外存,最终形成25个文本。【IO次数:25次】
      • 第二步:从每个文本中读取最小值,共25个,进行排序,选取最最小值,写回新文本。
      • 第三部:重复第二步,直到所有数据读取完。【IO次数:100G次】
  • 方法三
    • 败者树:
      • 上述提到的25个文本互相晋级,每次晋级对比的时候,仍然分成两份,一个比另一份都要大,大的晋级,小的不动。

10 、 1 亿 个 数 据 进 行 T o p K 排 序 \color{7f1A8A}10、1亿个数据进行TopK排序 101亿TopK

  • 方法一
    • 维护最小堆,在内存中维护一个K大的堆,一亿个数逐个进来,最终这个堆就是TopK
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值