6.3 KiB
Data stored in a text file is represented in human-readable form. Data stored in a binary file is represented in binary form. You cannot read binary files. They are designed to be read by programs. For example, Java source programs are stored in text files and can be read by a text editor, but Java classes are stored in binary files and are read by the JVM. The advantage of binary files is that they are more efficient to process than text files.
Java的I/O抽象
网络通讯对象的抽象
我们考虑一个问题,如果要实现两个程序之间的通讯应该如何使用面向对象的思维来设计?首先我们需要把通讯进行抽象化,如果使用一个对象来进行通讯,应该如何操作?
在上图中,Side_A
和Side_B
分别代表通讯系统的两个端点,如果这两个端点要实现通讯,最简单的方式需要实现两个函数:
public byte[] read(){...}; // 读取
public void[] write(byte buf[]){...} // 写入
这里无论是读取还是写入都使用到一个最基本的结构-字节数组;因为在计算机中,字节是处理数据的基本单位。当然,我们没有考虑到实际通讯当中的复杂性,例如:连接、断开、错误处理等等。但是这两个基本的函数可以是我们通讯的基础。
那么,要实现一个可以通讯的类就需要对这两个函数进行实现(完成函数体中的算法)。但是问题又来了,通讯可以是基于以太网的,也可以是基于无线的,或者是光通讯等多种通讯媒介。那么我们势必为每个通讯类都实现这两个函数。这样就有了下面的这个UML:
classDiagram
class ComEthernet{
+read()
+write()
}
class ComWifi{
+read()
+write()
}
class ComFiber{
+read()
+write()
}
通过前面的知识,我们可以看出,这三个类都有同样的函数签名,应该进行抽象,因此我们增加了一个抽象父类-Com:
classDiagram
Com <|-- ComEthernet
Com <|-- ComWifi
Com <|-- ComFiber
class Com{
<<Abstract>>
+read()*
+write()*
}
class ComEthernet{
+read()
+write()
}
class ComWifi{
+read()
+write()
}
class ComFiber{
+read()
+write()
}
从上个例子中我们看到了如何从面向对象的思维来解决实际的问题。在即将学到的网络课程当中会发现网络的构建真的也有面向对象的思想。
文件的抽象
好了,现在我们来看看主题:文件系统。文件中存放的最小单元也是字节(byte),因此可以沿用上面网络的例子。只不过,这是一个真实的
环境了,比上述的网络的例子要包含更多的内容,但是基本的思路是一致的。
在文件系统中有个流(Stream)的概念,初学者可能对这个概念理解起来比较困难。上述两个抽象类(InputStream,OutputStream)其实主要的目的就是定义读和写的抽象方法:InputStream(读);OutputStream(写)。后面的派生类都需要实现这两个方法。只不过加入了更多的内容。
分离读/写主要目的是因为有些类只需要读取或者写入功能。
注意:InputStream(读);OutputStream(写)这两个类不是只针对文件系统,凡是可以进行字节读写的类都可以由这两个类进行扩展(例如网络中的通讯)。从这里可以看到,Java不仅仅是面向对象的语言,由于Java提供了一套基本的API(类),使得java也是一套编码规范或者是框架。多数情况下,需要满足Java中基本类型和接口的继承,才能使程序更具有通用性。
InputStream
注意:read()
函数后面的返回标注的是int,其实返回的值是0~255。因为java没有unsigned类型,byte类型的取值范围达不到0~255,所以使用int作为返回。
可以看到有多个关于读的函数,具体含义看书上的解释。
提高:为什么只是read()函数是抽象函数,其几个重载的read函数并不是抽象函数?可以通过分析源码来理解。
OutputStream
同样,write函数的参数类型虽然是int,实际是写入一个字节。
FileInputStream/FileOutputStream
这两个类型实现了对文件的读和写操作。
import java.io.*;
public class TestFileStream {
public static void main(String[] args) throws IOException {
try (
// Create an output stream to the file
FileOutputStream output = new FileOutputStream("/home/danny/temp.txt");) {
// Output values to the file
for (int i = 1; i <= 10; i++)
output.write(i);
}
try (
// Create an input stream for the file
FileInputStream input = new FileInputStream("/home/danny/temp.txt");) {
// Read values from the file
int value;
while ((value = input.read()) != -1)
System.out.print(value + " ");
}
}
}
还记得try(...){...}
的语法形式吗?小括号中只能是构造函数的语句,大括号中可以是任意语句;小括号中打开的文件不用关闭close(),该语法可以保证打开的文件自动关闭。
注意,这两个类实现都是字节的操作,因此文件是二进制文件。虽然后缀名是txt,如果尝试打开会得到乱码。
A java.io.FileNotFoundException would occur if you attempt to create a FileInputStream with a nonexistent file.
当使用 FileInputStream 的构造函数打开一个不存在的文件的时候,将会抛出
java.io.FileNotFoundException
异常。
构造函数也是函数,因此也可以抛出异常。如果阅读源码,会发现FileOutputStream
的构造函数也可以抛出异常。
注意:这两个类的构造函数是重载的,有一个版本可以接受一个File对象作为需要打开的文件。其实在Java中,大多预定义类都可以接受字符串或者是File对象,具体情况需要查看源码实现。