Jade Dungeon

常用工具

随机数:Java 8

在Java8中java.util.Random类的一个非常明显的变化就是新增了返回随机数流(random Stream of numbers)的一些方法。

下面的代码是创建一个无穷大的double类型的数字流,这些数字在0(包括0)和1( 不包含1)之间。

Random random = new Random();
DoubleStream doubleStream = random.doubles();

下面的代码是创建一个无穷大的int类型的数字流,这些数字在0(包括0)和100(不包括 100)之间。

Random random = new Random();
IntStream intStream = random.ints(0, 100);

那么这些无穷大的数字流用来做什么呢?接下来,我通过一些案例来分析。记住,这些 无穷大的数字流只能通过某种方式被截断(limited)。

示例1:创建10个随机的整数流并打印出来:

intStream.limit(10).forEach(System.out::println);

示例2:创建100个随机整数:

List<Integer> randomBetween0And99 = 
	intStream.limit(100).boxed().collect(Collectors.toList());

对于高斯伪随机数(gaussian pseudo-random values)来说,没有等价于 random.doubles()方法所创建的流,然而,如果用java8所提供的功能是非常容易实现 的。

Random random = new Random();
DoubleStream gaussianStream = 
	Stream.generate(random::nextGaussian).mapToDouble(e -> e);

这里,我使用了Stream.generate() api,并传入Supplier 类的对象作为参数,这个 对象是通过调用Random类中的方法 nextGaussian()创建另一个高斯伪随机数。

接下来,我们来对double类型的伪随机数流和double类型的高斯伪随机数流做一个更加 有意思的事情,那就是获得两个流的随机数的分配情况。预期的结果是:

  • double类型的伪随机数是均匀的分配的,
  • 而double类型的高斯伪随机数应该是正态分布的。

通过下面的代码,我生成了一百万个伪随机数,这是通过java8提供的api实现的:

Random random = new Random();
DoubleStream doubleStream = random.doubles(-1.0, 1.0);
LinkedHashMap<Range, Integer> rangeCountMap = 
	doubleStream.limit(1000000).boxed().map(Ranges::of).collect(
			Ranges::emptyRangeCountMap, (m, e) -> m.put(e, m.get(e) + 1), 
			Ranges::mergeRangeCountMaps);
 
rangeCountMap.forEach((k, v) -> System.out.println(k.from() + "\t" + v));

代码的运行结果如下:

-1      49730
-0.9    49931
-0.8    50057
-0.7    50060
-0.6    49963
-0.5    50159
-0.4    49921
-0.3    49962
-0.2    50231
-0.1    49658
0       50177
0.1     49861
0.2     49947
0.3     50157
0.4     50414
0.5     50006
0.6     50038
0.7     49962
0.8     50071
0.9     49695

为了类比,我们再生成一百万个高斯伪随机数:

Random random = new Random();
DoubleStream gaussianStream = 
	Stream.generate(random::nextGaussian).mapToDouble(e -> e);
LinkedHashMap<Range, Integer> gaussianRangeCountMap =
    gaussianStream.filter(e -> (e >= -1.0 && e < 1.0)).limit(1000000).boxed()
		.map(Ranges::of).collect(Ranges::emptyRangeCountMap, 
				(m, e) -> m.put(e, m.get(e) + 1), Ranges::mergeRangeCountMaps);
 
gaussianRangeCountMap.forEach(
		(k, v) -> System.out.println(k.from() + "\t" + v));

上面代码输出的结果恰恰与我们预期结果相吻合,即:double类型的伪随机数是均匀分配 的,而double类型的高斯伪随机数应该是正态分布的。

用伪随机数与高斯伪随机数所得的结果分别为:

图示

完整代码:

package lambda;
 
import org.junit.Test;
 
import java.util.*;
import java.util.stream.*;
 
public class RandomStreamTest {
 
@Test
public void testRandomStream() {
Random random = new Random();
DoubleStream doubleStream = random.doubles(-1.0, 1.0);
LinkedHashMap<Range, Integer> rangeCountMap = doubleStream.limit(1000000)
.boxed()
.map(Ranges::of)
.collect(Ranges::emptyRangeCountMap, (m, e) -> m.put(e, m.get(e) + 1), Ranges::mergeRangeCountMaps);
 
rangeCountMap.forEach((k, v) -> System.out.println(k.from() + "\t" + v));
}
 
@Test
public void testGaussianRandomStream() {
Random random = new Random();
DoubleStream gaussianStream = Stream.generate(random::nextGaussian).mapToDouble(e -> e);
LinkedHashMap<Range, Integer> gaussianRangeCountMap =
gaussianStream
.filter(e -> (e >= -1.0 && e < 1.0))
.limit(1000000)
.boxed()
.map(Ranges::of)
.collect(Ranges::emptyRangeCountMap, (m, e) -> m.put(e, m.get(e) + 1), Ranges::mergeRangeCountMaps);
 
gaussianRangeCountMap.forEach((k, v) -> System.out.println(k.from() + "\t" + v));
}
 
 
public static class Range {
private final double from;
private final double to;
 
public Range(double from, double to) {
this.from = from;
this.to = to;
}
 
public double from() {
return from;
}
 
public double to() {
return to;
}
 
@Override
public String toString() {
return "From: " + from + " To: " + to;
 
}
 
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
 
Range range = (Range) o;
 
if (Double.compare(range.from, from) != 0) return false;
if (Double.compare(range.to, to) != 0) return false;
 
return true;
}
 
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(from);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(to);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
 
public static class Ranges {
private static LinkedHashMap<Integer, Range> rangeMap = new LinkedHashMap<>();
static {
rangeMap.put(-10, new Range(-1.0, -0.9));
rangeMap.put(-9, new Range(-0.9, -0.8));
rangeMap.put(-8, new Range(-0.8, -0.7));
rangeMap.put(-7, new Range(-0.7, -0.6));
rangeMap.put(-6, new Range(-0.6, -0.5));
rangeMap.put(-5, new Range(-0.5, -0.4));
rangeMap.put(-4, new Range(-0.4, -0.3));
rangeMap.put(-3, new Range(-0.3, -0.2));
rangeMap.put(-2, new Range(-0.2, -0.1));
rangeMap.put(-1, new Range(-0.1, 0.0 ));
rangeMap.put(0, new Range(0.0, 0.1 ));
rangeMap.put(1, new Range(0.1, 0.2 ));
rangeMap.put(2, new Range(0.2, 0.3 ));
rangeMap.put(3, new Range(0.3, 0.4 ));
rangeMap.put(4, new Range(0.4, 0.5 ));
rangeMap.put(5, new Range(0.5, 0.6 ));
rangeMap.put(6, new Range(0.6, 0.7 ));
rangeMap.put(7, new Range(0.7, 0.8 ));
rangeMap.put(8, new Range(0.8, 0.9 ));
rangeMap.put(9, new Range(0.9, 1.0 ));
}
 
public static Range of(double d) {
int key = (int) Math.floor(d * 10);
return rangeMap.get(key);
}
 
public static LinkedHashMap<Range, Integer> emptyRangeCountMap() {
LinkedHashMap<Range, Integer> rangeCountMap = new LinkedHashMap<>();
for (Range range : rangeMap.values()) {
rangeCountMap.put(range, 0);
}
return rangeCountMap;
}
 
public static void mergeRangeCountMaps(Map<Range, Integer> map1, Map<Range, Integer> map2) {
for (Range range : rangeMap.values()) {
map1.put(range, map1.get(range) + map2.get(range));
}
}
}

调用Bash

public static String runBashScript(String script, String[] envp, File dir)
		throws IOException, InterruptedException 
{
	String result = null;
	Process ps = Runtime.getRuntime().exec(
			new String[] { "/bin/bash", "-c", script }, 
			envp, dir);
	ps.waitFor();

	BufferedReader br = new BufferedReader(new InputStreamReader(
			ps.getInputStream()));
	StringBuffer sb = new StringBuffer();
	String line;
	while ((line = br.readLine()) != null) {
		sb.append(line).append("\n");
	}
	result = sb.toString();
	return result;
}

注意:

  • 对于awk类的外部程序一定要new String[]{"/bin/sh","-c",shStr} 才可以获得流。
  • ps.waitFor()方法等待外部程序执行完毕。

取得源代码的行号

在 C/C++ 的程序,编译器提供了两个宏来支持取得源文件中的行号和文件名,这两个宏是 __FILE____LINE__你可以如下的方法打印行号和文件名:

#include <stdio.h>
int main()
{
	fprintf(stdout,"[%s:%d] Hello World!",__FILE__,__LINE__);
	return 0;
}

但是在 JAVA 下没有这两个宏,那么我们如何来取得文件名和行号,翻阅 JDK ,我们找到 StackTraceElement这个类。这个类可以从Throwable取得,另外也可以从Thread类 取得,通过这些我写如下的一个打印行号的测试程序:

public class LineNo {

	public static int getLineNumber() {
		return Thread.currentThread().getStackTrace()[2].getLineNumber();
	}

	public static String getFileName() {
		return Thread.currentThread().getStackTrace()[2].getFileName();
	}

	public static void main(String args[]) {
		System.out.println("["+getFileName()+" : "+ getLineNumber()
			+"]"+"Hello World!");
	}

}

留下一个问题,上面程序中的 magic 数字 2 代表什么含义呢?

混淆

Aliasing指不同类型的多个别名指向同一个位置。

图示

栈和堆

方法和对象在运行时内存中的位置:

图示

equals和hashCode

hashCode()方法用来提升性能,它与equals()的关系:

  • 如果equalhashCode必须一致。
  • 如果hashCode,不一定equal

图示

集合

集合类型Collection与工具类Collections

图示

集合类型层级:

图示

异常

红色为Checked Exception,必须被捕获或在方法是声明抛出:

图示