对于程序语言的设计者来说,创建一个好的I/O系统是一项很艰难的任务。挑战不仅存在于IO多样的输入/输出端,还在于多种不同的通信方式,如顺序存取、缓冲、二进制、按字符/按行/按字存取等。Java类库的设计者创建了大量的类来解决这个困难。
Java I/O设计初衷是避免使用过多的类,但讽刺的是,Java I/O系统提供的类库依旧很多。
Java 的 I/O 操作类在包 java.io 下,大概有将近 80 个类,但是这些类大概可以分成四组,分别是:
- 基于字节操作的 I/O 接口:InputStream 和 OutputStream
- 基于字符操作的 I/O 接口:Writer 和 Reader
- 基于磁盘操作的 I/O 接口:File @Deprecated
- 基于网络操作的 I/O 接口:Socket @Deprecated
典型的数据源及媒介有:
- 文件
- 控制台
- 网络
- 设备
前三个位于package java.io
下,最后一个位于package java.net
下。
File
File
类不负责具体文件内容的读写,但是负责文件及文件目录的操作,是基于磁盘操作的I/O。
File
类名字有一定的误导性。我们可能会认为它指代的是文件,实际上并非如此。它既能指代一个具体的文件,也能代表一个目录。
常用File
类操作:
//创建文件
public boolean createNewFile() throws IOException;
//列举目录列表
public String[] list();
//列举目录列表,FilenameFilter用来辅助过滤。如列举以`.java`结尾的文件,则可通过implements FilenameFilter来实现
public String[] list(FilenameFilter filter);
//获取文件/目录名
public String getName();
//获取文件父目录路径
public String getParent();
//删除文件/目录
public boolean delete()
更多其他操作可参考JDK文档。
基于字节和字符的I/O操作
流
是Java IO的核心。
流
分为输入流(Input)和输出流(Output)两部分。输入流从数据源读取数据,输出流将数据写入到特定的目标。
常见数据源
- 字节数组
- String对象
- 文件
- 管道
- 其他种类的输出流
- 网络
常见数据输出端
- 字节数组
- 文件
- 管道
- 其他种类的输入流
- 网络
所有的输入流都有read()
方法,所有的输出流都有write()
方法。
不管是磁盘还是网络传输,最小的存储单元都是字节,因此支持基于字节的I/O就显然而已了。但是我们的程序中通常操作的数据都是以字符形式,为了操作方便当然要提供一个直接写字符的 I/O 接口,因此又产生了基于字符的I/O。
基于字节的IO
计算机识别数据的基本单位就是字节。
基于字节的 I/O 操作接口输入和输出分别是:InputStream
和 OutputStream
。
InputStream
InputStream
是所有输入类的接口,部分方法如下:
public abstract class InputStream implements Closeable {
public int available() throws IOException;
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int len) throws IOException;
}
常见InputStream
实现类
ByteArrayInputStream
将内存当做数据源,构造函数需要传入byte数组。
public class ByteArrayInputStream extends InputStream {
public ByteArrayInputStream(byte buf[]);
}
FileInputStream
数据源是文件,构造函数如下:
public class FileInputStream extends InputStream{
public FileInputStream(File file) throws FileNotFoundException;
public FileInputStream(FileDescriptor fdObj)
public FileInputStream(String name) throws FileNotFoundException;
}
PipedInputStream
数据源是PipedOutputStream
,常用在多线程中
public class PipedInputStream extends InputStream {
public PipedInputStream(PipedOutputStream src) throws IOException;
}
FilterInputStream
这里使用了装饰者模式,做为装饰器的接口。可以从构造函数看出,只支持传入InputStream
类型的对象。
public class FilterInputStream extends InputStream {
protected FilterInputStream(InputStream in);
}
不太了解装饰者模式的可以看下Design Pattern - 装饰者模式
为什么需要装饰者模式呢?JDK的设计者主要是为了给不同的I/O提供更多的灵活性。
举个例子,传统的I/O是无缓冲的,也就是说每次读或者写哪怕一个字节,都需要调用底层操作系统接口,这是个效率很低的操作。为传统I/O设计一个缓冲区,是非常必要的。但是读取文件的FileInputStream
需要缓冲区,读取内存的ByteArrayInputStream
需要缓冲区,其他的InputStream
的实现类也需要缓冲区。怎么设计才能支持所有的InputStream
都能支持缓冲区呢?JDK的设计者就想到了使用装饰者模式。使用BufferedInputStream
来装饰任意InputStream
对象,该输入流就能支持缓冲区了!
如:
byte[] buff = new byte[1024];
File file = new File(filePath);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
in.read(buff); //支持了缓冲的read操作
OutputStream
OutputStream
是所有输出类的接口,部分方法如下:
public abstract class OutputStream implements Closeable, Flushable{
public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;
public void flush() throws IOException;
}
常见OutputStream
实现类
OutputStream
的实现类大部分是跟InputStream
一一对应的,这个从名字上就能看出来。二者的原理类似,因此这里就不再做过多介绍了。
基于字符的IO
基于字符的 I/O 操作接口输入和输出分别是:Reader
和 Writer
。
Reader
Reader
的继承层级:
StringReader
String做为输入源
public class StringReader extends Reader{
public StringReader(String s);
}
BufferedReader
从其他Reader
读取数据到缓冲区。类似BufferedInputStream
的功能
public class BufferedReader extends Reader{
public BufferedReader(Reader in, int sz);
}
FileReader
从文件中读取数据
public class FileReader extends InputStreamReader {
public FileReader(File file) throws FileNotFoundException;
}
PipedReader
从管道中读取数据。跟PipedWriter
联合使用。
例子:
public void read(){
String hello= new String( "hello word!");
File file= new File( "d:/test.txt");
//因为是用字符流来读媒介,所以对应的是Writer,又因为媒介对象是文件,所以用到子类是FileWriter
Writer os= new FileWriter( file);
os.write( hello);
os.close();
}
Writer
Writer
的实现类同Reader
对应,从名字上就能很容易找到联系。
例子:
public static void write() throws IOException{
File file= new File( "d:/test.txt");
//因为是用字符流来读媒介,所以对应的是Reader
//又因为媒介对象是文件,所以用到子类是FileReader
Reader reader= new FileReader( file);
char [] byteArray= new char[( int) file.length()];
int size= reader.read( byteArray);
System. out.println( "大小:"+size +";内容:" +new String(byteArray));
reader.close();
}
字节和字符的转换
数据持久化或网络传输都是以字节进行的,所以必须要有字符到字节或字节到字符的转化。字符到字节需要转化,其中读的转化过程如下图所示:
InputStreamReader
类是字节到字符的转化桥梁,InputStream
到 Reader
的过程要指定编码字符集,否则将采用操作系统默认字符集,很可能会出现乱码问题。StreamDecoder
正是完成字节到字符的解码的实现类。也就是当你用如下方式读取一个文件时:
try {
StringBuffer str = new StringBuffer();
char[] buf = new char[1024];
FileReader f = new FileReader("file");
while(f.read(buf)>0){
str.append(buf);
}
str.toString();
} catch (IOException e) {}
写入也是类似的过程如下图所示:
通过 OutputStreamWriter
类完成,字符到字节的编码过程,由 StreamEncoder
完成编码过程。
基于网络的IO
网络编程依赖于ServerSocket
和Socket
。同File
类似,只是表示网络的描述符。真正的数据传输依旧依赖于InputStream
和OutputStream
!!!
在互联网时代,网络编程在任何一个编程语言中都是一个极其重要的内容。因此,这部分的内容会在一个专门的文中来讲。
Java1.4引入了NIO
Java NIO