java.io流个人总结

来源:互联网 发布:杭州认知网络投资人 编辑:程序博客网 时间:2024/06/16 01:40

InputStream和OutputStream

先看下类的声明:

public abstract class InputStream implements Closeablepublic abstract class OutputStream implements Closeable, Flushable

可见此二者都是抽象类,而非接口。也就是说除了分别满足java.io.Closeable和java.io.Flushable,提供了close()和flush()方法的默认实现外,还给出了其它实现,像InputStream就提供了skip()方法实现等。

我们更关心的是InputStream中的几个read()方法和OutputStream的几个write()方法。而实际上最核心的read()和write()方法,InputStream和OutputStream并未给出直接实现,这正是InputStream和OutputStream抽象的地方,我们来看下。

InputStream的read()方法:

public abstract int read() throws IOException; 核心read()方法,留给子类实现。    public int read(byte b[])public int read(byte b[], int off, int len)

后两者是调用第一个方法读特定长度的数据放入byte数组中。

OutputStream的write()方法:

public abstract void write(int b) throws IOException; 核心write()方法,留给子类实现。public void write(byte b[])public void write(byte b[], int off, int len)

和read()类似,把字节数组中的特定数据挨个调用第一个write()方法写出。

再说read()和wirte()实现,那么就要看一下InputStream和OutputStream的子类。我们先来看看InputStream和OutputStream的直接子类。

在java.io中InputStream的直接子类有:

java.io.ByteArrayInputStreamjava.io.FileInputStreamjava.io.FilterInputStreamjava.io.ObjectInputStreamjava.io.PipedInputStreamjava.io.SequenceInputStreamjava.io.StringBufferInputStream

而java.io中OutputStream的直接子类有:

java.io.ByteArrayOutputStreamjava.io.FileOutputStreamjava.io.FilterOutputStreamjava.io.ObjectOutputStreamjava.io.PipedOutputStream

这些当中,FileInputStream和FileOutputStream是与外部IO直接有关系的,而FilterInputStream和FilterOutputStream是“装饰者”设计实现的基类,其它各类都是特定场景下InputStream和OutputStream的实现,我们来具体看看。

FileInputStream/FileOutputStream

顾名思义,是文件的输入流和输出流。文件存在于哪里呢?通常存在于外部设备上,这个是这些实现类中比较特殊的。我们如果做过C语言开发,知道我们通常会open操作系统中的文件,并保留其文件描述符fd。其实,在Java中我们也有类似的东西。首先,就有fd属性,它是FileDescriptor类,和C等底层语言中一样,这实际上是对底层文件的一个描述符,和底层文件进行交互的时候少不了它。实际上在FileInputStream/FileOutputStream对象构造方法中,我们初始化了FileDescriptor的fd对象,并调用了open()方法。

遗憾的是,像open()、read()、write()、skip()、available()这些FileInputStream/FileOutputStream中的具体操作方法,在(Sun)JDK中都冠以native,即本地实现,这样做的好处就是上层开发使用者不必关心,统统交由JVM等底层实现进行处理,实现了平台无关性。

在FileInputStream还有这样几个属性:

private final FileDescriptor fd;private FileChannel channel = null;   JDK1.4之后为了支持NIO的Channel操作private final Object closeLock = new Object();  关闭时的并发同步锁private volatile boolean closed = false; 关闭标志private static final ThreadLocal<Boolean> runningFinalize =new ThreadLocal<>();  finalize是否运行的标志

此外,再简要看下FileInputStream构造方法的一个具体实现:

public FileInputStream(File file) throws FileNotFoundException {    String name = (file != null ? file.getPath() : null);    SecurityManager security = System.getSecurityManager();    if (security != null) {        security.checkRead(name);    }    if (name == null) {        throw new NullPointerException();    }    fd = new FileDescriptor();    fd.incrementAndGetUseCount();    open(name);}

其它重载要么是调用了这个方法,要么实质上是一致的。FileOutputStream也是类似,出了open()调用的时候有一个append属性参数,标志是否为文件追加。

ByteArrayInputStream/ByteArrayOutputStream

名称也很直接,实际上就是字节数组,在构造类对象的时候给出一个byte数组。对于ByteArrayInputStream来说,这个byte数组就是读取的源头,而ByteArrayOutputStream则是write()的目的地。

此外,构造方法中还可以指定这个byte数组的有效起点和长度。

public ByteArrayInputStream(byte buf[], int offset, int length) {    this.buf = buf;    this.pos = offset;    this.count = Math.min(offset + length, buf.length);    this.mark = offset;}

而对于ByteArrayOutputStream,给出byte数组长度参数即可。甚至还有默认实现,长度32。

public ByteArrayOutputStream(int size) {    if (size < 0) {        throw new IllegalArgumentException("Negative initial size: "                                           + size);    }    buf = new byte[size];}

PipedInputStream/PipedOutputStream

从名字理解也没错,就是管道输入输出(字节)流。它们的存在需要彼此,即PipedInputStream对象和PipedOutputStream对象只有互相存在才真正有意义,当然可以先构建后连接(connect)。

总体来说就是PipedInputStream维护了一个PipedOutputStream对象的属性,而PipedOutputStream也维护了一个PipedInputStream对象属性。而PipedInputStream额外维护了一个缓冲区数组。当PipedOutputStream执行write()的时候,如果PipedInputStream对象未就绪,会发生一个异常。就绪情况下会调用PipedInputStream对象的receive()方法进行接收,也就是写入缓冲数组buf[]。而最终的PipedInputStream的读取就从这个buf[]缓冲数组中来。

SequenceInputStream

这个SequenceInputStream是InputStream的另一个实现,而且没有OutputStream与其对应(至少在java.io中没有)。这个类实际上所做的,也是一个对多个其它InputStream的“包装”,而这个所谓的“包装”的作用就是把这些依次(sequently)连接起来,当一个read()到头了(返回-1)就接着读下一个。

下面是read()方法实现:

public int read() throws IOException {    if (in == null) {        return -1;    }    int c = in.read();    if (c == -1) {        nextStream();        return read();    }    return c;}

FilterInputStream和FilterOutputStream

首先,这两个都分别是InputStream和OutputStream的子类。而且,FilterInputStream和FilterOutputStream是具体的子类,实现了InputStream和OutputStream这两个抽象类中为给出实现的方法。

但是,FilterInputStream和FilterOutputStream仅仅是“装饰者模式”封装的开始,它们在各个方法中的实现都是最基本的实现,都是基于构造方法中传入参数封装的InputStream和OutputStream的原始对象。

比如,在FilterInputStream类中,封装了这样一个属性:

protected volatile InputStream in;

而对应的构造方法是:

protected FilterInputStream(InputStream in) {    this.in = in;}

read()方法的实现则为:

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

其它方法的实现,以及FilterOutputStream也都是同理类似的。

我们注意到FilterInputStream和FilterOutputStream并没给出其它额外的功能实现,只是做了一层简单地封装。那么实现额外功能的实际是FilterInputStream和FilterOutputStream的各个子类。

BufferedInputStream/BufferedOutputStream

先说说这个最简单的一对,BufferedInputStream和BufferedOutputStream。顾名思义,Buffered就是缓冲了的。在BufferedInputStream和BufferedOutputStream中,都额外实现了byte数组做buffer。

我们知道在父类FilterInputStream和FilterOutputStream类中,已经在构造方法时封装了原始的InputStream或者OutputStream对象。

在我们使用BufferedInputStream和BufferedOutputStream来进行read()和write()调用的时候,并不一定直接对封装的InputStream或者OutputStream对象进行操作,而是要经过缓冲处理。

在BufferedInputStream的read()中,实际上是一次读取了多个字节到缓冲数组,而非一次只读取一个。后续的read()操作可以直接从数组中获取字节,而不必再次调用封装的InputStream对象的read()操作。这样做其实在一定情况下可以减少底层的read调用次数,降低成本开销,提高了效率。

在BufferedOutputStream中也是一样,它的write()会先把数据写到缓冲数组中,直到数据达到了某个特定的限额,再调用write()的时候回真正调用到封装的OutputStream对象的write()方法。

DataInputStream/DataOutputStream

这也是比较重要的一对Filter实现。那么说起功能,实际上就不得不提到他们除了extends FilterInputStream/FilterOutputStream外,还额外实现了DataInput和DataOutput接口。

我们可以先来看下DataInput和DataOutput这两个interface。

再看DataOutput:

而DataInputStream/DataOutputStream这一对实际上所做的也就是这两个接口所定义的方法。再DataInputStream/DataOutputStream中,这些方法做了拼接和拆分字节的工作。通过这些方法,我们可以方便的读取、写出各种我们实际所面对的类型的数据,而不必具体去在字节层面上做细节操作。

PrintStream(在控制台可以输出文件内的字符,重定向输出)

这个类不成对,只有这样一个OutputStream。看起名字中的Print,我们会想到什么?println()方法和print()方法。实际上,包括java.lang包中的System.out和System.error,使用的都是PrintStream类对象。

从使用层面来看,这个类主要是提供了丰富的print()和println()的各类参数重载方法。对各个类型的输出,包括换行处理等都集成在内。还有format()方法,帮我们做到了类似C语言中printf()函数的效果。

而从实现上来看,这些print()和println()最终都调用了各类write()方法。在这些write()方法中,实际上使用到了类中封装的这两个属性。

private BufferedWriter textOut;private OutputStreamWriter charOut;

他们都是Writer的子类,是字符流处理类,有编码解码机制,我们会在后续文章中详细说明。

PushbackInputStream

此类增加了“回退(push back)”功能。在读完一部分数据之后,可以“回退”到之前读过的某个“位置”,并重新设置这些数据。

在实现上,类似于BufferedInputStream,在PushbackInputStream增加了缓冲数组,回退时调整数组的下标索引,并边回退边重置数据。

自己给自己写了一个小案例防止忘记

public static void main(String[] args) throws IOException {    File f = new File("e:\\test.txt");    if(!f.exists()){        f.createNewFile();    }    String path = f.getAbsolutePath();    //字节流    OutputStream fos = new FileOutputStream(path, true);    String s = "hello world你好";    fos.write(s.getBytes(),0,s.getBytes().length);    InputStream fis = new FileInputStream(path);    byte[] b = new byte[fis.available()];    fis.read(b, 0, fis.available());    String str = new String(b,"UTF-8");    str = str+"qin";    fos.write(str.getBytes(), 0, str.getBytes().length);    //字符流    FileWriter fw = new FileWriter(path,true);    BufferedWriter bw = new BufferedWriter(fw);    bw.newLine();    bw.write("新一行");    bw.flush();    fw.close();    FileReader fr = new FileReader(path);    BufferedReader br = new BufferedReader(fr);    String ss = br.readLine();    while(ss != null){        System.out.println(ss);        ss = br.readLine();    }    br.close();    fr.close();    ......}
0 0