Java核心卷Ⅱ(原书第10版)笔记(上)

您所在的位置:网站首页 java核心技术卷二pdf Java核心卷Ⅱ(原书第10版)笔记(上)

Java核心卷Ⅱ(原书第10版)笔记(上)

2023-10-23 04:44| 来源: 网络整理| 查看: 265

Java核心卷Ⅱ(原书第10版)笔记(上)

写在最前面,个人认为,卷Ⅱ更适合当手册使用,更多的是讲API的使用,前两章内容比较实际,要是合并到卷一就好了。

文章目录 第1章 Java SE 8 的流库1.1 从迭代到流的操作1.2 流的创建1.3 filter、map 和 flatMap 方法1.4 抽取子流和连接流1.5 其他的流转换1.6 简单简约1.7 Optional 类型1.7.2 不适合使用 Optional 值的方式1.7.3 创建 Optional 值1.7.4 用 flatMap 来构建 Optional 值的函数1.8 收集结果1.9 收集到映射表中1.10 群组和分区1.11 下游收集器1.12 约简操作1.13 基本类型流1.14 并行流 第2章 输入与输出2.1 输入/输出流2.1.1 读写字节2.1.2 完整的流家族2.1.3 组合输入/输出流过滤器 2.2 文本输入与输出2.2.1 如何写出文本输出2.2.2 如何读入文本输入2.2.4 字符编码方式获得编码的方式 2.3 读写二进制数据2.3.1 `DataInput` 和 `DataOutput` 接口2.3.2 随机访问文件2.3.3 ZIP 文档ZIP 读入ZIP 写入 2.4 对象输入/输出流与序列化2.4.1 保存和加载序列化对象序列号 2.4.2 理解对象序列化的文件格式(溜了溜了)2.4.3 修改默认的序列化机制2.4.4 序列化单例和类型安全的枚举2.4.5 版本管理2.4.6 为克隆使用序列化 2.5 操作文件2.5.1 Path2.5.2 读写文件处理中等长度的文本文件处理比较长的,或二进制的文本文件 2.5.3 创建文件和目录2.5.4 复制、移动和删除文件2.5.5 获取文件信息2.5.6 访问目录中的项2.5.7 使用目录流2.5.8 ZIP 文件系统 2.6 内存映射文件2.6.2 缓冲区数据结构2.6.3 文件加锁机制文件加锁是依赖OS的,注意事项: 2.7 正则表达式(略) 第3章 XML3.1 XML 概述XML 与 HTML 的区别: 3.2 解析 XML 文档3.3 验证 XML 文档3.4 使用 XPath 来定位信息3.5 使用命名空间3.6 流机制解析器SAX 解析器StAX 解析器 3.7 生成 XML 文档3.8 XSL 转换(略) 第 4 章网络4.1 连接到服务器4.2 实现服务器4.3 可中断套接字4.4 获取 Web 数URL 和 URI4.4.2 使用 URLConnection 获取信息 4.5 发送 E-mail 第 5 章 数据库编程5.1 JDBC 的设计5.1.1 JDBC 驱动程序类型5.1.2 JDBC 的典型用法 5.2 结构化查询语言(学过数据库都会涉及,略)5.3 JDBC 配置(目前都是Spring集成配置,略)5.4 使用 JDBC 语句5.4.3 分析 SQL 异常 5.5 执行查询操作5.6 可滚动和可更新的结果集(略)5.7 行集(略)5.8 元数据5.9 事务(目前框架都交给Spring处理,简单看)5.10 高级 SOL 类型SOL 数据类型及其对应的 Java 类型 5.11 Web 与企业应用中的连接管理(略,连接池与数据库配置等都集成于Spring)

第1章 Java SE 8 的流库

流提供了一钟让我们可以在比集合更高的概念级别上指定计算的数据视图,它是在Java SE 8 中引入的,用来以“做什么而非怎么做”的方式处理集合。可对集合类型使用 stream 方法生成一个流,或使用 parallelStream 方法生成并行流。

引元:以为例List,其中String就是引元。

1.1 从迭代到流的操作

流表面上看起来和集合很类似,都可以转化和获取数据,但也存在明显差别:

流并不存储元素。这些元素可能存储在底层的集合中,或者是按需生成。流的操作不会修改其他数据源。例如,filter方法不会从原流中移除元素,而是新生成一个流。流的操作是尽可能惰性执行的。直到终止操作才处理之前的惰性内容,例如,只想找到前5个长单词而不是所有的长单词,filter方法会在匹配到第5个单词后停止过滤,代码如下所示。因此,我们甚至可用操作无限流(因为流是惰性的,直到终止操作才处理之前的惰性内容)。

List collect = list.stream().filter(t -> t.length() > 13).limit(5).collect(Collectors.toList());

这部分代码,我们建立的一个包含三个阶段的操作管道:

创建一个流。stream parallelStream指定将初始化流转化为其他流的中间操作,也可能包含多个步骤。filter limit应用终止操作,从而产生结果。这个结果会强制执行之前的惰性操作。从此之后,这个流就再也不能用了。collect count 1.2 流的创建 // 可用 Collection 接口的 stream/parallelStream 方法将任何集合转化为一个流。 list.stream(); list.parallelStream(); // 可用 Stream.of 方法生成流,of方法具有可变参数。 Stream song = Stream.of("gently", "down", "the", "Stream"); // 可用 Array.stream(array, from, to)可以从数据中位于 from 和 to 的元素中创建一个流(前闭后开)。 Arrays.stream(array, startInclusive, endExclusive); // 可用 Stream.empty 方法创建不包含任何元素的流。 Stream silence = Stream.empty(); /** * Stream 接口有两个用于创建无限流的静态方法。 * 1. generate 方法,参数是一个函数(Supplier)。 * 2. iterate 方法,参数是一个“种子”值和一个函数(UnaryOperation),函数会反复应用到之前的结果上。 */ Stream echos = Stream.generate(() -> "Echo"); // 流中有“无限”个“Echo” Stream integers = Stream.iterate(0, n -> n + 1); // 0,1,2,3,4,…

除了上述代码之外,Java API 中有大量方法都可用产生流,例如,Files.lines(path)方法返回一个包含了文件中所有行的Steam,而Pattern.compile(RegExp).splitAsStream(contents)方法按照某个正则表达式来分割一个CharSequence对象。

1.3 filter、map 和 flatMap 方法 filter :按某种条件过滤。map :按某种方式转化流中的值。flatMap :将map后的内容摊平。 1.4 抽取子流和连接流 limit:会返回一个新的流,在N个元素之后结束(N > 原来流的长度 ? 原来流的长度 : N)。skip:丢弃前N个元素。concat:连接两个流。第一个流不应该是无限的,否则第二个流永远都不会处理。 1.5 其他的流转换 distinct:剔除重复元素,流中排列顺序不变。sorted:接受一个Comparator对象,使用此对象进行排序。peek:接受一个函数,每次获取元素时会调用一次。对于调试之类的很方便。 1.6 简单简约

简约是一钟 终结操作(terminal operation),它会将流简约为可以在程序中使用的非流值。

count:返回流中元素的数量。max 和 min:返回最大值和最小值。返回Optional。getFirst:获得第一个元素。返回Optional。findAny:最好用平行流。获得一个任意匹配的元素。返回Optional。。anyMatch:最好用平行流。判断流中是否有元素与传入条件匹配,返回boolean值。allMatch:最好用平行流。判断流中是否所有元素都与传入条件匹配,返回boolean值。noneMatch:最好用平行流。判断流中是否所有元素都没有与传入条件匹配,返回boolean值。 1.7 Optional 类型

Optional对象是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象。

不存在任何值的情况下产生替代物

orElse:Optional不包含对象,则返回orElse参数内容(非函数,类型为T)。orElseGet:Optional不包含对象,则返回通过orElseGet传入的函数计算的内容。orElseThrow:Optional不包含对象,则抛出异常,此异常通过orElseThrow传入的异常构造方法构造。

其值存在时使用

ifPresent:此方法接受一个函数,如果Optional包含对象,则传递给函数处理,否则不做任何操作 。此方法没有返回值,需要处理返回值请用map。 1.7.2 不适合使用 Optional 值的方式 get: 方法会在 Optional 值存在的情况下获得其中包装的元素,或者在不存在的情况下抛出一个 NoSuchElementException 对象。 只是用于判断是否非空,使用Optional值的方式并不比传统的非空比较(!= null)跟加安全与容易处理。 个人认为:Optional更适合用于包含对象不存在时的备选对象获取的处理。 1.7.3 创建 Optional 值 of:创建Optional对象(不能是null,否则会NullPointerException异常)。empty:创建空的Optional对象。ofNullable:创建Optional对象,可以为null,对象为null返回empty对象,否则返回of对象。 1.7.4 用 flatMap 来构建 Optional 值的函数 flatMap可以理解为先map操作,再做了一个flat摊平操作: /** 从两个用户流中,各取出第一个对象,组成一个新的List。 **/ User user1 = new User(1, "u1"); User user2 = new User(2, "u2"); User user5 = new User(5, "u5"); User user6 = new User(6, "u6"); List lis = new ArrayList(); lis.add(user1); lis.add(user2); List lis2 = new ArrayList(); lis2.add(user5); lis2.add(user6); // listStream 中包含两个List对象 Stream listStream = Stream.of(lis, lis2); // 如果我们使用 map ,u.subList(0, 1).stream() 会产生用户流,流的数量与列表数量相同,此处为两个。 Stream mapStream = Stream.of(lis, lis2).map(u -> u.subList(0, 1).stream()); // 使用 flatMap ,将 map 后的数据摊平(flat),java 帮我们把 map 后的每个内容都抽离出来了变为同一层了。 Stream flatMapStream = Stream.of(lis, lis2).flatMap(u -> u.subList(0, 1).stream()); // 最终完成代码: List figures = Stream.of(lis, lis2).flatMap(u -> u.subList(0, 1).stream()).collect(Collectors.toList()); // 将打印 u1 和 u5 figures.forEach(f -> System.out.println(f.getName()));

可以将对 flatMap 的调用链接起来,从而构建由这些步骤构成的管道,只有所有步骤都成功时,该管道才会成功,如果其中过一个调用返回Optional.empty(),整个结果都会为空。

1.8 收集结果 iterator:返回一个Iterator对象。forEach:将函数应用于每个元素。并行流上将以任意顺序遍历各个元素。forEachOrdered:按顺序将函数应用于每个元素。在并行流中将丧失并行处理的优势。toArray:将流中元素组成数组。默认返回Object[],如果需要指定的类型,请传入构造器。collect:接受一个Collector接口实例。Collectors 类提供了大扯用于生成公共收集器的工厂方法。 Collectors.tolist():将流中内容收集到列表中。Collectors.toSet():将流中内容收集到集中。Collectors.toCollection(TreeSet::new):将流中内容收集到特定的集中。Collectors.joining(","):将流中字符串内容拼接成一个字符串,用 “,” 分割,可不给分割符号。Collectors.summarizing【Int/Long/Double】:传入一个函数,将流的结果简约为总和、平均值、最大值、最小值。将返回一个(Int/Long/Double)SummaryStatistics对象存储结果。 1.9 收集到映射表中 Collectors.toMap(id,value):将流中内容根据函数收集到映射表中。如若发生冲突,有以下处理方案: // 第三个参数为冲突时调用的函数,这里冲突时使用旧值。 Map map = stream.collect( Collectors.toMap( User::getId, User::getName, (existingValue, newValue) -> existingValue)); // 如果想要得到 TreeMap, 那么可以将构造器作为第 4 个引元来提供。 你必须提供一种合并函数。 Map map = stream.collect( Collectors.toMap( User::getId, User::getName, (existingValue, newValue) -> existingValue), TreeMap::new );

每个 toMap 方法,都有一个入参相同的可以产生并发映射表的 toConcurrentMap 方法。 单个并发映射表可以用于并行集合处理。当使用并行流时,共享的映射表比合并映射表要更高效(因为ConcurrentMap可以并行处理鸭)。 注意,元素不再是按照流中的顺序收集的,但是通常这不会有什么问题。

1.10 群组和分区 Collectors.groupingBy:按传入函数排序(传入函数计算出的内容为Key,流中元素为Value)返回Map。Collectors.partitioningBy:流的元素可以分区为两个列表:该函数返回 true 的元素和其他的元素。Collectors.groupingByConcurrent:就会在使用并行流时获得一个被并行组装的并行映射表。这与 toConcurrentMap 方法完全类似 。 1.11 下游收集器

Collectors.groupingBy方法会产生一个映射表,它的每个值都是一个列表。如果想要以某种方式来处理这些列表,就需要提供一个“下游收集器” 。注意,下方的下游收集器应静态导入java.util.stream.Collectors.*的原因,除第一个外,省略Collectors。

Collectors.groupingBy(User::getId, Collectors.toSet()):封装排序对象的Value值为Set。groupingBy(User::getGid, counting()):收集到的元素的个数为Value。groupingBy(User::getGid, summing【Int/Long/Double】):收集到的元素的个数为Value。groupingBy(User::getGid, maxBy(Comparator.comparing(User::getAge))):获得最大值,最小值用minBy。groupingBy(User::getGid, mapping(User::name, maxBy(Comparator.comparing(User::getAge)))):将返回一个Map,Key为用户组ID,Value为同一用户组内年龄最大的用户名。 1.12 约简操作

reduce:接受一个二元函数,并从前两个元素开始持续应用它。如:reduce(Integer::sum),将返回Optional对象。

可以带上一个 幺元值,作为计算的起点,如加法可以使用0作为幺元值。如:reduce(0, Integer::sum)。如果流为空,则会返回幺元值,所以返回类型是T。可以使用reduce来计算累加,但是,当计算被并行化时,会有多个这种类型的计算(关于这么做到并行累加,可以看卷Ⅰ的 “14.7.6 并行数组算法”内容),需要添加第二个函数来辅助处理。如:reduce(O, (total, word) -> total + word.length() ,(total1, total2) -> total1 + total2);如果是数字流,可以使用mapTolnt(String::length).sum(),因为它不涉及装箱操作,更高效。

collect

有时 reduce 会显得并不够通用 。 例如,假设我们想要收集 BitSet 中的结果。如果收集操作是并行的,那么就不能直接将元素放到单个 BitSet 中,因其对象不是线程安全的。 因此,我们不能使用 reduce, 因为每个部分都需要以其自己的空集开始,并且 reduce 只能让我们提供一个么元值。此时,应该使用 collect, 它会接受单个引元 : 一个提供者,它会创建目标类型的新实例,例如散列集的构造器 。一个累积器,它会将一个元素添加到一个实例上,例如 add 方法。一个组合器,它会将两个实例合并成一个,例如 addAll。

下面的代码展示了 collect 方法是如何操作位集的:

BitSet result = stream.collect(BitSet::new, BitSet::set, BitSet::or); 1.13 基本类型流

流库中具有专门的基本类型流 IntStream、LongStream 和 DoubleStream,用来直接存储基本类型值,而无需使用包装器。如果想要存储 short 、 char 、 byte 和 boolean, 可以使用 IntStream, 而对于 float, 可以使用 DoubleStream。

创建IntStream

可以用IntStream.of 和 Arrays.stream方法创建IntStream。也可以用IntStream.range(0, 100);和IntStream.rangeClosed(0, 100);方法创建。CharSequence 接口拥有 codePoints 和 chars 方法,可以生成由字符的 Unicode 码或由 UTF-16 编码机制的码元构成的 IntStream 。

转化为基本类型流

可用mapToInt、mapToLong、mapToDouble将流通过传入的函数,转为基本类型流。

基本类型流转换为对象流

IntStream.range(0, 100).boxed();

基本类型流上的方法与对象流上的方法的差异:

toArray 方法会返回基本类型数组。产生可选结果的方法会返回一个 OptionalInt 、 OptionalLong 或 OptionalDouble 。这些类与 Optional 类类似,但是具有 getAsInt 、 getAsLong 和 getAsDouble 方法,而不是 get 方法。具有返回总和、平均值、最大值和最小值的 sum 、 average 、 max 和 min 方法。对象流没有定义这些方法。summaryStatisties 方法会产生一个类型为 IntSummaryStatistics 、 LongSummaryStatistics 或 DoubleSummaryStatistics 的对象,它们可以同时报告流的总和、平均值、最大值和最小值。

Random 类具有 ints 、 longs 和 doubles 方法,它们会返回由随机数构成的基本类型流。

1.14 并行流

只要在终结方法执行时 ,流处于并行模式,那么所有的中间流操作都将被并行化。当流操作并行运行时,其目标是要让其返回结果与顺序执行时返回的结果相同。重要的是,这些操作可以以任意顺序执行。你的职责是要确保传递给并行流操作的任何函数都可以安全地并行执行,达到这个目的的最佳方式是远离易变状态(可以将其分组,不会多次操作)。

传递给并行流操作的函数不应该被堵寒 。 并行流使用 fork-join 池来操作流的各个部分 。 如果多个流操作被阻塞,那么池可能就无法做任何事情了 。

通过在流上调用 unordered 方法,就可以明确表示我们对排序不感兴趣。还可以通过放弃排序要求来提高 limit 方法的速度。如果只想从流中取出任意 n 个元素,可以调用list.parallelStream().unordered(). limit(n);

不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。 记住,流并不会收集它们的数据,数据总是在单独的集合中。 如果修改了这样的集合,那么流操作的结果就是未定义的 。JDK 文档对这项需求并未做出任何约束 ,并且对顺序流和并行流都采用了这种处理方式。 更准确地讲,因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的 。 例如,下面的操作尽管并不推荐,但是仍旧可以工作:

List wordList = . . . ; Stream words = wordList.stream(); wordList.add("END"); long n = words.distinct().count(); // 但是,下面的代码是错误的: Stream words = wordList.stream(); words.forEach(s -> if (s.length() ZipEntry ze = new ZipEntry(filename); // 将数据写入 zout zout.putNextEntry(ze); zout.closeEntry(); } zout.close();

JAR 文件(在卷 1 笫 13 章中讨论过)只是带有一个特殊项的 ZIP 文件,这个项称作清单。你可以使用 JarInputStream 和 JarOutputStream 类来读写清单项。

ZIP 输入流是一个能够展示流的抽象化的强大之处的实例。当你读入以压缩格式存储的数据时,不必担心边请求边解压数据的问题,而且 ZIP 格式的字节源并非必须是文件,也可以是来自网络连接的 ZIP 数据。事实上,当 Applet 的类加载器读入 JAR 文件时,它就是在读入和解压来自网络的数据。

2.4 对象输入/输出流与序列化 2.4.1 保存和加载序列化对象

序列化对象需要考虑很多问题,可查看《Effectvie Java》相关建议。

对希望在对象输出流中存储或从对象输人流中恢复的所有类都应进行一下修改,这些类必须实现 Serializable 接口(此接口是一个 标记接口,无任何方法)。

将对象写入指定流:ObjectOutputStream.writeObject。从指定流中读取对象:ObjectInputStream.readObject。

对象流类都实现了 DataInput / DataOutput 接口 。

序列号

每个对象都是用一个 序列号 (serial number) 保存的,这就是这种机制之所以称为对象序列化的原因。用于保证对于原本被多个类引用的对象,这个对象只会被实际写入到流一次,读取时也只构造一次,多个原引用它的类指向它。下面是其算法:

写入对象

对你遇到的每一个对象引用都关联一个序列号。对于每个对象,当第一次遇到时,保存其对象数据到输出流中。如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为 x 的对象相同”。

读回对象时,整个过程是反过来的 。

对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联。当遇到“与之前保存过的序列号为 x 的对象相同”标记时,获取与这个顺序号相关联的对象引用 。 2.4.2 理解对象序列化的文件格式(溜了溜了) 2.4.3 修改默认的序列化机制

transient:用于标记不想序列化的字段,如:private transient String name。

可通过创建(Object没这两个方法)以下方法,自定义读取/写入对象的逻辑:

// readObject 和 writeObject 方法是私有的,并且只能被序列化机制调用 。 private void readObject(ObjectinputStream in) throws IOException, ClassNotFoundException; private void writeObject(ObjectOutputStream out) throws IOException; // 之后,数据域就再也不会被自动序列化,取而代之的是调用这些方法。 除了让序列化机制来保存和恢复对象数据,类还可以定义它自己的机制。为了做到这一点,这个类必须实现 Externalizable 接口(它继承了Serializable接口)。Externalizable接口有两个需要实现的方法: // readExternal 和 writeExternal 方法是公共的。特别是,readExternal 还潜在地允许修改现有对象的状态,PS:书中的入参有误没有Stream(如:应该是 ObjectInput 不是 ObjectInputStream)。 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; public void writeExternal(ObjectOutput out) throws IOException;

与前面一节描述的 readObject 和 writeObject 不同,这些方法对包括超类数据在内的整个对象的存储和恢复负全责。在写出对象时,序列化机制在输出流中仅仅只是记录该对象所属的类。在读人可外部化的类时,对象输入流将用无参构造器创建一个对象,然后调用readExternal 方法。

个人理解:Serializable标记的对象Java会自动将其序列化(会帮你自动创建对象,自动处理字段),可能会占用更多空间。而Externalizable接口则完全由开发人员自己操作对象的读写,需要开发人员自己创建对象,麻烦但可用更有效的控制序列化的存储内容。

2.4.4 序列化单例和类型安全的枚举

请记住向遗留代码中所有类型安全的枚举以及向所有支持单例设计模式的类中添加 readResolve 方法。应该返回已存在对象(应可用==操作符比较),而不是新建。

readResolve :在对象被序列化之后就会调用它。它必须返回一个对象,而该对象之后会成为 readObject 的返回值。 2.4.5 版本管理

如果一个类具有名为 serialVersionUID 的静态数据成员,它就不再需要人工地计算其指纹,而只衙直接使用这个值。如果这个类只有方法产生了变化,那么在读人新对象数据时是不会有任何问题的(计算结果相同)。但是如果数据域产生了变化,那么就可能会有问题。

对象输入流会将这个类当前版本的数据域与被序列化的版本中的数据域进行比较,当然,对象流只会考虑非瞬时(非transient)和非静态的数据域。如果这两部分数据域之间名字匹配而类型不匹配,那么对象输入流不会尝试将一种类型转换成另一种类型,因为这两个对象不兼容;如果被序列化的对象具有在当前版本中所没有的数据域,那么对象输入流会忽略这些额外的数据;如果当前版本具有在被序列化的对象中所没有的数据域,那么这些新添加的域将被设置成它们的默认值(如果是对象则是 null, 如果是数字则为 0, 如果是 boolean 值则是 false) 。

2.4.6 为克隆使用序列化

序列化机制有一种很有趣的用法:即提供了一种克隆对象的简便途径,只要对应的类是可序列化的即可。其做法很简单:直接将对象序列化到输出流中,然后将其读回。这样产生的新对象是对现有对象的一个 深拷贝 (deep copy) 。在此过程中,我们不必将对象写出到文件中,因为可以用 ByteArrayOutputStream将数据保存到字节数组中(把对象写入流中,再读回来)。

我们应该当心这个方法,尽管它很灵巧,但是它通常会比显式地构建新对象并复制或克隆数据域的克隆方法慢得多。

2.5 操作文件

Path 接口和 Files 类是在 Java SE 7 中新添加进来的,它们用起来比自 JDK 1.0 以来就一直使用的 File 类要方便得多。

2.5.1 Path

Path 表示的是一个目录名序列,其后还可以跟着一个文件名。

Paths.get 方法接受一个或多个字符串(可能绝对,可能相对),并将它们用默认文件系统的路径分隔符(类 Unix 文件系统是 /, Windows 是\)连接起来。然后它解析连接起来的结果,如果其表示的不是给定文件系统中的合法路径,那么就抛出 InvalidPathException 异常。这个连接起来的结果就是一个 Path 对象。

basePath.resolve(q) :

如果 q 是绝对路径,则结果就是 q 。否则,根据文件系统的规则,将 “basePath 后面跟着 q” 作为结果。

workPath.resolveSibling("temp"):如果 workPath 是 /opt/myapp/work,将创建 /opt/myapp/temp。

basePath.relativize(r):如果 basePath 是 /home/cay,r 是 /home/fred/myprog,将创建 … /fred/myprog。

basePath.normalize():将移除所有冗余的.和…部件(或文件系统认为的所有冗余部件),例如:规范化 /home/cay/…/fred/./myprog 将产生 /home/fred/myprog 。

basePath.toAbsolutePath():将产生给定路径的绝对路径,该绝对路径从根部件开始,例如/home/fred/input.txt 或 c:\Users\fred\input.txt 。

Path 类有许多有用的方法用来将路径断开。下面的代砃示例展示了其中部分最有用的方法:

Path p = Paths.get("/home", "fred", "myprog.properties"); Path parent= p.getParent(); // the path /home/fred Path file= p.getFileName(); // the path myprog.properties Path root = p.getRoot(); // the path / 正如你已经在卷 I 中看到的,还可以从 Path 对象中构建 Scanner 对象: Scanner in = new Scanner(Paths.get("/home/fred/input.txt"));

偶尔,你可能需要与遗留系统的 API 交互,它们使用的是 File 类而不是 Path 接口。Path 接口有一个 toFile 方法,而 File 类有一个 toPath 方法。

2.5.2 读写文件 处理中等长度的文本文件 // 取文件的所有内容 byte[] bytes = Files.readAllBytes(path); // 将文件当作字符串读入 String content = new String(bytes, charset); // 将文件当作行序列读入 List lines= Files.readAllLines(path, charset); // 写出一个字符串到文件中 Files.write(path, content.getBytes(charset)); // 向指定文件追加内容 Files.write(path, content.getBytes(charset), StandardOpenOption.APPEND); // 一个行的集合写出到文件中 Files.write(path, lines); 处理比较长的,或二进制的文本文件 // 应该使用所熟知的输入/输出流或者读入器/写出器: InputStream in = Files.newInputStream(path); OutputStream out = Files.newOutputStream(path); Reader in = Files.newBufferedReader(path, charset); Writer out= Files.newBufferedWriter(path, charset); 2.5.3 创建文件和目录

Files.createDirectory(path):创建新目录。路径中除最后一个部件外,其他部分都必须是已存在的。 Files.createDirectories(path):创建一个空文件。如果文件已经存在了,那么这个洞用就会抛出异常。检查文件是否存在和创建文件是原子性的,如果文件不存在,该文件就会被创建,并且其他程序在此过程中是无法执行文件创建操作的。

给定位置或者系统指定位置创建临时文件或临时目录

Path newPath = Files.createTempFile(dir, prefix, suffix); Path newPath = Files.createTempFile(prefix, suffix); Path newPath = Files.createTempDirectory(dir, prefix); Path newPath = Files.createTempDirectory(prefix); // 其中, dir 是一个 Path 对象, prefix 和 suffix 是可以为 null 的字符串。 // 例如,调用 Files.createTempFile(null," .txt") 可能会返回一个像 /tmp/1234405522364837194.txt 这样的路径。 2.5.4 复制、移动和删除文件

Files.copy(fromPath, toPath);:将文件从一个位置复制到另一个位置。 Files.move(fromPath, toPath);:移动文件(即复制并删除原文件)。

// 如果想要覆盖已有的目标路径,可以使用 REPLACE_EXISTING 选项。如果想要复制所有的文件属性性,可以使用 COPY_ATTRIBUTES 选项也可以像下面这样同时选择这两个选项: Files.copy(fromPath, toPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); // 你可以将移动操作定义为原子性的,这样就可以保证要么移动操作成功完成,要么源文件继续保持在原来位置。具体可以使用 ATOMIC_MOVE 选项来实现: Files.move(fromPath, toPath, StandardCopyOption.ATOMIC_MOVE);

Files.copy(inputStream, toPath);:将一个输人流复制到 Path 中,这表示你想要将该输入流存储列硬盘。 Files.copy(fromPath, outputStrearn);:将一个 Path 复制到输出流中。 Files.delete(path);:删除文件。文件不存在会抛出异常。 Fi1es.deleteIfExists(path);:删除文件。返回一个boolean值,删除成功返回 true,否则 false。

2.5.5 获取文件信息

下面的静态方法都将返同 一个 boolean 值,表示检查路径的某个属性的结果 :

existsisHiddenisReadable, isWritable, isExecutableisRegularFile , isDirectory, isSymboliclink

要获取如:创建文件时间等属性,可以调用以下代码获取:

BasicfileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);

如果用户的文件系统兼容 POSIX,也可以将 BasicFileAttributes改为PosixFileAttributes获取POSIX文件实例。

2.5.6 访问目录中的项

Files.list:返回一个可以读取目录中各个项的 Stream 对象。目录是被惰性读取的,这使得处理具有大址项的目录可以变得更高效。

File.walk:list 方法不会进入子目录。只要遍历的项是目录,那么在进入它之前,会继续访问它的兄弟项。

File.find:如果要过滤 walk返回的路径,并且你的过滤标准涉及与目录存储相关的文件属性,例如尺寸、创建时间和类型(文件、目录、符号链接),那么应该使用 find 方法来替代 walk 方法 。

2.5.7 使用目录流

DirectoryStream接口对象,可对目录遍历过程进行更加细粒度的控制。(它不是 java.util.stream.Stream 的子接口,而是专门用于目录遍历的接口。它是 Iterable 的子接口,可用forEach遍历)

Files.newDirectoryStream:会产生一个DirectoryStream接口对象,可用try语句块确保目录流可以被正确关闭(书中API错了,File 没有这个方法)。访问目录中的项并没有具体的顺序。可以用 glob 模式(类似通配符,如果是windows系统,反斜杠需要转义两次)来过滤文件:

try (DirectoryStream entries = Files.newDirectoryStream(dir, "*.java"))

Files.walkFileTree:方法可以访问某个目录的所有子孙成员。入参需要一个FileVisitor



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3