java中的流(stream)

1.什么是流?

说到流,第一反应想到的就是水流。而java中的流,与水流非常的相似,可以把它想象成管道中的水流。组成流的元素不是水,而是数据。

流具有一个很重要的特性,就是方向性。它会从管道的一端流向另一端。管道的一端,是我们编写的程序,而另一端有可能是内存、文件或者网络等等。当流从内存、文件或网络流向程序,这种流称为输入流。当流从程序流向内存、文件或网络,这种流称为输出流。

stream1

 

2.流的分类:

上面将流分成了输入流和输出流。我们还可以从别的方面来进行分类。

(1)处理的数据单位

字节流:继承自InputStream/OutputStream,数据单位是字节(byte=8bit)

stream2stream3

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)

stream4stream5

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

1 Reply to “java中的流(stream)”

发表评论