mirror of
https://github.com/LingCoder/OnJava8.git
synced 2026-08-24 15:03:29 +08:00
Merge pull request #145 from blackwatchcup/master
The Rest of the Chapter and files
This commit is contained in:
@@ -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**,并且还会删除任何冗余元素。
|
||||
|
||||
@@ -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表达式的缺陷。
|
||||
|
||||
- 创建和运行任务
|
||||
任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级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 -->
|
||||
## 创建和运行任务
|
||||
|
||||
Reference in New Issue
Block a user