Merge pull request #145 from blackwatchcup/master

The Rest of the Chapter and files
This commit is contained in:
Joe
2019-07-19 13:53:09 +08:00
committed by GitHub
2 changed files with 73 additions and 5 deletions

View File

@@ -2,17 +2,23 @@
<!-- File -->
# 第十七章 文件
在丑陋的Java I/O编程方式诞生多年以后Java终于简化了文件读写的基本操作。这种"困难方式"的全部细节都在[Appendix: I/O Streams]()。如果你读过这个部分就会认同Java设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来是非常常用的由于I/O糟糕的设计以至于
>在丑陋的Java I/O编程方式诞生多年以后Java终于简化了文件读写的基本操作。这种"困难方式"的全部细节都在
[Appendix: I/O Streams](.\Appendix-IO-Streams.md)。如果你读过这个部分就会认同Java设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来是非常常用的由于I/O糟糕的设计以至于
很少有人能够在不依赖其他参考代码的情况下完成打开文件的操作。
好像Java设计者终于意识到了Java使用者多年来的痛苦在Java7中对此引入了巨大的改进。这些新元素被放在**java.nio.file**包下面,过去人们通常把**nio**中的**n**理解为**new**即新的**io**,现在更应该当成是**non-blocking**非阻塞**io**(**io**就是*input/output输入/输出*)。**java.nio.file**库终于将Java文件操作带到与其他编程语言相同的水平。最重要的是Java8新增的streams与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件
1. 文件或者目录的路径;
2. 文件本身。
<!-- File and Directory Paths -->
## 文件和目录路径
### `Paths`
一个**Path**对象表示一个文件或者目录的路径是一个跨操作系统OS和文件系统的抽象目的是在构造路径时不必关注底层操作系统代码可以在不进行修改的情况下运行在不同的操作系统上。**java.nio.file.Paths**类包含一个重载方法**static get()**,该方法方法接受一系列**Strings**字符串或一个*统一资源标识符*(URI)作为参数,并且进行转换返回一个**Path**对象:
```java
// files/PathInfo.java
import java.nio.file.*;
@@ -24,7 +30,7 @@ public class PathInfo {
static void show(String id, Object p) {
System.out.println(id + ": " + p);
}
static void info(Path p) {
show("toString", p);
show("Exists", Files.exists(p));
@@ -38,7 +44,7 @@ public class PathInfo {
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
info(Paths.get("C:", "path", "to", "nowhere", "NoFile.txt"));
info(Paths.get("C:", "path", "to", "nowhere", "NoFile.txt"));
Path p = Paths.get("PathInfo.java");
info(p);
Path ap = p.toAbsolutePath();
@@ -116,7 +122,7 @@ true
*/
```
我已经在这一章第一个程序的**main()**方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用**/**或者**\\**路径分隔符进行分隔。你可以看到我运行在Windows 10上的程序输出。
我已经在这一章第一个程序的 **main()** 方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用 **/** 或者 **\\** 路径分隔符进行分隔。你可以看到我运行在Windows 10上的程序输出。
当**toString()**方法生成完整形式的路径,你可以看到**getFileName()** 方法总是返回当前文件名。
通过使用**Files**工具类(我们接下类将会更多的使用它),可以测试一个文件是否存在,测试是否是一个"真正"的文件还是一个目录等等。"Nofile.txt"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。"PathInfo.java"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为"存在"。一旦我们将其转换为绝对路径,我们将会得到一个从"C:"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,**toRealPath()** 将返回实际情况下的**Path**,并且还会删除任何冗余元素。

View File

@@ -212,7 +212,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的
Java是一种多线程语言如果您了解它们是否存在并发问题。因此有许多Java程序正在使用中或者只是偶然工作或者大部分时间工作并且不时地发生问题因为。有时这种问题是相对良性的但有时它意味着丢失有价值的数据如果你没有意识到并发问题你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统则可以暴露或放大这些类型的问题。基本上了解并发性使您意识到正确的程序可能会表现出错误的行为。
<!-- The Brutal Truth -->
## 残酷的真相
## <span id = "The-Brutal-Truth">残酷的真相</span>
当人类开始烹饪他们的食物时,他们大大减少了他们的身体分解和消化食物所需的能量。烹饪创造了一个“外化的胃”,从而释放出追去其他的的能力。火的使用促成了文明。
@@ -248,14 +248,76 @@ Java实验告诉我们结果是悄然灾难性的。程序员很容易陷入
这是我们将在本章的其余部分介绍的内容。请记住本章的重点是使用最新的高级Java并发结构。使用这些使得您的生活比旧的替代品更加轻松。但是您仍会在遗留代码中遇到一些低级工具。有时你可能会被迫自己使用其中的一些。附录[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。
- Parallel Streams并发流
到目前为止我已经强调了Java 8 Streams提供的改进语法。现在您对该语法作为一个粉丝我希望感到满意您可以获得额外的好处您可以通过简单地将parallel添加到表达式来并行化流。这是一种简单强大坦率地说是利用多处理器的惊人方式
添加parallel来提高速度似乎是微不足道的但是它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel到Stream表达式的缺陷。
- 创建和运行任务
任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识本节介绍了一种比并行流或CompletableFuturesExecutor更复杂的机制。执行者管理一些低级Thread对象Java中最原始的并发形式。您创建一个任务然后将其交给Executorto运行。
有多种类型的Executor用于不同的目的。在这里我们将展示规范形式代表创建和运行任务的最简单和最佳方法。
- 终止长时间运行的任务
任务独立运行因此需要一种机制来关闭它们。典型的方法使用了一个标志这引入了共享内存的问题我们将使用Java的“Atomic”库来回避它。
- Completable Futures
当您将衣服带到干洗店时他们会给您一张收据。你继续完成其他任务最终你的衣服很干净你可以拿起它。收据是您与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。
Future比以前的方法更方便但你仍然必须出现并用收据取出干洗等待任务没有完成。对于一系列操作Futures并没有真正帮助那么多。
Java 8 CompletableFuture是一个更好的解决方案它允许您将操作链接在一起因此您不必将代码写入接口排序操作。有了CompletableFuture完美的结合就可以更容易地做出“采购原料组合成分烹饪食物提供食物清理菜肴储存菜肴”等一系列链式操作。
- 死锁
某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,等待另一个被阻止的任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入僵局。
如果在运行程序时没有立即出现死锁,则会出现最大的问题。您的系统可能容易出现死锁,并且只会在某些条件下死锁。程序可能在某个平台上运行正常,例如您的开发机器,但是当您将其部署到不同的硬件时会开始死锁。
死锁通常源于细微的编程错误;一系列无辜的决定,最终意外地创建了一个依赖循环。本节包含一个经典示例,演示了死锁的特性。
我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是完成配置。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来加速计划。
- 努力,复杂,成本
<!-- Parallel Streams -->
## 并行流
Java 8流的一个显着优点是在某些情况下它们可以很容易地并行化。这来自仔细的库设计特别是流使用内部迭代的方式 - 也就是说它们控制着自己的迭代器。特别是它们使用一种特殊的迭代器称为Spliterator它被限制为易于自动分类。这产生了相当神奇的结果只能说.parallel并且你的流中的所有东西都是作为一组并行任务运行的。如果您的代码是使用Streams编写的那么并行化以提高速度似乎微不足道。
例如考虑来自Streams的Prime.java。查找质数可能是一个耗时的过程我们可以看到该程序的计时
```java
// concurrent/ParallelPrime.java
import java.util.*;
import java.util.stream.*;
import static java.util.stream.LongStream.*;
import java.io.*;
import java.nio.file.*;
import onjava.Timer;
public class ParallelPrime {
static final int COUNT = 100_000;
public static boolean isPrime(long n){
return rangeClosed(2, (long)Math.sqrt(n)).noneMatch(i -> n % i == 0);
}
public static void main(String[] args)
throws IOException {
Timer timer = new Timer();
List<String> primes =
iterate(2, i -> i + 1)
.parallel() // [1]
.filter(ParallelPrime::isPrime)
.limit(COUNT)
.mapToObj(Long::toString)
.collect(Collectors.toList());
System.out.println(timer.duration());
Files.write(Paths.get("primes.txt"), primes, StandardOpenOption.CREATE);
}
}
/*
Output:
1224
*/
```
请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止激进的优化;如果我们没有对结果做任何事情那么一个狡猾的编译器可能会观察到程序没有意义并且消除了计算这不太可能但并非不可能。请注意使用nio2库编写文件的简单性在[文件](./17-Files.md)一章中有描述)。
<!-- Creating and Running Tasks -->
## 创建和运行任务