~读书笔记,这章没记录完,有点长哈哈~
对程序语言的设计者来说,创建”一个好的输入/输出(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的作用是用来表示哪些从不同数据源产生的输入的类。这些数据源包括:
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章后边表示没看完….
留言交流