您的位置:首页 > 职场人生

黑马程序员【结合源码介绍IO体系框架】

2014-07-13 00:28 399 查看
------- android培训java培训、期待与您交流! ----------

一、IO框架的概括

初学Java的IO框架,感觉这个体系很庞大,总是理不清头绪。随着学习的不断深入,加之阅读一些IO源代码,对IO框架的整体有了新的认识。首先,理解两个概念输入流和输出流。所谓流,就是数据的有序排列,而流是可以是从某个源(Source
of Stream)出来,流向个目的地(Sink of Stream)。根据流的方向,可以分成输入流和输出流,一个程序从输入流读取数据向输出流写数据。根据数据类型输入流有可分为字节输入流和字符输入流,输出流分为字节输出流和字符输出流,如下图。



IO体系中四大基本类(抽象类):InputStream,OutputStream,Reader,Writer。InputStream和OutputStream处理8为字节的输入和输出,Reader和Writer处理16位字符(char型)的输入和输出。

二、IO框架的详细说明

1、字节流体系结构



InputStream和OutputStream是两个抽象类,从源码中可以看出,类中只是简单的定义了基本的读写方法和关闭流方法。最重要的两个读写方法read()和Writer方法分别是类中唯一的抽象方法,是其子类必须实现的方法。其中FileInputStream中的read()方法和FileOutputStream中的Writer()方法是native本地方法,真正从磁盘读取数据和往磁盘写数据的正是这两个native方法。无论是字符流还是字节流,无论中间经历的多么复杂的过程,数据源如果是磁盘,最终调用的是native
read(),数据目的地是磁盘最终调用native Write()方法。因为Java 是运行在JVM之上的,不能直接与底层硬件和OS交互,只能通过C/C++等语言编写的native方法实现交互。native方法就像是JVM和磁盘之间一个接口,要想对磁盘进行IO操作,程序必须链接到此接口。

ByteArrayStream(ByteArrayInputStream和ByteArrayOutputStream简写)的数据源和数据宿都是内存,其中就没有native方法,所以调用close()方法是没作用的。此类继承自InputStream和OutputStream,但只是简单重写父类的所有方法,用流的思想去操作数组。但是作为字节流的子类,是可以链接在其他字节流上的,比如BufferedInputStream。因为此类操作内存数据的,ByteArrayInputStream部分源码如下:
public class ByteArrayInputStream extends InputStream {

//数据源是数组
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
//利用数组指针  逐一读取数组数据
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
//没有任何语句 空函数
public void close() throws IOException {
}

}

可以把ByteArrayStream简单的理解成操作字节数组的工具类。
IO架构整体设计利用了decorator模式,以FilterInputStream为例,部分源码如下:
public class FilterInputStream extends InputStream {

//The input stream to be filtered.

protected volatile InputStream in;

protected FilterInputStream(InputStream in) {
this.in = in;
}
//	//调用父类的方法
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
public long skip(long n) throws IOException {
return in.skip(n);
}

FilterInputStream继承自InputStream,然后又引用的InputStream,这是典型的decorator模式。从源码中可以看出,此类简单重现父类所有方法,调用的都是父类方法。而真正负责装饰InputStream的是FilterInputStream的子类BufferedInputStream、DataInputStream、LineNumberInputStream等。装饰者类必须接受另外一个流对象(被装饰者)作为源。源经过装饰者后会具备一些额外的增强功能。
BufferedInputStream:为输入流提供一个内存缓冲区,解决了每次要用数据的时候都要进行物理读取的问题。
DataInputStream:和DataOutputStream配合使用,实现了直接对java基本数据类型IO操作。
LineNumberInputStream(已弃用):跟踪输入流的行号;有getLineNumber(
)和setLineNumber(int)方法。
看一下DataStream怎么进行装饰的:
//DataOutputStream
//把Boolean型转换成数字1、0保存到文件
public final void writeBoolean(boolean v) throws IOException {
out.write(v ? 1 : 0);
incCount(1);
}
<span style="white-space:pre">	</span>//DataInputStream
//读取时,再转换回来 1返回true,0返回false
public final boolean readBoolean() throws IOException {
int ch = in.read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
很简单,就是把Boolean的true/false和1/0相互转换。

最后还有一个略显特殊的类RandomAccessFile,该类是Object的直接子类。从结构图上看,该类不属于IO体系,可能是因为这个类能同时进行文件的读写吧,既属于Input也属于Output不好划分啊。结果自己就傲娇的独立出来了。我们先来看一下这个类和InputStream、OutputStream有什么区别,还是看源码,源码能直接说明问题:
/**
* Although RandomAccessFile is not a subclass of
* InputStream, this method behaves in exactly the same
* way as the {@link InputStream#read()} method of InputStream.
*/
public int read() throws IOException {
Object traceContext = IoTrace.fileReadBegin(path);
int b = 0;
try {
b = read0();
} finally {
IoTrace.fileReadEnd(traceContext, b == -1 ? 0 : 1);
}
return b;
}
//native方法 读
private native int read0() throws IOException;
//native方法 写
private native void write0(int b) throws IOException;
虽然RandomAccessFile不是InputStream的子类,但是read()方法和InputStream要完成的功能是一样的,而且read()和write都是native方法。该类和IO体系中的类的最大区别就是可以读也可以写,而且有一个非常强大的seek()方法,能对文件的任意位置进行读写,也就是所谓的随机访问文件。既然不属于IO体系当然也不能使用装饰类,所以只能自己实现,实现了DataInput和DataOutput接口,具备了对基本数据类型进行读写的功能。

2、字节流体系结构



理解了字节流框架,字符流也就非常简单了,无非就是把字节转换成了字符进行操作。先来看一下Reader的部分源码:

public abstract class Reader implements Readable, Closeable {
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
//最重要的方法,确实抽象方法
abstract public int read(char cbuf[], int off, int len) throws IOException;


Reader和Writer真的可以直接以字符为单位对磁盘进行读写吗?当然不可以,所有的文件在计算机中都是1001的二进制,哪有什么字符。构造InputStreamReader时要传入InputStream,说明读取文件的还是InputStream中的native read(),InputStreamReader的作用是把字节解码成字符,就是所谓的字节流通向字符流的桥梁。

import sun.nio.cs.StreamDecoder;

/**
* An InputStreamReader is a bridge from byte streams to character streams: It
* reads bytes and decodes them into characters using a specified charset.
*/

public class InputStreamReader extends Reader {
//负责把字节转换成字符
private final StreamDecoder sd;
//InputStream读取数据
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
// The default encoding should always be available
throw new Error(e);
}
}

public int read() throws IOException {
return sd.read();
}

public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}

public boolean ready() throws IOException {
return sd.ready();
}

public void close() throws IOException {
sd.close();
}
}
这是InputStreamReader的全部代码,很简单,都是在调用StreamDecode类中方法,StreamDecode是负责把读取的字节数据解码成字符数据的关键类。这个类好像是java官方的内部类,源码包中没有这个类,不过在这里可以查看。总之一句话,Reader、Writer就是把InputStream、OutputStream又装饰了一下,可以让程序方便的直接对字符读写。

字符流整体框架结构和字节流框架差不多,有一点值得注意:为什么BufferedReader不是FilterReader的子类,怎么直接继承了Reader?很简单,Reader这个类本身就是装饰类(这句话对么??)。

我用了一个星期才把IO框架理解到了这个程度,智商捉急,如有不对的地方,还望有大神能指正。

最后说明一下,这个框架图我是参考的别人的,稍作完善,尊重版权。参考博客

 ------- android培训java培训、期待与您交流!

----------详情请查看:http://edu.csdn.net/heima
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: