系列文章:
前言:
- 不要盲目相信并行的效率比串行高(于个人,原因未知。猜测:一部分原因是需要评估使用并行方式所创建的线程,线程调度以及同步处理等所造成的资源损耗,与串行方式相比的效益如何,比如单个线程计算量小且有同步的需求,那么并行一般比串行效率低。这部分简单理解下就好,不一定是正确的)
- 如下图所示,调换代码块 A、B 的位置,两者的运行效率发生了变化——代码块后运行的效率较高(于个人,原因未知)
// 代码块 A
long singleStart = System.nanoTime();
int singleCount = words.stream()
.filter(word -> word.length() > 12)
.map(String::length)
.reduce(0, (a, b) -> a + b, (a, b) -> a + b);
long singleEnd = System.nanoTime();
System.out.println("nanos = " + (singleEnd - singleStart) + ", stream = " + singleCount);
// 代码块 B
long parallelStart = System.nanoTime();
int parallelCount = words.parallelStream()
.filter(word -> word.length() > 12)
.map(String::length).reduce(0, (a, b) -> a + b, (a, b) -> a + b);
long parallelEnd = System.nanoTime();
System.out.println("nanos = " + (parallelEnd - parallelStart) + ", parallelStream = " + parallelCount);
代码地址:
题目:
1、编写一个第 2.1 节中 for 循环的并行版本。获取处理器的数量,创造出多个独立的线程,每个都只处理列表的一个片段,然后将它们各自的结果汇总起来。(我们不希望这些线程都更新一个计数器,为什么?)
这道题的思路是创建N个线程,每个线程分别去处理 list 的不同部分,最后汇总每个线程计算出来的个数即可。
为什么不共用一个计数器:公用一个计数器,需要增加同步处理,由于每条线程对该计数器的操作很频繁,使用同步处理,效率未必会比串行高。
2、请想办法验证一下,对于获得前5个最长单词的代码,一旦找到第 5 个最长的单词后,就不会调用 filter 方法了。一个简单的方法是记录每次的方法调用。
public static void main(String[] args) {
try {
String content = new String(Files.readAllBytes(Paths
.get("C:\\Users\\Administrator\\Desktop\\user_main.c")), StandardCharsets.UTF_8);
List<String> words = Arrays.asList(content.split("[\\P{L}]+"));
// 注意不要使用并行模式 words.parallelStream()
words.stream().filter(word -> {
if(word.length() > 12){
System.out.println("bingo");
return true;
} else {
return false;
}
}).limit(3).peek(e -> System.out.println(e)).count();
} catch (IOException e) {
e.printStackTrace();
}
}
3、要统计长单词的数量,使用 parallelStream 与使用 stream 有什么区别?请具体测试一下。你可以在调用方法之前和之后调用 System.nanoTime, 并打印出它们之间的区别。如果你有较快速度的计算机,可以试着处理一个较大的文档(例如《战争与和平》的英文原著)。
不要盲目相信并行的效率比串行高(于个人,原因未知。猜测:一部分原因是需要评估使用并行方式所创建的线程,线程调度以及同步处理等所造成的资源损耗,与串行方式相比的效益如何,比如单个线程计算量小且有同步的需求,那么并行一般比串行效率低。这部分简单理解下就好,不一定是正确的)
如下图所示,调换代码块 A、B 的位置,两者的运行效率发生了变化——代码块后运行的效率较高(于个人,原因未知)
public static void main(String[] args) {
String content1 = new String(Files.readAllBytes(Paths
.get("C:\\Users\\Administrator\\Desktop\\user_main.c")), StandardCharsets.UTF_8);
List<String> words = Arrays.asList(content1.split("[\\P{L}]+"));
// 代码块 A
long parallelStart = System.nanoTime();
int parallelCount = words.parallelStream()
.filter(word -> word.length() > 12)
.map(String::length).reduce(0, (a, b) -> a + b, (a, b) -> a + b);
long parallelEnd = System.nanoTime();
System.out.println("nanos = " + (parallelEnd - parallelStart) + ", parallelStream = " + parallelCount);
// 代码块 B
long singleStart = System.nanoTime();
int singleCount = words.stream()
.filter(word -> word.length() > 12)
.map(String::length).reduce(0, (a, b) -> a + b, (a, b) -> a + b);
long singleEnd = System.nanoTime();
System.out.println("nanos = " + (singleEnd - singleStart) + ", stream = " + singleCount);
}
4、假设你有一个数组 int[] values = {1, 4, 9, 16}。那么
Stream.of(values)
的结果是什么?你如何获得一个 int 类型的流?
public static void main(String[] args) {
int[] values = { 1, 4, 9, 16 };
// 返回 Stream<int[]>
Stream<int[]> result = Stream.of(values);
IntStream is = Arrays.stream(values);
is.forEachOrdered(System.out::println);
}
5、使用 Stream.iterate 来得到一个包含随机数字的无限流——不许调用 Math.random, 只能直接实现一个线性同余生成器(LCG)。在这个生成器中,你可以从 x 0 x_0 x0 = seed 开始,然后根据合适的 a、c 和 m 值产生 x n + 1 x_{n+1} xn+1 = (a * x n x_n xn + c) % m。你应该实现一个含有参数 a、c、m 和 seed 的方法,并返回一个 Stream 对象。可以试一下 a = 25214903917、c = 11 且 m = 2 48 2^{48} 248
public static void main(String[] args) {
random(0L, 25214903917L, 11, (long)Math.pow(2, 48));
// 看到一个牛逼点的写法
random(0L, 25214903917L, 11, 1L << 48);
}
private static void random(long seed, long a, long c, long m) {
Stream.iterate(seed, item -> (a * item + c) % m).limit(10).forEach(System.out::println);
}
6、第 2.3 节中的 characterStream 方法不是很好用,它需要先填充一个数组列表,然后再转变为一个流。试着写一行基于流的代码。一个方法是构造一个从 0 开始到 s.length() - 1 的整数流,然后使用 s::charAt 方法引用来映射它。
public static void main(String[] args) {
String s = "dsfsgfdngjkfdlfkweoirirknr";
Stream.iterate(0, i -> i + 1)
.limit(s.length())
.map(i -> s.charAt(i))
.forEach(System.out::println);
}
7、假设你的老板让你编写一个方法
public static <T> boolean isFinite(Stream<T> stream)
。为什么这不是一个好主意?不管怎样,先试着写一写。
无限的流,那肯定会计算不完啊。除非你做特殊处理,达到某个时间或者某个数量,就退出计数,当成无限的流。这里可以参考另一篇博文
public static void main(String[] args) {
long count = Stream.iterate(0, i -> i++)
/*.limit(1000000)*/
.count();
System.out.println(count);
}
8、编写一个方法
public static <T> Stream<T> zip(Stream<T> first, Stream<T> second)
,依次调换流 first 和 second 中的元素位置,直到其中一个流结束位置。
我不会。参考另一篇博文
9、将一个 Stream<ArrayList> 中的全部元素连接为一个 ArrrayList。试着用三种不同形式的聚合方法来实现。
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>() {
{
add("1");
add("2");
add("3");
add("4");
add("5");
}
};
List<String> list2 = new ArrayList<String>() {
{
add("11");
add("22");
add("33");
add("44");
add("55");
}
};
List<String> list3 = new ArrayList<String>() {
{
add("111");
add("222");
add("333");
add("444");
add("555");
}
};
// method1(Stream.of(list1, list2, list3)).stream().forEach(i ->
// System.out.printf("%s\t", i));
// method2(Stream.of(list1, list2, list3)).stream().forEach(i ->
// System.out.printf("%s\t", i));
method3(Stream.of(list1, list2, list3)).stream()
.forEach(i -> System.out.printf("%s\t", i));
}
public static <T> List<T> method1(Stream<List<T>> source) {
return source.flatMap(item -> item.stream())
.collect(Collectors.toList());
}
public static <T> List<T> method2(Stream<List<T>> source) {
return source.map(item -> item.stream())
.reduce((a, b) -> Stream.concat(a, b)).get()
.collect(Collectors.toList());
}
public static <T> List<T> method3(Stream<List<T>> source) {
List<T> result = new ArrayList<T>();
source.forEach(item -> {
result.addAll(item);
});
return result;
}
10、编写一个可以用于计算 Stream 平均值的聚合方法。为什么不能直接计算综合再除以 count();
使用流计算出总数之后,流就关闭了,如果你还想计算调用 count() 来计算数量,那么你还得再开一个流。
public static void main(String[] args) {
Stream<Double> s1 = Stream.of(0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 89.0);
System.out.println(average1(s1));
Stream<Double> s2 = Stream.of(0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 89.0);
System.out.println(average2(s2));
}
private static Double average1(Stream<Double> source) {
DoubleSummaryStatistics s = source.collect(Collectors
.summarizingDouble(Double::doubleValue));
return s.getAverage();
}
private static Double average2(Stream<Double> source) {
return source.collect(Collectors.averagingDouble(Double::doubleValue));
}
11、我们应该可以将流的结果并发收集到一个 ArrayList 中,而不是将多个 ArrayList 合并起来。由于对集合不相交部分的并发操作是线程安全的,所以我假设这个 ArrayList 的初始大小即为流的大小。如何能做到这一点。
没看懂,不理解。参考另一篇博文
12、如第 2.13 节所示,通过更新一个 AtomicInteger 数组来计算一个并行 Stream 宏的所有短单词。使用原子操作方法 getAndIncrement 来安全地增加每个计数器的值。
public static void main(String[] args) {
String content = "";
try {
content = new String(Files.readAllBytes(Paths
.get("C:\\Users\\Administrator\\Desktop\\user_main.c")), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
Stream<String> words1 = Stream.of(content.split("[\\P{L}]+"));
long count = System.nanoTime();
System.out.println(sum1(words1));
System.out.println(System.nanoTime() - count);
}
private static int sum1(Stream<String> source) {
AtomicInteger result = new AtomicInteger();
source.parallel().filter(item -> {
if(item.length() < 12){
result.getAndIncrement();
return true;
} else {
return false;
}
}).forEach(item -> System.out.printf("%s\t", item));
return result.get();
}
13、重复上一个练习,这次使用 collect 方法、Collectors.groupingBy 方法和Collectors.counting 方法来过滤出短单词。
public static void main(String[] args) {
String content = "";
try {
content = new String(Files.readAllBytes(Paths
.get("C:\\Users\\Administrator\\Desktop\\user_main.c")), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
Stream<String> words1 = Stream.of(content.split("[\\P{L}]+"));
long count = System.nanoTime();
System.out.println(sum1(words1));
System.out.println(System.nanoTime() - count);
}
private static Long sum1(Stream<String> source) {
return source
.parallel()
.collect(Collectors.groupingBy(item -> item.length() < 12, Collectors
.counting())).get(true);
}