《On Java 8》中文版,又名《Java编程思想》 第5版
接下来的都是个人学习过程的笔记,不是总结,没有参考价值,但是这本书很棒
文件和路径
import java.nio.file.*;
import java.net.URI;
import java.io.File;
import java.io.IOException;
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));
show("RegularFile", Files.isRegularFile(p));
show("Directory", Files.isDirectory(p));
show("Absolute", p.isAbsolute());
show("FileName", p.getFileName());
show("Parent", p.getParent());
show("Root", p.getRoot());
System.out.println("******************");
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
info(Paths.get("C:", "path", "to", "nowhere", "NoFile.txt"));
Path p = Paths.get("PathInfo.java");
info(p);
Path ap = p.toAbsolutePath();
info(ap);
info(ap.getParent());
try {
info(p.toRealPath());
} catch(IOException e) {
System.out.println(e);
}
URI u = p.toUri();
System.out.println("URI: " + u);
Path puri = Paths.get(u);
System.out.println(Files.exists(puri));
File f = ap.toFile(); // Don't be fooled
}
}
我是在idea里跑上面的代码,发现Path p = Paths.get("PathInfo.java");
找不到文件,得写成Path p = Paths.get("src\\chapter17\\PathInfo.java");
才能找到这个文件,看起来是从项目文件夹为起点开始搜寻的,呆逼了点,但也有道理,谁TM没事儿往src文件夹下面存数据文件的。
最后,你会在 Path 中看到一些有点欺骗的东西,这就是调用 toFile() 方法会生成一个 File 对象。听起来似乎可以得到一个类似文件的东西(毕竟被称为 File ),但是这个方法的存在仅仅是为了向后兼容。虽然看上去应该被称为"路径",实际上却应该表示目录或者文件本身。这是个非常草率并且令人困惑的命名,但是由于 java.nio.file 的存在我们可以安全地忽略它的存在。
上面这段话很有趣
选取路径部分片段
路径分析
Paths的增减修改
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TestPaths {
public static void main(String[] args) throws IOException {
Path p= Paths.get(".",".");
System.out.println(p);
System.out.println(p.toRealPath());
System.out.println(p.normalize());
System.out.println(p.toAbsolutePath());
System.out.println(p.toAbsolutePath().normalize());
}
}
首先看上面的代码,他们的打印结果如下,各种方法都在文档里写的都挺明白的
.\.
D:\code\OnJava8
D:\code\OnJava8\.\.
D:\code\OnJava8
package chapter17;
// files/AddAndSubtractPaths.java
import java.nio.file.*;
import java.io.IOException;
public class AddAndSubtractPaths {
static Path base = Paths.get(".").toAbsolutePath().normalize();
static void show(int id, Path result) {
if(result.isAbsolute())
System.out.println("(" + id + ")r " + base.relativize(result));
else
System.out.println("(" + id + ") " + result);
try {
System.out.println("RealPath: " + result.toRealPath());
} catch(IOException e) {
System.out.println(e);
}
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
System.out.println(base);
Path p = Paths.get("src/chapter17/AddAndSubtractPaths.java").toAbsolutePath();
show(1, p);
Path convoluted = p.getParent().getParent()
.resolve("strings").resolve("..")
.resolve(p.getParent().getFileName());
show(2, convoluted);
show(3, convoluted.normalize());
Path p2 = Paths.get("..", "..");
show(4, p2);
show(5, p2.normalize());
show(6, p2.toAbsolutePath().normalize());
Path p3 = Paths.get(".").toAbsolutePath();
Path p4 = p3.resolve(p2);
show(7, p4);
show(8, p4.normalize());
Path p5 = Paths.get("src/chapter17").toAbsolutePath();
show(9, p5);
show(10, p5.resolveSibling("chapter14"));
show(11, Paths.get("nonexistent"));
}
}
由于我用的是idea,因此上面的代码我稍微修改了一点,其实没啥好讲的,看方法名都能看出来啥意思。resolve
就是在路径末尾加新的路径,参数可以是字符串也可以是其他的Path
对象。normalize
方法就是规范化标准化的意思,但对比(7)和(8)我们发现,normalize
方法只会对.
起作用,对..
不起作用。resolveSibling
方法就是找当前路径的兄弟路径
目录
// files/Directories.java
import java.util.*;
import java.nio.file.*;
import onjava.RmDir;
public class Directories {
static Path test = Paths.get("test");
static String sep = FileSystems.getDefault().getSeparator();
static List<String> parts = Arrays.asList("foo", "bar", "baz", "bag");
static Path makeVariant() {
Collections.rotate(parts, 1);
return Paths.get("test", String.join(sep, parts));
}
static void refreshTestDir() throws Exception {
if(Files.exists(test))
RmDir.rmdir(test);
if(!Files.exists(test))
Files.createDirectory(test);
}
public static void main(String[] args) throws Exception {
refreshTestDir();
Files.createFile(test.resolve("Hello.txt"));
Path variant = makeVariant();
// Throws exception (too many levels):
try {
Files.createDirectory(variant);
} catch(Exception e) {
System.out.println("Nope, that doesn't work.");
}
populateTestDir();
Path tempdir = Files.createTempDirectory(test, "DIR_");
Files.createTempFile(tempdir, "pre", ".non");
Files.newDirectoryStream(test).forEach(System.out::println);
System.out.println("*********");
Files.walk(test).forEach(System.out::println);
}
static void populateTestDir() throws Exception {
for(int i = 0; i < parts.size(); i++) {
Path variant = makeVariant();
if(!Files.exists(variant)) {
Files.createDirectories(variant);
Files.copy(Paths.get("Directories.java"),
variant.resolve("File.txt"));
Files.createTempFile(variant, null, null);
}
}
}
}
没啥,只是想说这段代码很好,解释的也很好
文件系统
路径监听
package chapter17;
import java.io.IOException;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.util.concurrent.*;
public class PathWatcher {
static Path test = Paths.get("test");
static void delTxtFiles() {
try {
Files.walk(test)
.filter(f ->
f.toString()
.endsWith(".txt"))
.forEach(f -> {
try {
System.out.println("deleting " + f);
Files.delete(f);
} catch(IOException e) {
throw new RuntimeException(e);
}
});
} catch(IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
Directories.refreshTestDir();
Directories.populateTestDir();
Files.createFile(test.resolve("Hello.txt"));
WatchService watcher = FileSystems.getDefault().newWatchService();
test.register(watcher, ENTRY_DELETE);
Executors.newSingleThreadScheduledExecutor()
.schedule(PathWatcher::delTxtFiles,
250, TimeUnit.MILLISECONDS);
WatchKey key = watcher.take();
for(WatchEvent evt : key.pollEvents()) {
System.out.println("evt.context(): " + evt.context() +
"\nevt.count(): " + evt.count() +
"\nevt.kind(): " + evt.kind());
System.exit(0);
}
}
}
所谓路径监听就是监听这个路径下发生了什么事件。
Executors.newSingleThreadScheduledExecutor() ;
这一行的功能再创建一个线程来执行删除的动作,意思就是推迟250毫秒后,执行PathWatcher::delTxtFiles
(实际上就是个Runnable
对象)
如下代码可以查看目前程序中的线程,其中currentGroup.enumerate(lstThreads);
的意思就是把当前线程组里的线程拷贝到那个线程数组里。如果把下面的代码,插入到WatchKey key = watcher.take();
之前,会看到线程里多了一个线程号:2 = Thread-0
,就是用来执行删除的线程;而如果放在它之后,会看到线程多了一个线程号:3 = pool-1-thread-1
这个就是用来监听事件的线程。然后正如书中所说,WatchKey key = watcher.take();
会阻塞,直到那个新建的线程执行了删除行动。而System.exit(0);
的存在是因为如果不手动退出,那么监听事件的进程会一直不关闭,导致程序无法退出。
package others;
public class TestThread {
public static void main(String[] args) {
ThreadGroup currentGroup =
Thread.currentThread().getThreadGroup();
int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads];
currentGroup.enumerate(lstThreads);
for (int i = 0; i < noThreads; i++)
System.out.println("线程号:" + i + " = " + lstThreads[i].getName());
}
}
另外,我看了一下MILLISECONDS
是一个枚举对象,其定义如下
MILLISECONDS {
public long toNanos(long d) { return x(d, C2/C0, MAX/(C2/C0)); }
public long toMicros(long d) { return x(d, C2/C1, MAX/(C2/C1)); }
public long toMillis(long d) { return d; }
public long toSeconds(long d) { return d/(C3/C2); }
public long toMinutes(long d) { return d/(C4/C2); }
public long toHours(long d) { return d/(C5/C2); }
public long toDays(long d) { return d/(C6/C2); }
public long convert(long d, TimeUnit u) { return u.toMillis(d); }
int excessNanos(long d, long m) { return 0; }
},
然后我发现MILLISECONDS.toNanos(d)
是可以调用的,这里我觉得可以把这种调用看做静态方法调用。
package chapter17;
// files/TreeWatcher.java
// {ExcludeFromGradle}
import java.io.IOException;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.util.concurrent.*;
public class TreeWatcher {
static void watchDir(Path dir) {
try {
WatchService watcher =
FileSystems.getDefault().newWatchService();
dir.register(watcher, ENTRY_DELETE);
Executors.newSingleThreadExecutor().submit(() -> {
try {
WatchKey key = watcher.take();
for (WatchEvent evt : key.pollEvents()) {
System.out.println(
"evt.context(): " + evt.context() +
"\nevt.count(): " + evt.count() +
"\nevt.kind(): " + evt.kind());
System.exit(0);
}
} catch (InterruptedException e) {
return;
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
Directories.refreshTestDir();
Directories.populateTestDir();
Files.walk(Paths.get("test"))
.filter(Files::isDirectory)
.forEach(TreeWatcher::watchDir);
PathWatcher.delTxtFiles();
}
}
上面这段代码是为每个目录都注册一个监听器,其中submit
方法是立刻执行的意思。而观察结果我们可以看到,只有可能只删除了两个文件(也可能是一个),并没有把全部txt文件删除掉,这是因为System.exit(0);
的存在。
文件查找
package chapter17;
import java.nio.file.*;
public class Find {
public static void main(String[] args) throws Exception {
Path test = Paths.get("test");
Directories.refreshTestDir();
Directories.populateTestDir();
// Creating a *directory*, not a file:
Files.createDirectory(test.resolve("dir.tmp"));
PathMatcher matcher = FileSystems.getDefault()
.getPathMatcher("glob:**/*.{tmp,txt}");
Files.walk(test)
.filter(matcher::matches)
.forEach(System.out::println);
System.out.println("***************");
PathMatcher matcher2 = FileSystems.getDefault()
.getPathMatcher("glob:*.tmp");
Files.walk(test)
.map(Path::getFileName)
.filter(matcher2::matches)
.forEach(System.out::println);
System.out.println("***************");
Files.walk(test) // Only look for files
.filter(Files::isRegularFile)
.map(Path::getFileName)
.filter(matcher2::matches)
.forEach(System.out::println);
}
}
没啥好讲的,Path::getFileName
非绑定方法调用,该操作会将完整路径减少到末尾的名称,返回的仍然是Path
类型的对象
文件读写
package chapter17;
import java.util.*;
import java.nio.file.*;
public class ListOfLines {
public static void main(String[] args) throws Exception {
Files.readAllLines(
Paths.get("Cheese.dat"))
.stream()
.filter(line -> !line.startsWith("//"))
.map(line ->
line.substring(0, line.length()/2))
.forEach(System.out::println);
}
}
readAllLines()
有一个重载版本,包含一个Charset
参数来存储文件的 Unicode 编码。默认使用的是UTF8
// files/StreamInAndOut.java
import java.io.*;
import java.nio.file.*;
import java.util.stream.*;
public class StreamInAndOut {
public static void main(String[] args) {
try(
Stream<String> input =
Files.lines(Paths.get("src/chapter17/StreamInAndOut.java"));
PrintWriter output =
new PrintWriter("StreamInAndOut.txt")
) {
input.map(String::toUpperCase)
.forEachOrdered(output::println);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
上面代码使用了try-resource-with,参见第15章——异常。
上面使用了input.map
和String::toUpperCase
对输入进行逐行处理,然后又应用forEachOrdered
进行输出,这当然是一种非常好的流式编程习惯。
但是我可能要结合多个输入流该怎么办呢。
第一种方法就是使用传统的循环,还记得每个流都有一个iterator
方法么,
package chapter17;
// files/StreamInAndOut.java
import java.io.*;
import java.nio.file.*;
import java.util.Iterator;
import java.util.stream.*;
public class StreamInAndOut {
public static void main(String[] args) {
try(
Stream<String> input =
Files.lines(Paths.get("src/chapter17/StreamInAndOut.java"));
PrintWriter output =
new PrintWriter("StreamInAndOut.txt")
) {
// input.map(String::toUpperCase)
// .forEachOrdered(output::println);
Iterator<String> iterator = input.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
就像上面这样,这样当然不太美观
第二种方法就是第14章的终端操作下收集那一小节里提到过,组合两个流的方法,当然也还是利用iterator,我把那段代码复制过来
// streams/MapCollector.java
import java.util.*;
import java.util.stream.*;
class Pair {
public final Character c;
public final Integer i;
Pair(Character c, Integer i) {
this.c = c;
this.i = i;
}
public Character getC() { return c; }
public Integer getI() { return i; }
@Override
public String toString() {
return "Pair(" + c + ", " + i + ")";
}
}
class RandomPair {
Random rand = new Random(47);
// An infinite iterator of random capital letters:
Iterator<Character> capChars = rand.ints(65,91)
.mapToObj(i -> (char)i)
.iterator();
public Stream<Pair> stream() {
return rand.ints(100, 1000).distinct()
.mapToObj(i -> new Pair(capChars.next(), i));
}
}
public class MapCollector {
public static void main(String[] args) {
Map<Integer, Character> map =
new RandomPair().stream()
.limit(8)
.collect(
Collectors.toMap(Pair::getI, Pair::getC));
System.out.println(map);
}
}