Java I/O系统学习笔记
对于程序语言的设计者来说,创建一个好的输入、输出(I/O)系统是一个项艰难的任务。
在本章学习笔记中,我将会记录关于File、I/O流的相关学习笔记。
File类
File类这个名字有一定的误导性;我们会认为它代指一个文件,实际上不是这样。FilePath对这个类来说是更好的名字,因为它既能代表一个文件,也可以代表一个文件集(文件夹),我们可以通过调用list()方法来获取文件集中的文件(集)。
目录列表器
FilenameFilter相当于一个过滤器,创建扩展于该接口的类的目的在于把accept()方法提供给list()使用,使list()可以回调accept()方法,进而决定哪些文件将包含在这个数组中。演示如下:
/**
* Created by Mr.W on 2017/11/8.
*/
public class FileClass {
public static void main(String[] args) {
// "."表示项目根目录
File file = new File("./src/com/stupidzhe/jdklearning/io/file-test");
System.out.println("FileName: " + file.getName());
System.out.println("isDirectory: " + file.isDirectory());
// 通过lambda表达式来实例化FilenameFilter的匿名类
String[] fileNameArray = file.list((dir, name) -> Pattern.matches("^[file].*", name));
if (null == fileNameArray) {
return;
}
for (String fileName : fileNameArray) {
System.out.println("fileName: " + fileName + " " + fileName.hashCode());
}
// 这里通过hashCode大小来排序
Arrays.sort(fileNameArray, ((o1, o2) -> (o1.hashCode() > o2.hashCode()?-1:1)));
for (String fileName : fileNameArray) {
System.out.println("fileName: " + fileName + " " + fileName.hashCode());
}
}
}
--------------output--------------
FileName: file-test
isDirectory: true
fileName: file1.c -855046422
fileName: file2.c -855045461
fileName: file3.c -855044500
fileName: file4.c -855043539
----------after sort--------
fileName: file4.c -855043539
fileName: file3.c -855044500
fileName: file2.c -855045461
fileName: file1.c -855046422
-----------------------------------
这种结构常常被称为“回调”。更具体地说,这是一个策略模式的例子,因为list()实现了基本的功能,而且按照FilenameFilter的形式提供了这个策略,以完善list()在提供服务时锁需的算法。
策略的目的就是提供了代码行为的灵活性。
目录的检查和创建
因为File类不仅仅只代表存在的文件或目录。也可以用File对象来创建新的目录或尚不存在的这个目录路径。我们还可以查看文件的特性(如:大小,最后修改日期,读/写),检查某个File对象代表的是一个文件还是一个目录,并可以删除文件。下面是例子:
public class FileClass {
public static void main(String[] args) {
File file = new File("./src/com/stupidzhe/jdklearning/io/file-test/t");
if (!file.exists()) {
System.out.println("this is not a file");
return;
}
System.out.println("is Directory: " + file.isDirectory());
System.out.println("is File: " + file.isFile());
System.out.println("its parent: " + file.getParent());
System.out.println("can write: " + file.canWrite());
System.out.println("can read: " + file.canRead());
System.out.println("absolute path: " + file.getAbsolutePath());
// 返回最近一次修改的时间戳
System.out.println("last modify: " + file.lastModified());
if (!file.delete()) {
System.out.println("delete file fail");
return;
}
file = new File("./src/com/stupidzhe/jdklearning/io/file-test/t");
if (file.exists()) {
return;
}
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("create dir: " + file.mkdir());
// 可以对文件(集)的位置和名称进行修改
//file.renameTo(new File("./src/com/stupidzhe/jdklearning/io/file-test/k"));
}
}
--------------output-----------
is Directory: true
is File: false
its parent: ./src/com/stupidzhe/jdklearning/io/file-test
can write: true
can read: true
absolute path: /Users/x72/Downloads/jdk-test/./src/com/stupidzhe/jdklearning/io/file-test/t
last modify: 1510140825000
create dir: true
-------------------------------
输入和输出
编程语言的I/O类库中常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或是有能力接收数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。
Java类库中的I/O类分成输入和输出两部分,可以在JDK文档里的类层次中查看到。通过继承,任何自InputStream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组。同样,任何自OutputStream或Write派生而来的类都含有名为write()的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。因此,我们很少使用单一的类来创建流对象。
InputStream类型
InputStream的作用是用来表示那些从不同数据源产生输入的类,这些数据包括:
- 字节数组
- String对象
- 文件
- “管道”,工作方式和实际管道相似,即从一端输入,从另一端输出
- 一个由其他种类的流组成的序列,以便我们可以将它们收集合并到一个流中
- 其他数据源,如Internet链接等
每一种数据源都有相应的InputStream子类,另外,FilterInputStream也属于一种InputStream,为装饰器类提供基类,其中,装饰类可以把属性或有用的接口与输入流连接起来。
下面的表格是InputStream类型的实现:
<table> <thead> <tr> <th>类</th> <th align="left">功能</th> <th align="left">构造器参数</th> <th align="left">如何使用</th> </tr> </thead> <tbody><tr> <td>ByteArrayInputStream</td> <td align="left">允许将内存的缓冲区当作InputStream使用</td> <td align="left">缓冲区,字节将从中取出</td> <td align="left">作为一种数据源:将其与FilterInputStream对象相连以提供有用接口</td> </tr> <tr> <td>StringBufferInputStream</td> <td align="left">将String转换成InputStream</td> <td align="left">字符串。底层实现实际使用StringBuffer</td> <td align="left">作为一种数据源:将其与FilterInputStream对象相连以提供有用接口</td> </tr> <tr> <td>FileInputStream</td> <td align="left">用于从文件读取信息</td> <td align="left">字符串,表示文件名、文件或FileDescriptor对象</td> <td align="left"></td> </tr> <tr> <td>PipedInputStream</td> <td align="left">产生用于写入相关PipedOutputStream的数据。实现“管道化”概念</td> <td align="left">PipedOutputStream</td> <td align="left">作为多线程中的数据源:将其与FilterInputStream对象相连以提供有用接口</td> </tr> <tr> <td>SequenceInputStream</td> <td align="left">将两个或多个InputStream对象转换成单一InputStream</td> <td align="left">两个InputStream对象或一个容纳InputStream对象的容器Enumeration</td> <td align="left">作为一种数据源:将其与FilterInputStream对象相连以提供有用接口</td> </tr> <tr> <td>FilterInputStream</td> <td align="left">抽象类,作为“装饰器”的接口。其中,“装饰器”为其他的InputStream类提供有用功能。</td> <td align="left"></td> <td align="left"></td> </tr> </tbody></table>
OutputStream
该类别的类决定了输出所要去往的目标:字节数组、文件、管道。
另外,FilterOutputStream为“装饰器”类提供了一个基类,“装饰器”类把属性或者有用的接口和输出流连接了起来。
下面是继承OutputStream的类型:
<table> <thead> <tr> <th>类</th> <th align="left">功能</th> <th align="left">构造器参数</th> <th align="left">如何使用</th> </tr> </thead> <tbody><tr> <td>ByteArrayOutputStream</td> <td align="left">在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲区</td> <td align="left">缓冲区初始化尺寸(可选的)</td> <td align="left">用于指定数据的目的地:将其与FilterOutputStream对象相连以提供有用接口</td> </tr> <tr> <td>FileOutputStream</td> <td align="left">用于将信息写至文件</td> <td align="left">字符串,表示文件名、文件或者FileDescriptor对象</td> <td align="left">指定数据的目的地:将其与FilterOutputStream对象相连以提供有用接口</td> </tr> <tr> <td>PipedOutputStream</td> <td align="left">任何写入其中的信息都会自动作为相关PipedInputStream的输出。实现“管道化”概念</td> <td align="left">PipedInputStream</td> <td align="left">指定用于多线程的数据的目的地:将其与FilterOutputStream对象相连以提供有用接口</td> </tr> <tr> <td>FilterOutputStream</td> <td align="left">抽象类,作为“装饰器”的接口。其中,“装饰器”为其他的InputStream类提供有用功能。</td> <td align="left"></td> <td align="left"></td> </tr> </tbody> </table>
关于FilterInputStream、FilterOutputStream
FilterInputStream类能够完成两件完全不同的事情,其中,DataInputStream允许我们读取不同的基本类型数据以及String对象(所有方法都以“read”开头,例如readByte()、readFloat()等等)。搭配相应的FilterOutputStream,我们可以通过数据“流”将基本类型的数据从一个地方迁移到另一个地方。具体是那些“地方”是由InputStream类型的实现的表格决定。
其他FilterInputStream类则在内部修改InputStream的行为方式:是否缓冲,是否保留它所读过的行(允许我们查询行数或设置行数),以及是否把单一字符推回输入流等等。
下面是FilterInputStream的实现类:
<table> <thead> <tr> <th>类</th> <th align="left">功能</th> <th align="left">构造器参数</th> <th align="left">如何使用</th> </tr> </thead> <tbody><tr> <td>DataInputStream</td> <td align="left">与DataOutputStream搭配使用,因此我们可以按照可移植方式从流读取基本数据类型(int,char,long等)</td> <td align="left">InputStream</td> <td align="left">包含用于读取基本类型数据的全部接口</td> </tr> <tr> <td>BufferedInputStream</td> <td align="left">使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区”</td> <td align="left">InputStream,可以指定缓冲区大小(可选的)</td> <td align="left">本质上不提供接口,只不过是向进程中添加缓冲区所必须的。与接口对象搭配</td> </tr> <tr> <td>LineNumberInputStream</td> <td align="left">跟踪输入流中的行号;可调用getLineNumber()和setLineNumber(int)</td> <td align="left">InputStream</td> <td align="left">仅增加了行号,因此可能要与接口对象搭配使用</td> </tr> <tr> <td>PushbackInputStream</td> <td align="left">具有“能弹出一个字节的缓冲区”。因此可以将读到的最后一个字符回退</td> <td align="left">InputStream</td> <td align="left">通常作为编译器的扫描器,之所以包含在内是因为Java编译器的需要,我们基本上用不到</td> </tr> </tbody></table>
与DataInputStream对应的是DataOutputStream,它可以将各种基本数据类型以及String对象格式化输出到流中;这样一来,任何机器的任何DataInputStream都能读取他们。所以方法都以“write”开头。
PrintStream最初的目的便是为了以可视化格式打印所有的基本数据类型以及String对象。这和DataOutputStream不同,后者的目的是将数据元素置入“流”中,使DataInputStream能够可移植地重构他们。
PrintStream内有两个重要的方法:print()、println()。对他们进行了重载,以便可以打印出各种数据类型。
DataInputStream的实现类:
<table> <thead> <tr> <th>类</th> <th align="left">功能</th> <th align="left">构造器参数</th> <th align="left">如何使用</th> </tr> </thead> <tbody><tr> <td>DataOutputStream</td> <td align="left">与DatInputStream搭配使用,因此我们可以按照可移植方式从流读取基本数据类型(int,char,long等)</td> <td align="left">OutputStream</td> <td align="left">包含用于写入基本类型数据的全部接口</td> </tr> <tr> <td>PrintStream</td> <td align="left">用于产生格式化输出。其中OutputStream处理数据的存储,PrintStream处理显示</td> <td align="left">OutputStream,可以用boolean值指示是否每次换行时情况缓冲区(可选的)应该是对OutputStream对象的“final”封装。可能会经常使用到它</td> <td align="left"></td> </tr> <tr> <td>BufferedOutputStream</td> <td align="left">使用它以避免每次发送数据时都要进行实际的写操作。代表“使用缓冲区”。可以调用flush()清空缓冲区</td> <td align="left">OutputStream,可以指定缓冲区大小(可选的)</td> <td align="left">本质上并不提供接口,只不过向进程中添加缓冲区所必需的。与接口对象搭配</td> </tr> </tbody></table>
Reader和Writer
当我们初次看到Reader和Writer时,可能会以为这是两个用来代替InputStream和OutputStream的类;但实际上并非如此。
Reader和Writer则提供兼容Unicode与面向字符的I/O功能, 另外:
有时我们必须把来自“字节”层次结构中的类和“字符”层次结构中的类结合起来使用。为了这个目的,要用到适配器类:InputStreamReader可以把InputStream转化为Reader,OutputStreamReader可以把OutputStream转化为Writer。