Java Io操作解析(2)——FilterInputStream

无简介

FilterInputStream是什么

FilterInputStream继承自InputStream,这个我们从FilterInputStream.class类中看到

public class FilterInputStream extends InputStream

然后,使用了装饰器模式编写了这个类【查看装饰器模式】 但是,装饰器模式有一个缺点:在编写程序的时候,它给我们提供了相当多的灵活性(因为我们可以很容易的混合和匹配属性),但是他同时也增加了代码的复杂性。Java I/O类库操作不便的原因在于:我们必须创建许多类——“核心”I/O类型加上所有的装饰器,才能得到我们所希望的单个I/O对象。【本段来自于Thinging in Java】

功能

构造器参数如何使用

DataInputStream

与DataOutputStream搭配使用,因此我么可以按照可移植方式从流读取基本数据类型(int,char,long等)

InputStream包含用于读取基本类型数据的全部接口

BufferedInputStream

使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区”

InputStream,可以指定缓冲区大小(可选的)本质上不提供接口,只不过是向进程中添加缓冲区所必须的。与接口对象搭配

LineNumberInputStream

跟踪数据流中的行号;可调用getLineNumber()和setLineNumber(int)

InputStream仅使用了行号,因此可能要与接口对象搭配使用

PushbackInputStream

具有“能弹出一个字节的缓冲区”。因此可以将读取到的最后一个字符回退

InputStream通常作为编译器的扫描器,之所以包含在内饰因为Java编译器的需要,我们可能永远不会用到

再来看一下FilterOutputStream类型

功能

构造器参数

如何使用

DataOutputStream

与DataInputStream搭配使用,因此可以按照可移植方式向流中写入基本类型数据(int ,char long 等)

OutputStream包含用于写入基本数据的全部接口

PrintStream

用于产生格式化的输入。其中DataOutputStream处理数据的储存,PrintStream处理显示

OutputStream可以用boolean值指示是否每次执行时清空缓冲区(可选的)应该是对OutputStream对象的”final”封装,可能会经常使用它

BufferedOutputStream

使用它以避免每次发送数据时都要进行实际的写操作。代表“使用缓冲区”。可以调用flush()清空缓冲区。

OutputStream,可以指定缓冲区大小(可选的),本质上并不提供接口,只不过是向进程中添加缓冲区所必须的。与接口对象搭配。

通过代码看问题

下面的代码是一个读取本地文件内容的代码,一步一步来分析下它是怎么做的。

public static void main(String[] args) {
File file = new File(“src\\cn\\sumile\\1.txt”);
InputStream in;
try {
in = new FileInputStream(file);//new FileInputStream的时候,内部都干了什么呢?【查看
BufferedInputStream bis = new BufferedInputStream(in);//接着看到这里,又将流传入到了BufferedInputSteam中,看看这时候干了什么【查看

            ![](https://sumile.cn/upload/zhixiang_30.jpg)byte b\[\] = new byte\[1024\];

int end = -1;
String result=“”;
//上面的第一句,我们搞了一个1Kb的byte类型的数组,用来缓冲读取
//然后,我们开始读取 bis.read(b) 这个方法就是在读取,看一下里面是怎么实现的【跟我走
while ((end = bis.read(b)) != -1) {
result+=new String(b, 0, end);
}
//我们把这两句代码单独拿出看【点这里
System.out.println(result);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

new FileInputStream的时候,内部都干了什么呢?—> Creates a FileInputStream by opening a connection to an actual file, the file named by the path name in the file system. A new FileDescriptor object is created to represent this file connection. First, if there is a security manager, its checkRead method is called with the path represented by the file argument as its argument. If the named file does not exist, is a directory rather than a regular file, or for some other reason cannot be opened for reading then a FileNotFoundException is thrown. 通过打开一个实际文件并建立连接来创建一个FileInputStream,文件被文件系统中的文件的路径来命名。一个新的FileDescriptor对象被创建出来来表示这个对文件的连接。 首先,如果有安全管理器,它将会把文件的参数作为它的参数来调用它的checkRead方法。 如果这个名字的file不是一个文件,或者是一个文件夹,抑或是什么其他类型的原因导致不能被打开,那就报FileNotFoundException

下面是代码:

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);
}

上面就是检查文件路径啊,安全管理器检查啊什么的一堆,然后通过native方法open打开了文件。 总之,在new FIleInputStream之后,我们拿到了关于这个文件的流。【返回这个是new BufferedInputStream的时候调用的代码

public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException(“Buffer size <= 0”);
}
buf = new byte[size];
}
我们发现,一进入到这个方法,它就调用了父类的构造方法,然后给他父类FilterInputStream内部的
protected volatile InputStream in;
赋了值。为什么这么做请参看【程序猿的复仇——Java中的Decorator模式
最后它自己又定义了一个字符数组,默认的值为:
private static int defaultBufferSize = 8192;

好了,看完这个,接着看下面的代码【跳转要看bis.read(),我们打开read方法:

public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}

可以看到,它又去调用了read(b,0,b.length)这个方法,那直接看三个参数的这个方法吧

public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}

下面是介绍:

public int read(byte[] b,
int off,
int len)
throws IOException

Reads up to len bytes of data from this input stream into an array of bytes. If len is not zero, the method blocks until some input is available; otherwise, no bytes are read and 0 is returned.This method simply performs in.read(b, off, len) and returns the result.

Overrides:

[read](http://docs.sumile.cn/java/api/java/io/InputStream.html#read-byte:A-int-int-) in class [InputStream](http://docs.sumile.cn/java/api/java/io/InputStream.html "class in java.io")

Parameters:

b - the buffer into which the data is read.

off - the start offset in the destination array b

len - the maximum number of bytes read.

Returns:

the total number of bytes read into the buffer, or -1 if there is no more data because the end of the stream has been reached.

Throws:

[NullPointerException](http://docs.sumile.cn/java/api/java/lang/NullPointerException.html "class in java.lang") - If b is null.

[IndexOutOfBoundsException](http://docs.sumile.cn/java/api/java/lang/IndexOutOfBoundsException.html "class in java.lang") - If off is negative, len is negative, or len is greater than b.length - off

[IOException](http://docs.sumile.cn/java/api/java/io/IOException.html "class in java.io") - if an I/O error occurs.

See Also:

in

从流中读取len长度的内容放置到字节数组中,如果len的长度不是0的话,read方法会被阻塞,知道这个流重新变成可用的。否则,不会读取任何字节并且返回0. 这个方法只需要执行in.read(b,0,len)并且返回值就可以了。 它调用的是in.read,而in,在上面已经看到过,是InputStream的那个in,这就是装饰器里面被装饰的那个。 所以,它在调用in.read的时候,其实是去执行InputStream中的read方法去了。(如过不明白,去代码里面按住ctrl点一点方法跟着跳转就好)

然后,来看InputStream中的read方法:

public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 len < 0 len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}

    int c = read();
    if (c == -1) {
        return -1;
    }
    b\[off\] = (byte)c;

    int i = 1;
    try {
        for (; i < len ; i++) {
            c = read();
            if (c == -1) {
                break;
            }
            b\[off + i\] = (byte)c;
        }
    } catch (IOException ee) {
    }
    return i;
}

瞄几眼代码,不用细看,先来看下它的注释是怎么说的:

Reads up to len bytes of data from the input stream into an array of bytes. An attempt is made to read as many as len bytes, but a smaller number may be read. The number of bytes actually read is returned as an integer. This method blocks until input data is available, end of file is detected, or an exception is thrown. If len is zero, then no bytes are read and 0 is returned; otherwise, there is an attempt to read at least one byte. If no byte is available because the stream is at end of file, the value -1 is returned; otherwise, at least one byte is read and stored into b. The first byte read is stored into element b[off], the next one into b[off+1], and so on. The number of bytes read is, at most, equal to len. Let k be the number of bytes actually read; these bytes will be stored in elements b[off] through b[off+k-1], leaving elements b[off+k] through b[off+len-1] unaffected. In every case, elements b[0] through b[off] and elements b[off+len] through b[b.length-1] are unaffected. 从输入流中读出len字节长度的数据放入到一个字节数组中。尝试读取和len长度一样多的字节数,但是可能读到的是一个比len小的数字。实际读取到的字节数会作为一个int形的数字返回。 这个方法会被阻塞直到输入的数据是可获得的、已经到了文件的末尾或者抛出了异常。 如果len是0,那么不会读取任何字节并返回0.其他情况下,会尝试读取至少1byte。如果因为流已经到了文件的末尾而没有任何数据存在,将会返回-1。或者,至少会读取1byte的数据放到b中。 第一个读取的字节将会放入到b[off]中,下一个放入b[off+1]中,依次而为。读取的字节的数量一般来说和len是相等的,然后将k(代码中为i)设置为实际读取的字节数;这些读取的字节将会存储到数组b中从off开始,到off+k-1的地方,而从off+k到off+len-1中的元素不受影响。(这里的最后一句很重要,等下看代码的时候会回顾) 每一种情况下,b中从0到off和从off+k到b.length-1的都是不变的。

看了上面的介绍,这时候我们应该知道:

  • 1.read方法有个返回值,返回值是一个int类型的数字,标识了当前我读了多少个byte,或者如果到了文件末尾的话,这里返回的值是-1。
  • 2.我们不是传进去一个byte数组么,也就是上面的b数组,程序会将读取到的byte放到b中,b这个数组有好大,是放到哪里呢?就是占用了b数组中从off开始len个长度的空间。
  • 3.如果我们读取的byte没有占满整个b数组的话,那么我们没有设置值的那些空间中,其中的值是依旧存在的。这点很重要。【这句,一定要记住

记住上面总结出来的几句话,然后我们继续看那个【实例

byte b[] = new byte[1024];
int end = -1;
while ((end = bis.read(b)) != -1) {
result+=new String(b, 0, end);
}

可以看到,读取字符的个数我们赋值给了end,在bis.read(b)执行之后,本次读取到的内容就放到了b数组之中,然后我们把b数组中的内容变成字符串,也就是new String(b,0,end)。那么,new String(b,0,end)中的end为什么要写成end呢?还记得上面的第二三条总结么?b这个数组我们可以定义为很大很大,假如说可以放10000个byte,但是如果在读取的时候我只读取到了100个byte,难道我要完整的将b这个byte的数组转换成字符串么?另外一种错误是我一个文件有10001个字符,第一次读取10000个的时候没有问题,但是我第二次读取的时候,我只读取了一个byte,那么我取b中所有的byte就出错了【错例】。那为什么要写成end呢,第一条总结里面说了,read方法里面会给我们一个返回值,如果不是-1,那么这个值表示的就是读取到的字符数,那么我们读取b中从0开始,到end那里的值就好了。 这是个错误的例子

public static void main(String[] args) {
File file = new File(“src\\cn\\sumile\\1.txt”);
InputStream in;
try {
in = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(in);
byte b[] = new byte[8];
int end = -1;
String result = “”;
while ((end = bis.read(b)) != -1) {
result += new String(b, 0, b.length);
}
System.out.println(result);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

输出的结果是:我是测试的文字试 但是我的txt中只有:我是测试的文字 多出来的那个就是在第二次取值的时候没有被覆盖掉的。

-------------本文结束  感谢您的阅读-------------
下次一定