写给大忙人看的 Java SE 8练习题(二)

系列文章:

写给大忙人看的 Java SE 8练习题(一)

前言:

  • 不要盲目相信并行的效率比串行高(于个人,原因未知。猜测:一部分原因是需要评估使用并行方式所创建的线程,线程调度以及同步处理等所造成的资源损耗,与串行方式相比的效益如何,比如单个线程计算量小且有同步的需求,那么并行一般比串行效率低。这部分简单理解下就好,不一定是正确的
  • 如下图所示,调换代码块 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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目录 第1章 lambda表达式 0 1.1 为什么要使用lambda表达式 2 1.2 lambda表达式的语法 4 1.3 函数式接口 6 1.4 方法引用 8 1.5 构造器引用 10 1.6 变量作用域 10 1.7 默认方法 14 1.8 接口中的静态方法 17 练习 18 第2章 Stream API 20 2.1 从迭代器到Stream操作 22 2.2 创建Stream 23 2.3 filter、map和flatMap方法 25 2.4 提取子流和组合流 26 2.5 有状态的转换 27 2.6 简单的聚合方法 28 2.7 Optional类型 29 2.7.1 使用Optional值 29 2.7.2 创建可选值 30 2.7.3 使用flatMap来组合可选值函数 31 2.8 聚合操作 32 2.9 收集结果 33 2.10 将结果收集到Map中 35 2.11 分组和分片 37 2.12 原始类型流 40 2.13 并行流 42 2.14 函数式接口 44 练习 45 第3章使用lambda编程 48 3.1 延迟执行 50 3.2 lambda表达式的参数 51 3.3 选择一个函数式接口 52 3.4 返回函数 55 3.5 组合 56 3.6 延迟 58 3.7 并行操作 59 3.8 处理异常 60 3.9 lambda表达式和泛型 63 3.10 一元操作 65 练习 67 第4章 JavaFX 72 4.1 Java GUI编程简史 74 4.2 你好,JavaFX! 75 4.3 事件处理 76 4.4 JavaFX属性 77 4.5 绑定 80 4.6 布局 85 4.7 FXML 91 4.8 CSS 95 4.9 动画和特殊效果 97 4.10 不寻常的控件 100 练习 103 第5章新的日期和时间API 106 5.1 时间线 108 5.2 本地日期 110 5.3 日期校正器 113 5.4 本地时间 114 5.5 带时区的时间 115 5.6 格式化和解析 119 5.7 与遗留代码互操作 122 练习 123 第6章并发增强 126 6.1 原子值 128 6.2 ConcurrentHashMap改进 131 6.2.1 更新值 132 6.2.2 批量数据操作 134 6.2.3 Set视图 136 6.3 并行数组操作 137 6.4 可完成的Future 138 6.4.1 Future 138 6.4.2 编写Future 139 6.4.3 Future流水线 139 6.4.4 编写异步操作 141 练习 143 第7章 JavaScript引擎——Nashorn 146 7.1 从命令行运行Nashorn 148 7.2 从Java运行Nashorn 149 7.3 调用方法 150 7.4 构造对象 151 7.5 字符串 153 7.6 数字 153 7.7 使用数组 154 7.8 列表和映射 155 7.9 lambda表达式 156 7.10 继承Java类及实现Java接口 157 7.11 异常 158 7.12 Shell脚本 159 7.12.1 执行Shell命令 159 7.12.2 字符串插值 160 7.12.3 脚本输入 161 7.13 Nashorn和JavaFX 162 练习 164 第8章杂项改进 166 8.1 字符串 168 8.2 数字类 168 8.3 新的数学函数 169 8.4 集合 170 8.4.1 集合类中添加的方法 170 8.4.2 比较器 171 8.4.3 Collections类 173 8.5 使用文件 173 8.5.1 读取文件行的流 173 8.5.2 遍历目录项的流 175 8.5.3 Base64编码 176 8.6 注解 177 8.6.1 可重复的注解 177 8.6.2 可用于类型的注解 179 8.6.3 方法参数反射 181 8.7 其他一些细微的改进 182 8.7.1 Null检查 182 8.7.2 延迟消息 182 8.7.3 正则表达式 183 8.7.4 语言环境 183 8.7.5 JDBC 185 练习 185 第9章你可能错过的Java 7特性 188 9.1 异常处理改进 190 9.1.1 try-with-resources语句 190 9.1.2 忽略异常 191 9.1.3 捕获多个异常 192 9.1.4 更简单地处理反射方法的异常 193 9.2 使用文件 193 9.2.1 Path 194 9.2.2 读取和写入文件 196 9.2.3 创建文件和目录 197 9.2.4

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值