Java Io 操作解析(2)——FilterInputStream – 热爱改变生活
我的GitHub GitHub |     登录
  • If you can't fly, then run; if you can't run, then walk; if you can't walk, then crawl
  • but whatever you do, you have to keep moving forward。
  • “你骗得了我有什么用,这是你自己的人生”
  • 曾有伤心之地,入梦如听 此歌

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

源码分析 sinvader 3994℃ 0评论

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 中,看看这时候干了什么【查看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 in class InputStream
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 – If b is null.
IndexOutOfBoundsException – If off is negative, len is negative, or len is greater than b.length - off
IOException – 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 中只有:我是测试的文字 多出来的那个就是在第二次取值的时候没有被覆盖掉的。

¥ 有帮助么?打赏一下~

转载请注明:热爱改变生活.cn » Java Io 操作解析(2)——FilterInputStream


本博客只要没有注明“转”,那么均为原创。 转载请注明链接:sumile.cn » Java Io 操作解析(2)——FilterInputStream

喜欢 (1)
发表我的评论
取消评论
表情

如需邮件形式接收回复,请注册登录

Hi,你需要填写昵称和邮箱~

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址