Thinking in java读书笔记 第18章 I/O系统

这篇日志发布时间已经超过一个月,许多内容可能已经失效,请读者酌情参考。

读书笔记,这章没记录完,有点长哈哈

对程序语言的设计者来说,创建”一个好的输入/输出(I/O)系统是一项艰难的任务。

18.1 File类

主要介绍了File作为文件和文件夹(路径)的一些方法。File中的list方法,返回的是string,而listFiles返回的则是文件的集合。

18.1.3 文件的检查及创建

(图丢了。。)

测试File的mkdir和mkdirs方法。

当File()为绝对路径的时候,貌似没有任何区别,

当File()为相对路径的时候,mkdir不能创建二级目录(一级目录是可以的)。所以就是说,mkdir不能在不存在的目录中创建文件夹。

18.2 输入和输出

编程语言的I/O库中经常使用“流”这个概念。“流”屏蔽了实际的I/O设备中处理数据的细节。

任何从Inputstream或者Reader派生的类,都有read()方法,用于读单个字节或者字节数组。

任何从Outputstream或者Writer派生的类,都有write()方法,用于写单个字节或者字节数组。

但是我们通常不会使用这些方法,他们之所以存在是因为别的类可以使用它们,以便提供更有效的接口。

因此,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(装饰器设计模式).

In fact,java中的“流”类库让人迷惑的主要原因在于:创建一个单一的结果流,却需要创建多个对象。

18.2.1 InputStream类型

InputStream的作用是用来表示哪些从不同数据源产生的输入的类。这些数据源包括:

    1. 字节数组
  1. String对象
  2. 文件
  3. “管道”,工作方式与真实管道类似,从一端输入,从另一端输出。
  4. 一个由其他种类的流组成的序列,以便我们可以将它们收集合并到一个流内。
  5. 其他数据源,如Internet连接等。
  6. (图丢了。。)

18.2.2 OutputStream 类型

FilterOutputStream为“装饰器”类提供了一个基类,装饰器类把属性或者有用的接口与输出流连接起来,这些稍后会做讨论。P535

18.3 添加属性和有用的接口

(装饰器在15章引入….)

18.3.1 通过FilterInputStream从InputStream读取数据

18.3.2通过FilterOutputStream向OutputStream写入

BufferedOutputStream是一个修改过的OutputStream,它对数据流使用缓冲技术,因此每次向流写入时,不必每次都进行实际的物理写入操动作。所以在进行输出时,我们可能更经常的是使用它。

18.4 Reader和Writer

…有时候我们必须把来自“字节”层次结构中的类和“字符”层次中的类结合起来使用。为了实现这个目的,要用到“适配器”adapter类:InputStreamReader可以把InputStream转换为Reader,而OutputStreamReader可以把OutputStream转换成Writer。

18.4.1 数据的来源和去处

因此,最明智的做法是尽量尝试使用Reader和Writer,一旦程序代码无法成功编译,我们就会发现自己不得不使用面向字节的类库。

(P538 表格,来源与去处)

18.4.2 更改流的行为

有一点很清楚:无论我们何时使用readLine(),都不应该使用DataInputStream(这样会遭到编译器的强烈反对),而应该使用BufferedReader,除了这一点,DataInputStream仍是I/O类库的首选成员。

18.5 自我独立的类:RandomAccessFile

说其独立,是因为他不是InputSteam或者OutputStream继承层次结构中的一部分。除了实现了DataInput和DataOutput接口之外,它和这两个继承层次结构完全没有关系。

只有RandomAccessFile支持搜寻方法,并且只是用于文件。

BufferedInputStream却能允许标注(mark())位置(其值存储于内部某个简单变量内)和重新设定位置(reset()),但这些功能有限,不是非常有用。

在JDK1.4中RandomAccessFile的大多数功能(但不是全部)由nio存储映射文件所取代。

18.6 I/O流的典型使用方式

…异常处理都被简化为将异常传递给控制台,但是这只有在小型实例和工具中才适用….

18.6.1 缓冲输入文件

如果想要打开一个文件永远字符输入,可以使用以String或者File对象作为文件名的FileInputStream。

为了提高速度,我们希望对那个文件进行缓冲,那么我们将所产生的一用传给一个BufferedReader构造器。

由于BufferedReader也提供readLine()方法, 所以这好似我们最终对象和进行读取的接口。当readLine()返回null时,表明达到文件末尾。

String 对象是不可改变的。每次使用 System.String 类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。在需要对字符串执行重复修改的情况下,与创建新的 String 对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用 System.Text.StringBuilder 类。例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder 类可以提升性能。

方法名 使用

StringBuilder.Append 将信息追加到当前 StringBuilder 的结尾。

StringBuilder.AppendFormat 用带格式文本替换字符串中传递的格式说明符。

StringBuilder.Insert 将字符串或对象插入到当前 StringBuilder 对象的指定索引处。

StringBuilder.Remove 从当前 StringBuilder 对象中移除指定数量的字符。

StringBuilder.Replace 替换指定索引处的指定字符。

18.6.3 格式化的内存输入

要读取格式化数据,可以使用DataInputStream,它是一个面向字节的I/O类(不是面向字符的)。因此我们必须使用InputStream类而不是Reader类。

如果我们从DataInputStream用readByte()一次一个字节的读取字符,那么任何字节都是合法的结果,因此返回值不能用来检测输入是否结束。相反,我们可以使用available()方法查看还有多少可供存取的字符。

注意,available()的工作方式会随着所读取的媒介类型的不同而有所不同;字面意思就是“在没有阻塞的情况下所能读取的字节数”。对于文件,这意味着整个文件;但是对于不同类型的流,可能就不是这样的,因此要谨慎使用。

我们也可以通过捕获异常来检测输入的末尾。但是,使用异常进行流控制,被认为是对异常特性的错误使用。

18.6.4 基本的文件输出

FileWriter对象可以向文件写入数据。首先,创建一个与指定文件连接的FileWriter。实际上,我们通常会用BufferedWriter将其封装起来用以缓冲输出(尝试移除此包来感受对性能的影响——缓冲往往能显著地增加I/O操作的性能)。

一旦读完输入数据流,readLine()就会返回 null,我们可以看到要为out显式调用close()。如果我们不为所有的输出文件调用close()/////flush(),就会发现缓冲区内容不会被刷新清空,那么它们也就不完整。

18.6.5 存储和恢复数据

如果我们使用DataOutputStream写入数据,Java保证我们可以使用DataOutputStream准确地读写数据——无论读和写数据的平台多么不同。[XML是另一种方案]

当我们使用DataOutputStream时,写字符串并且让DataIntputStream能够恢复它的唯一可靠做法就是使用UTF-8编码,在这个示例中是用writeUTF()和readUTF()来实现的。UTF-8是一种多字节格式,其编码长度根据实际使用的字符集会有所变化。如果我们使用的只是ASCII或者几乎都是ASCII字符(只占7位),那么就显得及其浪费空间和带宽,所以UTF-8将ASCII字符编码成单一字节的形式,而非ASCII字符则编码成2-3个字节的形式。另外,字符串的长度存储在UTF-8字符串的前两个字节中。但是writeUTF()和readUTF()使用的是适合于java的UTF-8遍体(JDK中有这些方法的详尽描述),因此如果我们用一个非java程序读取用writeUTF()缩写的字符串时,必须编写一些特殊代码才能正确读取字符串。

有了writeUTF()和readUTF(),我们就可以用DataOutputStream把字符串和其他数据类型混合,我们知道字符串完全可以作为unicode来存储,并且可以很容易地使用DataInputStream来恢复它。

18.6.6 读写随机访问文件

使用RandomAccessFile,类似于组合使用了DataInputStream和DataOutputStream(因为它实现了相同的接口:DataInput和DataOutput)。另外我们可以看到,seek()方法可以在文件中到处移动,并修改文件中的某个值。

在使用RandomAccessFile时,你必须知道文件排版,这样才能正确地操作它。 RandomAccessFile拥有读取基本类型和UTF-8字符串的各种方法。

18.6.7 管道流

PipedInputStream、PipedOutputStream、PipedReader和PipedWriter在本章只是简单地提到。但这并不表明他们没有什么用处,它们的价值只有在我们开始理解多线程之后才会显现,因为管道流用于任务之间的通信。这些在第21章会用一个示例进行讲述。

18.7 文件读写的实用工具

一个很常见的程序化任务就是读取文件到内存,修改,然后再写出。java I/O类库的问题之一就是:它需要编写相当多的代码去执行这些常用操作——没有任何基本的帮助功能可以为我们做这一切。更糟糕的是,装饰器会使得要记住如何打开文件变成一件相当困难的事。

这节主要是写了两个小工具,TextFile和BinaryFile,用于封装成常见的I/O操作小工具的示例。

18.8 标准I/O

标准I/O这个术语参考的是Unix中“程序所使用的单一信息流”这个概念(在windows和其他许多操作系统中,也有相似形式的实现)。

程序的所有输入都可以来自于标准输入,它的所有输出也都可以送达标准输出,以及所有的错误信息都可以发送到标准错误。

标准I/O的意义在于:我们可以很容易地把程序串联起来,一个程序的标准输出可以成为另一个程序的标准输入。这真是一个强大的工具。

18.8.1 从标准输入中读取

按照标准I/O模型,Java提供了System.in、System.out和System.err。

其中System.out和System.err是已经被包装过的PrintStream,但是System.in却是一个没有被包装过的InputStream,这意味着我们可以立即使用System.out和System.err,但是在读取System.in之前必须对其进行包装。

18.8.2 将System.out转换成PrintWriter

System.out是一个PrintStream。而PrintStream是一个OutputStream。

PrintWriter有一个可以接受OutputStream作为参数的构造器。因此,只要需要,就可以使用那个构造器把System.out转换成PrintWriter。

代码如下

package com.prettydz.exercises;

import java.io.PrintWriter;

public class ChangeSystemOut {

public static void main(String[] args) {

PrintWriter out = new PrintWriter(System.out, true);

out.println("测试");

}

}

PrintWriter的第二个参数true表示自动清空,以便显示出来。

18.8.3 标准I/O重定向

Java的System类提供了一些简单的静态方法调用,以允许我们对标准输入、输出和错误I/O流进行重定向:

setIn(InputStream)

setOut(PrintStream)

setErr(PrintStream)

如果我们突然开始在显示器上创建大量输出,而这些输出滚动得太快以至于无法阅读时,重定向输出就显得极为有用(第22章展示了一种更方便的解决方案:一个GUI程序,具有带滚动的文本区域)。

package com.prettydz.exercises;

 

import java.io.*;

 

public class Redirecting {

public static void main(String[] args) throws IOException {

PrintStream console = System.out;

BufferedInputStream in = new BufferedInputStream(new FileInputStream("D:\\我的桌面\\in.txt"));

PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("D:\\我的桌面\\out.txt")));

System.setIn(in);

System.setOut(out);

System.setOut(out);

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

String zs;

while((zs = br.readLine())!=null){

System.out.println(zs);

}

out.close();

System.setOut(console);

System.out.println("测试");

}

}

这个示例将System.in重定向为in.txt,即从指定文件输入。当读入的是ANSI文件时候,会产生乱码,处理方法就是用Charset类decode一下。

程序开头存储了System.out以便在程序末尾恢复重定向,最后的输出用于console测试。

I/O重定向操作的是字节流,而不是字符流;因此我们使用的是InputStream和OutputStream而不是Reader和Writer。(这个可以用于写程序日志)

18.9 进程控制

你经常会需要在Java内部执行其他操作系统的程序,并且要控制这些程序的输入和输出。Java类库提供了执行这些操作的类。

一个常见的任务就是运行程序,并将产生的结果输出到控制台。

要想运行一个程序,你需要像OSExecute.command()传递一个command字符串,它与你在控制台上运行该程序所需要键入的命令相同。这个命令被传递给java.lang.ProcessBuilder构造器,然后所产生的ProcessBuilder对象被启动。

package com.prettydz.exercises;

 

import java.io.BufferedReader;

import java.io.InputStreamReader;

 

public class OSExecute {

public static void command(String command) {

boolean err = false;

try {

Process process = new ProcessBuilder(command.split(" ")).start();

BufferedReader results = new BufferedReader(new InputStreamReader(process.getInputStream()));

String zs;

while ((zs = results.readLine()) != null) {

System.out.println(zs);

}

BufferedReader errs = new BufferedReader(new InputStreamReader(process.getErrorStream()));

while ((zs = errs.readLine()) != null) {

System.err.println(zs);

err = true;

}

} catch (Exception e) {

// 针对windows的测试

if (!command.startsWith("CMD /C ") && !command.startsWith("cmd /c ")) {

command("CMD /C " + command);

} else

throw new RuntimeException(e);

}

if (err) {

System.err.println("Errors executing :" + command);

}

}

 

public static void main(String[] args) {

OSExecute.command("dir");

}

}

18.10 新I/O

JDK 1.4 的java.nio.*包中引入了新的I/O类库,其目的在于提高速度。实际上,旧的I/O包已经用nio重新实现过,以便充分利用这种速度提高,因此,即使我们不显式地用nio编写代码,也能从中受益。速度的提升在文件I/O和网络I/O中都有可能发生,我们在这里只研究前者,对于后者,在《Thinking in Enterprise Java》中有论述。(FUCK)

速度的提升来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。

唯一直接与通道交互的缓冲器是ByteBuffer——也就是说,可以存储未加工字节的缓冲器。

旧I/O类库中有三个类被修改了用以产生FileChannel。这三个被修改的类是FileInputStream FileOutputStream 以及既读又写的RandomAccessFile。注意这些是字节操作,与低层的nio性质一样。Reader和Writer这种字符模式类不能用于产生通道;但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer。

方法:getChannel()

方法摘要
`abstract void``force(boolean metaData)`强制将所有对此通道的文件更新写入包含该文件的存储设备中。
`FileLock``lock()`获取对此通道的文件的独占锁定。
`abstract FileLock``lock(long position, long size, boolean shared)`获取此通道的文件给定区域上的锁定。
`abstract MappedByteBuffer``map(FileChannel.MapMode mode, long position, long size)`将此通道的文件区域直接映射到内存中。
`abstract long``position()`返回此通道的文件位置。
`abstract FileChannel``position(long newPosition)`设置此通道的文件位置。
`abstract int``read(ByteBuffer dst)`将字节序列从此通道读入给定的缓冲区。
`long``read(ByteBuffer[] dsts)`将字节序列从此通道读入给定的缓冲区。
`abstract long``read(ByteBuffer[] dsts, int offset, int length)`将字节序列从此通道读入给定缓冲区的子序列中。
`abstract int``read(ByteBuffer dst, long position)`从给定的文件位置开始,从此通道读取字节序列,并写入给定的缓冲区。
`abstract long``size()`返回此通道的文件的当前大小。
`abstract long``transferFrom(ReadableByteChannel src, long position, long count)`将字节从给定的可读取字节通道传输到此通道的文件中。
`abstract long``transferTo(long position, long count, WritableByteChannel target)`将字节从此通道的文件传输到给定的可写入字节通道。
`abstract FileChannel``truncate(long size)`将此通道的文件截取为给定大小。
`FileLock``tryLock()`试图获取对此通道的文件的独占锁定。
`abstract FileLock``tryLock(long position, long size, boolean shared)`试图获取对此通道的文件给定区域的锁定。
`abstract int``write(ByteBuffer src)`将字节序列从给定的缓冲区写入此通道。
`long``write(ByteBuffer[] srcs)`将字节序列从给定的缓冲区写入此通道。
`abstract long``write(ByteBuffer[] srcs, int offset, int length)`将字节序列从给定缓冲区的子序列写入此通道。
`abstract int``write(ByteBuffer src, long position)`从给定的文件位置开始,将字节序列从给定缓冲区写入此通道。

对于这所展示的任何流类,getChannel()将会产生一个FileChannel。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问(稍后讲述)。

将字节放入ByterBuffer的方法之一是:使用一种put方法直接对它进行填充,填入一个或多个字节,或基本数据类型的值。不过正如所见,也可以使用wrap()方法将已经存在的字节数组包装到ByteBuffer中。一旦如此,就不再复制底层的数组,而是它作为所产生的ByteBuffer的存储器,我们称之为数组支持的ByteBuffer。

对于只读访问,我们必须显式地使用静态方法allocate()方法来分配ByteBuffer。nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要——实际上,这里使用的1K可能比我们通常用的要小一点(必须通过实际运行应用程序来找到最佳尺寸)。

一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。

……..P554

18.10.1 转换数据

缓冲器容纳的是普通的字节,为了把它们转换成字节,我们要么在输入它们的时候对其进行编码(这样它们输出市才有意义),要么在将其从缓冲区输出时对它们进行解码。可以使用java.nio.charset.Chaset类实现这些功能。

…….18章后边表示没看完….

留言交流

没有评论
点击换图
exclaimrazzsadevilexclaimsmileredfacebiggrinsurprisedeekconfusedcoollolmadtwistedrolleyeswinkideaarrowneutralcrymrgreen