我从 Stack Overflow 上找的了一些高关注度且高赞的问题。这些问题可能平时我们遇不到,但既然是高关注的问题和高点赞的回答说明是被大家普遍认可的,如果我们提前学到了以后不管工作中还是面试中处理起来就会更得心应手。本篇文章是第一周的内容,一共 5 个题目。我每天都会在公众号发一篇,你如果觉得这个系列对你有价值,欢迎文末关注我的公众号。


DAY1.  复合运算符中的强制转换

今天讨论的问题是“符合运算符中的强制转换”。以 += 为例,我编写了如下代码,你可以先考虑下为什么会出现下面这种情况。

int i = 5;
long j = 10; i += j; //正常
i = i+j; //报错,Incompatible types.

这个问题可以从 “Java 语言手册” 中找到答案,原文如下:

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T) ((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

翻译一下:形如 E1 op= E2 的复合赋值表达式等价于 E1 = (T)((E1) op (E2)), 其中,T 是 E1 的类型。所以,回到本例,i+j 的结果会强制转换成 int 再赋值给 i。

其实验证也比较容易,我们看下编译后的 .class 文件就知道做了什么处理。

从 .class 文件可以看出,有两处强制转换。第一处是 i+j 时,由于 j 是 long 类型,因此 i 进行类型提升,强转为 long, 这个过程我们比较熟悉。第二处是我们今天讨论的内容,i+j 的结果强转成了 int 类型。
这里面我们还可以在进一步思考,因为在这个例子中强转可能会导致计算结果溢出,那你可以想想为什么 Java 设计的时候不让它报错呢?
我的猜想是这样的,假设遇到这种情况报错,我们看看会有什么样的后果。比如在 byte 或者 short 类型中使用 += 运算符。

byte b = 1;
b += 1;

按照我们的假设,这里就会报错,因为 i+1 返回的 int 类型。然而实际应用场景中这种代码很常见,因此,假设成立的话,将会严重影响复合赋值运算符的应用范围,最终设计出来可能就是一个比较鸡肋的东西。所以,为了普适性只能把判断交给用户,让用户来保障使用复合赋值运算符不会发生溢出。我们平时应用时一定要注意这个潜在的风险。

原文地址


DAY2.  生成随机数你用对了吗

在 Java 中如何生成一个随机数?如果你的答案是 Random 类,那就有必要继续向下看了。Java 7 之前使用 Random 类生成随机数,Java 7 之后的标准做法是使用 ThreadLocalRandom 类,代码如下:

ThreadLocalRandom.current().nextInt();

既然 Java 7 要引入一个新的类取代之前的 Random 类,说明之前生成随机数的方式存在一定的问题,下面就结合源码简单介绍一下这两个类的区别。
Random 类是线程安全的,如果多线程同时使用一个 Random 实例生成随机数,那么就会共享同一个随机种子,从而存在并发问题导致性能下降,下面看看 next(int bits) 方法的源码:

protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}

看到代码并不复杂,其中,随机种子 seed 是 AtomicLong 类型的,并且使用 CAS 方式更新种子。
接下来再看看 ThreadLocalRandom 类,多线程调用 ThreadLocalRandom.current() 返回的是同一个 ThreadLocalRandom 实例,但它并不存在多线程同步的问题。看下它更新种子的代码:

final long nextSeed() {
Thread t; long r; // read and update per-thread seed
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}

可以看到,这里面不存在线程同步的代码。猜测代码中使用了Thread.currentThread() 达到了  ThreadLocal 的目的,因此不存在线程安全的问题。使用 ThreadLocalRandom 还有个好处是不需要自己 new 对象,使用起来更方便。如果你的项目是 Java 7+ 并且仍在使用 Random 生成随机数,那么建议你切换成 ThreadLocalRandom。由于它继承了 Random 类,因此不会对你现有的代码造成很大的影响。

原文地址


DAY3.  InputStream转String有多少种方法

Java 中如果要将 InputStream 转成 String,你能想到多少种方法?

String str = "测试";
InputStream inputStream = new ByteArrayInputStream(str.getBytes());

1. 使用 ByteArrayOutputStream 循环读取

/** 1. 使用 ByteArrayOutputStream 循环读取 */
BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int tmpRes = bis.read();
while(tmpRes != -1) {
buf.write((byte) tmpRes);
tmpRes = bis.read();
}
System.out.println(buf.toString());

2. 使用 InputStreamReader 批量读取

/** 2. 使用 InputStreamReader 批量读取 */
final char[] buffer = new char[1024];
final StringBuilder out = new StringBuilder();
Reader in = new InputStreamReader(inputStream);
for (; ; ) {
int rsz = in.read(buffer, 0, buffer.length);
if (rsz < 0) {
break;
}
out.append(buffer, 0, rsz);
}
System.out.println(out.toString());

3. 使用 JDK Scanner

/** 3. 使用 JDK Scanner */
Scanner s = new Scanner(inputStream).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";
System.out.println(result);

4. 使用 Java 8 Stream API

/** 4. 使用 Java 8 Stream API */
result = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining("\n"));
System.out.println(result);

5. 使用 IOUtils StringWriter

/** 5. 使用 IOUtils StringWriter */
StringWriter stringWriter = new StringWriter();
IOUtils.copy(inputStream, stringWriter);
System.out.println(stringWriter.toString());

6. 使用 IOUtils.toString 一步到位

/** 6. 使用 IOUtils.toString 一步到位 */
System.out.println(IOUtils.toString(inputStream));

这里我们用了 6 种方式实现,实际还会有更多的方法。简单总结一下这几个方法。
第一种和第二种方法使用原始的循环读取,代码量比较大。第三和第四种方法使用了 JDK 封装好的 API 可以明显减少代码量, 同时 Stream API 可以让我们将代码写成一行,更方便书写。最后使用 IOUtils 工具类(commons-io 库), 听名字就知道是专门做 IO 用的,它也提供了两种方式,第五种框架提供了更加开放,灵活的方式叫做 copy 方法,也就是说除了 copy 到 String 还可以 copy 到其他地方。第六种就完全的定制化,就是专门用来转 String 的,当然定制化的结果就是不灵活,但对于单纯转 String 这个需求来说却是最方便、最省事的。其实我们平时编程也是一样,对于一个产品需求有时候不需要暴露太多的开放性的选择,针对需求提供一个简单粗暴的实现方式也许是最佳选择。
最后补充一句,我们平时可以多关注框架,用到的时候直接拿过来省时省力,减少代码量。当然有兴趣的话我们也可以深入学习框架内部的设计和实现。

原文地址


DAY4.  面试官:写个内存泄漏的例子

我们都是知道 Java 自带垃圾回收机制,内存泄漏这事好像跟 Java 程序员关系不大。所以,写 Java 程序一般会比 C/C++ 程序轻松一些。记得前领导写 C++ 代码时说过一句话,“写 C++ 程序一定会漏的,只不过是能不能被发现而已”。所以看来 C/C++ 程序员还是比较苦逼的,虽然他们经常鄙视 Java 程序员,哈哈~~。
尽管 Java 程序出现出现内存泄漏的可能性较少,但不代表不会出现。如果你哪天去面试,面试官让你用 Java 写一个内存泄漏的例子,你有思路吗?下面我就举一个内存泄漏的例子。

public final class ClassLoaderLeakExample {
static volatile boolean running = true;
/**
* 1. main 函数,逻辑比较简单只是创建一个 LongRunningThread 线程,并接受停止的指令
*/
public static void main(String[] args) throws Exception {
Thread thread = new LongRunningThread();
try {
thread.start();
System.out.println("Running, press any key to stop.");
System.in.read();
} finally {
running = false;
thread.join();
}
} /**
* 2. 定义 LongRunningThread 线程,该线程做的事情比较简单,每隔 100ms 调用 loadAndDiscard 方法
*/
static final class LongRunningThread extends Thread {
@Override public void run() {
while(running) {
try {
loadAndDiscard();
} catch (Throwable ex) {
ex.printStackTrace();
}
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
System.out.println("Caught InterruptedException, shutting down.");
running = false;
}
}
}
} /**
* 3. 定义一个 class loader - ChildOnlyClassLoader,它在我们的例子中至关重要。
* ChildOnlyClassLoader 专门用来装载 LoadedInChildClassLoader 类,
* 逻辑比较简单,读取 LoadedInChildClassLoader 类的 .class 文件,返回类对象。
*/
static final class ChildOnlyClassLoader extends ClassLoader {
ChildOnlyClassLoader() {
super(ClassLoaderLeakExample.class.getClassLoader());
} @Override protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (!LoadedInChildClassLoader.class.getName().equals(name)) {
return super.loadClass(name, resolve);
}
try {
Path path = Paths.get(LoadedInChildClassLoader.class.getName()
+ ".class");
byte[] classBytes = Files.readAllBytes(path);
Class<?> c = defineClass(name, classBytes, 0, classBytes.length);
if (resolve) {
resolveClass(c);
}
return c;
} catch (IOException ex) {
throw new ClassNotFoundException("Could not load " + name, ex);
}
}
} /**
* 4. 编写 loadAndDiscard 方法的代码,也就是在 LongRunningThread 线程中被调用的方法。
* 该方法创建 ChildOnlyClassLoader 对象,用来装载 LoadedInChildClassLoader 类,将结果赋值给 childClass 变量,
* childClass 调用 newInstance 方法来创建 LoadedInChildClassLoader 对象。
* 每次调用 loadAndDiscard 方法,都会加载一次 LoadedInChildClassLoader 类并创建其对象。
*/
static void loadAndDiscard() throws Exception {
ClassLoader childClassLoader = new ChildOnlyClassLoader();
Class<?> childClass = Class.forName(
LoadedInChildClassLoader.class.getName(), true, childClassLoader);
childClass.newInstance();
} /**
* 5. 定义 LoadedInChildClassLoader 类
* 该类中定义了一个 moreBytesToLeak 字节数组,初始大小比较大是为了尽快模拟出内存泄漏的结果。
* 在类的构造方法调用 threadLocal 的 set 方法存储对象本身的引用。
*/
public static final class LoadedInChildClassLoader {
static final byte[] moreBytesToLeak = new byte[1024 * 1024 * 10]; private static final ThreadLocal<LoadedInChildClassLoader> threadLocal
= new ThreadLocal<>(); public LoadedInChildClassLoader() {
threadLocal.set(this);
}
}
}

这是完整的例子, 可以按照注释中的序号的顺序阅读代码。最后运行代码,在 ClassLoaderLeakExample 类所在的目录下执行以下命令

javac ClassLoaderLeakExample.java
java -cp . ClassLoaderLeakExample

运行后会打印 "Running, press any key to stop." 等一分钟左右就会报内存不足的错误 "java.lang.OutOfMemoryError: Java heap space" 。
简单梳理一下逻辑,loadAndDiscard 方法会不断地被调用,每次被调用在该方法中都会加载一次 LoadedInChildClassLoader 类,每加载一次类就会创建一个新的threadLocal 和 moreBytesToLeak 属性。虽然创建的 LoadedInChildClassLoader 对象是局部变量,但退出 loadAndDiscard  方法后该对象仍然不会被回收,因为 threadLocal 保存了该对象的引用,对象保存了对类的引用,而类保存了对类加载器的引用,类加载器反过来保存对它已加载的类的引用。因此虽然退出 loadAndDiscard 方法,该对象对我们不可见了,但是它永远不会被回收。随着每次加载的类越来越多,创建的 moreBytesToLeak 越来越多并且内存得不到清理,会导致 OutOfMemory 错误。

为了对比你可以去掉自定义类加载器这个参数,loadAndDiscard 方法中的代码修改如下:

Class<?> childClass = Class.forName(
LoadedInChildClassLoader.class.getName(), true, childClassLoader);
//改为:
Class<?> childClass = Class.forName(
LoadedInChildClassLoader.class.getName());

再运行就不会出现 OOM 的错误。修改之后,无论 loadAndDiscard 方法被调用多少次都只会加载一次 LoadedInChildClassLoader 类,也就是说只有一个 threadLocal 和 moreBytesToLeak 属性。当再次创建 LoadedInChildClassLoader 对象时,threadLocal 会设置成当前的对象,之前 set 的对象就没有任何变量引用它,因此之前的对象会被回收。

原文地址


DAY5.  为什么密码用 char[] 存储而不用String

周五,放松一下。一起来看一个无需写代码的问题“为什么 Java 程序中用 char[] 保存密码而不用 String”。既然提到密码,我们用脚指头想想也知道肯定是出于安全性的考虑。具体的是为什么呢?我这里提供两点答案供你参考。

先说第一点,也是最重要的一点。String 存储的字符串是不可变的,也就是说用它存储密码后,这块内存是无法被人为改变的。并且只能等 GC 将其清除。如果有其他进程恶意将内存 dump 下来,就可能会造成密码泄露。
然而使用 char[] 存储密码对我们来说就是可控的,我们可以在任何时候将 char[] 的内容设置为空或者其他无意义的字符,从而保证密码不会长期驻留内存。相对使用 String 存储密码来说更加安全。

再说说第二点,假设我们在程序中无意地将密码打印到日志中了。如果使用 String 存储密码将会被明文输出,而使用 char[] 存储密码只会输出地址不会泄露密码。
这两点都是从安全性的角度出发。

第一点更侧重防止密码驻留内存不安全,第二点则侧重防止密码驻留外存。虽然第二点发生的概率比较低,但也给了我们一个新的视角。

以上便是 Stack Overflow 的第一周周报,希望对你有用,后续会继续更新,如果想看日更内容欢迎关注公众号。

欢迎关注公众号「渡码」,分享更多高质量内容

StackOverflow 周报 - 这些高关注的问题你是否都会的更多相关文章

  1. StackOverflow 周报 - 与高关注的问题过过招(Java)

    本篇文章是 Stack Overflow 周报的第二周,共收集了 4 道高关注的问题和对应的高赞回答.公众号「渡码」为日更,欢迎关注. DAY1.  serialVersionUID 的重要性 关注: ...

  2. StackOverflow 周报 - 第四周高质量问题的问答(Java、Python)

    这是 Stack Overflow 第三周周报,由于本周周四外出,所以只有三篇内容.两篇 Java.一篇 Python.公众号「渡码」为日更,欢迎关注. DAY1. 枚举对象 == 和 equals ...

  3. StackOverflow 周报 - 高质量问题的问答(Java、Python)

    这是 Stack Overflow 第三周周报,本周加入了 Python 的内容,原计划两篇 Java.两篇 Python.但明天过节所以今天就先把周报发了,两篇 Java.一篇 Python.公众号 ...

  4. 【Python + Selenium】Mock Testing 是啥?一个so上的高票答案。

    There are many kinds of testing which really made me confused. To be honest, I've never heard of som ...

  5. Twitter 高并发高可用架构

    解决 Twitter的“问题”就像玩玩具一样,这是一个很有趣的扩展性比喻.每个人都觉得 Twitter很简单,一个菜鸟架构师随便摆弄一下个可伸缩的 Twitter就有了,就这么简单.然而事实不是这样, ...

  6. Nginx:论高并发,在座各位都是渣渣

    NGINX 在网络应用中表现超群,在于其独特的设计.许多网络或应用服务器大都是基于线程或者进程的简单框架,NGINX突出的地方就在于其成熟的事件驱动框架,它能应对现代硬件上成千上万的并发连接. NGI ...

  7. (转)MySQL高可用解决方案

    MySQL高可用解决方案 原文:http://www.ywnds.com/?p=5565 有这么两个概念,数据库的可靠性和数据库的可用性,可靠性指的是数据可靠,而可用性指的是服务可用.但是不管是可靠性 ...

  8. 前端工作面试问题--摘取自github

    前端工作面试问题 本文包含了一些用于考查候选者的前端面试问题.不建议对单个候选者问及每个问题 (那需要好几个小时).只要从列表里挑选一些,就能帮助你考查候选者是否具备所需要的技能. 备注: 这些问题中 ...

  9. (译文)Python中的staticmethod与classmethod

    原文是stackoverflow的一则高票回答,原文链接 可能之前也有人翻译过,但是刚好自己也有疑惑,所以搬运一下,个人水平有限所以可能翻译存在误差,欢迎指正(如侵删). 尽管classmethod和 ...

随机推荐

  1. Openstack中用keypair生成和访问虚机的方法

    Openstack中用keypair生成和访问虚机的方法 标签:task   iso   perm   cte   生成   复制   vol   rsa   sla Openstack中用镜像文件生 ...

  2. JSP一二章笔试题

    一. 什么是B/S架构,什么是C/S架构B/S(Browser/Server) 浏览器/服务器 C/S(Client/Server) 客户端/服务器 二. B/S架构的工作原理 浏览器请求服务器 通过 ...

  3. springBoot数据校验与统一异常处理

    概念 异常,在程序中经常发生,如果发生异常怎样给用户一个良好的反馈体验就是我们需要处理的问题.以前处理异常信息,经常都是给前端一个统一的响应,如数据错误,程序崩溃等等.没办法指出哪里出错了,这是一种对 ...

  4. Python基础总结之异常、调试代码第十二天开始(新手可相互督促)

    年薪20万的梦想,加油! 我们在写代码的时候,控制台经常会报错,因为某种错误,导致我们的程序停止,且不再运行下面的代码. 我们看一个错误的代码示例: def add_1(): #没有参数 print( ...

  5. Java匹马行天下之J2EE框架开发——Spring—>用IDEA开发Spring程序(01)

    一.心动不如行动 一.创建项目 *注:在IDEA中我创建的Maven项目,不了解Maven的朋友可以看我之前的博客“我们一起走进Maven——知己知彼”,了解Maven后可以看我之前的博客“Maven ...

  6. zxing 扫码第三方SDK版本不兼容问题

    在AndroidStudio环境下,或许会遇到下面的问题: Error:Execution failed for task ':app:preDebugAndroidTestBuild'. > ...

  7. 伽马变换(一些基本的灰度变换函数)基本原理及Python实现

    1. 基本原理 变换形式 $$s=cr^{\gamma}$$ c与$\gamma$均为常数 可通过调整$\gamma$来调整该变换,最常用于伽马校正与对比度增强 2. 测试结果 图源自skimage ...

  8. Python 使用k-means方法将列表中相似的句子聚为一类

    由于今年暑假在学习一些自然语言处理的东西,发现网上对k-means的讲解不是很清楚,网上大多数代码只是将聚类结果以图片的形式呈现,而不是将聚类的结果表示出来,一下代码将老师给的代码和网上的代码结合了一 ...

  9. 佳木斯集训Day3

    D3是我的巅峰 D3的出题人毒瘤!!!T3放了一道莫队,我们全体爆炸,到现在只有一个奆老A掉了T3 据说lkh被晓姐姐D了 T1是个26进制数,当时在考场上想了好久才想到(太次了)注意需要处理一下溢出 ...

  10. 【Java例题】5.1 多项式计算

    1. 计算下列多项式的值. pn=an*x^n+...+a1*x+a0其中,"^"表示乘方. x.n以及ai(i=0,1,...,n-1)由键盘输入. package chapte ...