2986 2018-04-09 2020-06-25

前言:程序和数据设备之间的顺序数据流动称为流,Java中的流分为输入流和输出流。

一、字节流

字节流是以8位(8bit为1字节)为单位的流,表示以字节为单位从流中读取或向流中写入信息。一个字符可能有多个字节。

字节流的基本类图如下

Java字节流基本类图

1、InputStream

InputStream是字节输入流的顶级父类,类的基本声明如下

public abstract class InputStream implements Closeable {
    // 最大跳过的缓冲字节数
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    // read方法是由子类自由扩展的,一次读入一个字节
    public abstract int read() throws IOException;

    // 一次读入一个字节数组,一个byte就等同于一个字节
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    // 指定起点坐标,读入len长度的字节,放入byte数组
    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;
    }

    public long skip(long n) throws IOException {
        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }
        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }
        return n - remaining;
    }

    public int available() throws IOException {
        return 0;
    }

    public void close() throws IOException {}
}

2、OutputStream

与InputStream相对应的,OutputStream是字节输出流的顶级父类,OutputStream类声明如下

public abstract class OutputStream implements Closeable, Flushable {
    // write方法是由子类自由扩展的
    public abstract void write(int b) throws IOException;

    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }   

    public void flush() throws IOException {
    }

    public void close() throws IOException {
    }
}

3、FileInputStream

我们先看一下用于操作文件的文件输入流。

public class FileInputStream extends InputStream {

    private final FileDescriptor fd;

    private final String path;

    private FileChannel channel = null;
    // 锁对象
    private final Object closeLock = new Object();
    // 线程可见性
    private volatile boolean closed = false;

    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();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        open(name);
    }

    public int read() throws IOException {
        return read0();
    }
    // 注意这个方法,意味着是C语言来读取文件
    private native int read0() throws IOException;  
    // 同理
    public native long skip(long n) throws IOException;
    public native int available() throws IOException;

    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }
}

FileOutputStream同理,在此不再赘述。

4、BufferedInputStream

BufferedInputStream是一个具有缓冲区的字节包装流(装饰流),整个Java的IO流架构都是基于半透明装饰模式

public class BufferedInputStream extends FilterInputStream {
    // 默认的缓冲区大小,可指定大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    // 最大值
    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

    protected volatile byte buf[];

    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    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;
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

    public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();
                return;
            }
            // Else retry in case a new buf was CASed in fill()
        }
    }
}

BufferedOutputStream同理,其他的包装流也同理,这里不再一一列举。

5、文件的拷贝

private static void testByte() throws FileNotFoundException {
    FileInputStream is = new FileInputStream("html/file.txt");
    BufferedInputStream bis = new BufferedInputStream(is);

    FileOutputStream fos = new FileOutputStream(new File("html/file-copy.txt"));
    BufferedOutputStream ois = new BufferedOutputStream(fos);
    try {
        int i;
        // 当然还有另外两种方式
        while ((i = bis.read()) != -1) {
            ois.write(i);
        }
        ois.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            ois.close();
            bis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

需要注意的是,所谓的输入/输出都是针对Java语言内部的。

二、字符流

字符流是以16位(2字节)为单位的流,表示以Unicode字符为单位从流中读取或向流中写入信息。一个字符可能有多个字节,因此对于字符的读取,字符流应足够聪明来识别一次应该读取几个字节。

1、Reader

Reader是字符输入流的顶级父类,类的基本声明如下

public abstract class Reader implements Readable, Closeable {
    protected Object lock;

    protected Reader() {
        this.lock = this;
    }

   protected Reader(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }    

    public int read(java.nio.CharBuffer target) throws IOException {
        int len = target.remaining();
        char[] cbuf = new char[len];
        int n = read(cbuf, 0, len);
        if (n > 0)
            target.put(cbuf, 0, n);
        return n;
    }

    public int read() throws IOException {
        char cb[] = new char[1];
        if (read(cb, 0, 1) == -1)
            return -1;
        else
            return cb[0];
    }
    // 关键方法留作子类实现其具体特征,一次读入一个字符,这个字符以数字返回
    /**
     * Reads a single character.
     *
     * @return The character read, as an integer in the range
     *         0 to 65535 (<tt>0x00-0xffff</tt>), or -1 if the
     *         end of the stream has been reached
     * @exception  IOException  If an I/O error occurs
     */
    abstract public int read(char cbuf[], int off, int len) throws IOException;

    abstract public void close() throws IOException;
}

2、Writer

Writer是字符输出流的顶级父类,类的基本声明如下

public abstract class Writer implements Appendable, Closeable, Flushable {
    private char[] writeBuffer;
    private static final int WRITE_BUFFER_SIZE = 1024;
    protected Object lock;

    protected Writer() {
        this.lock = this;
    }

    protected Writer(Object lock) {
        if (lock == null) {
            throw new NullPointerException();
        }
        this.lock = lock;
    }    

    public void write(int c) throws IOException {
        synchronized (lock) {
            if (writeBuffer == null){
                writeBuffer = new char[WRITE_BUFFER_SIZE];
            }
            writeBuffer[0] = (char) c;
            write(writeBuffer, 0, 1);
        }
    }

   public void write(char cbuf[]) throws IOException {
        write(cbuf, 0, cbuf.length);
    }
    // 关键方法由子类实现
    abstract public void write(char cbuf[], int off, int len) throws IOException;

    public void write(String str) throws IOException {
        write(str, 0, str.length());
    }

    public void write(String str, int off, int len) throws IOException {
        synchronized (lock) {
            char cbuf[];
            if (len <= WRITE_BUFFER_SIZE) {
                if (writeBuffer == null) {
                    writeBuffer = new char[WRITE_BUFFER_SIZE];
                }
                cbuf = writeBuffer;
            } else {    // Don't permanently allocate very large buffers.
                cbuf = new char[len];
            }
            str.getChars(off, (off + len), cbuf, 0);
            write(cbuf, 0, len);
        }
    }

    public Writer append(CharSequence csq) throws IOException {
        if (csq == null)
            write("null");
        else
            write(csq.toString());
        return this;
    }

    public Writer append(CharSequence csq, int start, int end) throws IOException {
        CharSequence cs = (csq == null ? "null" : csq);
        write(cs.subSequence(start, end).toString());
        return this;
    }

    public Writer append(char c) throws IOException {
        write(c);
        return this;
    }

    abstract public void flush() throws IOException;

    abstract public void close() throws IOException;    
}

3、InputStreamReader

InputStreamReader实现从字节输入流到字符流输入流的转换,FileReader是其派生出来的子类。

public class InputStreamReader extends Reader {
    // 流解码器
    private final StreamDecoder sd;

    public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }

    // 这个方法有编码参数,说明字节流可以转向指定的字符流,在某些情况下特别有用
    public InputStreamReader(InputStream in, String charsetName)
        throws UnsupportedEncodingException
    {
        super(in);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
    }

    public InputStreamReader(InputStream in, Charset cs) {
        super(in);
        if (cs == null)
            throw new NullPointerException("charset");
        sd = StreamDecoder.forInputStreamReader(in, this, cs);
    }

    public String getEncoding() {
        return sd.getEncoding();
    }

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

    public int read(char cbuf[], int offset, int length) throws IOException {
        return sd.read(cbuf, offset, length);
    }

    public void close() throws IOException {
        sd.close();
    }
}

public class FileReader extends InputStreamReader {
    public FileReader(String fileName) throws FileNotFoundException {
        super(new FileInputStream(fileName));
    }

    public FileReader(File file) throws FileNotFoundException {
        super(new FileInputStream(file));
    }   
}

4、BufferedReader

BufferedReader是一个具有缓冲区的字符包装流(装饰流)。

public class BufferedReader extends Reader {
    private Reader in;
    private char cb[];
    private int nChars, nextChar;   
    private static int defaultCharBufferSize = 8192;
    private static int defaultExpectedLineLength = 80;

    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        cb = new char[sz];
        nextChar = nChars = 0;
    }

    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }

    // 其他read方法省略
    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            for (;;) {
                if (nextChar >= nChars) {
                    fill();
                    if (nextChar >= nChars)
                        return -1;
                }
                if (skipLF) {
                    skipLF = false;
                    if (cb[nextChar] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];
            }
        }
    }

    public String readLine() throws IOException {
        return readLine(false);
    }

    // 这个函数虽然有很多的逻辑,不过我们还是可以大致猜测是读到换行符就返回之前读到的字符串
    String readLine(boolean ignoreLF) throws IOException {
        StringBuffer s = null;
        int startChar;
        synchronized (lock) {
            ensureOpen();
            boolean omitLF = ignoreLF || skipLF;
        bufferLoop:
            for (;;) {
                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars) { /* EOF */
                    if (s != null && s.length() > 0)
                        return s.toString();
                    else
                        return null;
                }
                boolean eol = false;
                char c = 0;
                int i;
                /* Skip a leftover '\n', if necessary */
                if (omitLF && (cb[nextChar] == '\n'))
                    nextChar++;
                skipLF = false;
                omitLF = false;
            charLoop:
                for (i = nextChar; i < nChars; i++) {
                    c = cb[i];
                    if ((c == '\n') || (c == '\r')) {
                        eol = true;
                        break charLoop;
                    }
                }
                startChar = nextChar;
                nextChar = i;
                if (eol) {
                    String str;
                    if (s == null) {
                        str = new String(cb, startChar, i - startChar);
                    } else {
                        s.append(cb, startChar, i - startChar);
                        str = s.toString();
                    }
                    nextChar++;
                    if (c == '\r') {
                        skipLF = true;
                    }
                    return str;
                }
                if (s == null)
                    s = new StringBuffer(defaultExpectedLineLength);
                s.append(cb, startChar, i - startChar);
            }
        }
    }    

    public void close() throws IOException {
        synchronized (lock) {
            if (in == null)
                return;
            try {
                in.close();
            } finally {
                in = null;
                cb = null;
            }
        }
    }
}

5、字符流的使用

private static void testChar() throws FileNotFoundException {
    FileReader fr = new FileReader("html/file.txt");
    BufferedReader br = new BufferedReader(fr);
    BufferedWriter bw = null;
    try {
        FileWriter fw = new FileWriter("html/file-copy1.txt");
        bw = new BufferedWriter(fw);
        int i;
        // 当然还有其他两种方法
        while ((i = br.read()) != -1) {
            System.out.print((char)i);
            bw.write(i);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            bw.close();
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、其他流

上述几种流是最常用到,除此之外,还有其他许多建立在上面的基础上的流。下面给出类图

Java数据流类图

四、装饰模式

1、优缺点

装饰模式的优点如下:

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 可以通过一种动态的方式来扩展一个对象的功能,如通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构建类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

装饰模式的缺点如下:

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加容易出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

2、适用环境

在一些情况可以使用装饰模式

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目成爆炸性增长;第二类是因为类定义不能被继承,如final类。

3、测试代码

下面是一些测试代码,可以进一步加深理解

public static void main(String[] args) throws Exception {
    byte[] bytes = new byte[]{65, 66, 67, 68};
    ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
    int i;
    System.out.println("开始读取ByteArrayInputStream--原始方法读取字节流");
    while ((i = bais.read()) != -1) {
        System.out.print((char) i + " ");
    }

    bais = new ByteArrayInputStream(bytes);
    BufferedInputStream bis = new BufferedInputStream(bais);
    System.out.println("\n开始读取BufferedInputStream--带有缓存的读取");
    while ((i = bis.read()) != -1) {
        System.out.print((char) i + " ");
    }

    bais = new ByteArrayInputStream(bytes);
    bis = new BufferedInputStream(bais);
    InputStreamReader isr = new InputStreamReader(bis);
    BufferedReader br = new BufferedReader(isr);
    String str;
    System.out.println("\n开始读取BufferedReader--带有缓存的字符流");
    while ((str = br.readLine()) != null) {
        System.out.print(str);
    }
}
// 下面是输出结果
开始读取ByteArrayInputStream--原始方法读取字节流
A B C D 
开始读取BufferedInputStream--带有缓存的读取
A B C D 
开始读取BufferedReader--带有缓存的字符流
ABCD

更多的特性以及具体怎么用,这个需要在实践中去学习。

总访问次数: 276次, 一般般帅 创建于 2018-04-09, 最后更新于 2020-06-25

进大厂! 欢迎关注微信公众号,第一时间掌握最新动态!