1.什么是流?
说到流,第一反应想到的就是水流。而java中的流,与水流非常的相似,可以把它想象成管道中的水流。组成流的元素不是水,而是数据。
流具有一个很重要的特性,就是方向性。它会从管道的一端流向另一端。管道的一端,是我们编写的程序,而另一端有可能是内存、文件或者网络等等。当流从内存、文件或网络流向程序,这种流称为输入流。当流从程序流向内存、文件或网络,这种流称为输出流。
2.流的分类:
上面将流分成了输入流和输出流。我们还可以从别的方面来进行分类。
(1)处理的数据单位
字节流:继承自InputStream/OutputStream,数据单位是字节(byte=8bit)
FileInputStream/FileOutputStream将文件作为流的输入/输出:
FileInputStream in = new FileInputStream(new File("stream.txt")); FileOutputStream out = new FileOutputStream(new File("stream.txt"));
FileInputStream提供了read()和read(byte[] b)两个方法来读取流中得数据。read()每次读取1byte的数据并返回,而
read(byte[] b)每次能读取更多的数据,并将数据放入b中,返回读取数据的大小。
int data1 = in.read(); byte[] data2 = new byte[256]; int lenght = in.read(data2);
FlieOutputStream的write(byte[] b)的方法与FileInputStream的read(byte[] b)方法很相似,不在赘述。
byte[] data = "aabbcc".getBytes(); out.write(data); out.flush();
字符流:继承自Reader/Writer,数据单位是字符(2byte=16bit)
FileReader/FileWriter将文件作为流的输入/输出:
与FileInputStream/FileOutputStream相类似,数据单位是字符。
(2)功能
节点流:直接将内存、文件、网络抽象而成的流。
例如FileInputStream/FileOutputStream,就可以将其看作为文件。
处理流:在已存在的流(节点流或处理流)的基础上,为数据的处理提供更强大的功能。
上面4个图中,浅颜色的都为处理流。说到处理流,就不得不说设计模式中得装饰器模式。在原有流的基础上,处理流运用装饰器模式对其进行了扩展。
BufferInputStream/BufferOutputStream/BufferReader/BufferWriter:
BufferedInputStream in = new BufferedInputStream(new FileInputStream(new File("path"))); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File("path"))); BufferedReader br = new BufferedReader(new FileReader(new File("path"))); BufferedWriter bw = new BufferedWriter(new FileWriter(new File("path")));
构造这4个处理流时都传入了相应的节点流。其实,输入输出仍然是调用节点流里的方法,但是处理流在方法前后做了一些工作,使输入输出方法得到了优化,这就是装饰器模式的应用。那处理流到底做了什么样的优化呢?
BufferInputStream/BufferOutputStream有8192字节的缓冲区,BufferReader/BufferWriter有8192字符的缓冲区。当BufferInputStream/BufferedReader在读取文本文件时,会先尽量从文件中读入数据并置入缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取。使用BufferOutputStream/BufferedWriter时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。
讲这么多不如看源码,就拿BufferWriter来举例说明:
public void write(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } // 如果当前要输出数据大小大于缓冲区的大小 // 先将缓冲区中现有的数据输出 // 然后将当前要输出数据直接输出,不再进入缓冲区 if (len >= nChars) { flushBuffer(); out.write(cbuf, off, len); return; } // 否则,将输出数据先放入缓冲区 // 直到缓冲区满,将缓冲区中数据一并输出 int b = off, t = off + len; while (b < t) { int d = min(nChars - nextChar, t - b); System.arraycopy(cbuf, b, cb, nextChar, d); b += d; nextChar += d; if (nextChar >= nChars) flushBuffer(); } } }
out.write(cbuf, off, len)中的out实际上是传入的节点流的实例,实际的输出还是调用它的write(cbuf, off, len)方法。
BufferReader/BufferWriter还分别提供了readLine()和newLine()方法,方便数据的输入输出。
下面再简单介绍两个常用的处理流:
DataInputStream/DataOutputStream将输入/输出的数据封装成java的基本数据类型:
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("path")))); DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(new File("path"))));
DataInputStream/DataOutputStream可以建立在节点流或者处理流之上,它们提供了针对与java基本数据类型的输入/输出方法。
in.readByte(); in.readChar(); in.readUTF(); out.writeBytes("aa啊啊"); out.writeChars("aa啊啊"); out.writeUTF("aa啊啊");
InputStreamReader/OutputStreamWriter将字节流转换为字符流:
InputStreamReader in = new InputStreamReader(new FileInputStream("path")); char[] data = new char[100]; in.read(data);
PS:流关闭的时候只需关闭最顶层的流即可,例如:
FileOutputStream fos = new FileOutputStream(new File("path")); BufferedOutputStream bos = new BufferedOutputStream(fos); DataOutputStream dos = new DataOutputStream(bos); dos.close();
楼下是疯子。哈哈