首页 > 程序开发 > 软件开发 > 其他 >

java io --- Reader类

2016-10-18

在前几篇文章中一直讲的都是InputStream,这是操作字节流的类,然而我们在程序中往往要从文件等stream中读取字符信息,如果只用InputStream能否读取字符信息呢?当然可以。

在前几篇文章中一直讲的都是InputStream,这是操作字节流的类,然而我们在程序中往往要从文件等stream中读取字符信息,如果只用InputStream能否读取字符信息呢?当然可以。但是这涉及到了一个编码和解码的问题,传输双方必须才用同一种编码方式才能正确接收,这就导致每次在传输时,传输方需要做这么几件事:

1)将需要传输的字符编码成指定字节

2)传输字节

接收方需要做这么几件事:

1)接收字节

2)将字节解码成对应的字符

我们看一下下面的例子:

我在对应目录有一个文件,这个文件是按照utf-8编码的,现在利用InputStream读取到一个byte数组中,如果我们想要读取到文件的内容,还需要继续转码成utf-8格式的字符串。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;

/**
 * Created by zhaohui on 16-10-14.
 */
public class Code {
    public static void main(String[] args) {
        try {
            FileInputStream inputStream = new FileInputStream("/home/zhaohui/tmp/zhaohui");
            byte[] buf = new byte[100];
            int length = inputStream.read(buf);
            System.out.println("the length of bytes is " + length);

            // 将字节数组中指定位置的字节转码成对应的字符串
            String content = new String(buf, 0, length, Charset.forName("utf-8"));
            System.out.println("the content is " + content);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

输出:

thelengthofbytesis16


thecontentis你好吗?

从上面的例子中,我们看到只有InputStream就能解决传输字符串的问题了,但是每次都要先读成byte字节,再进行转码,麻烦,能不能直接传字符呢?????

答案是:不能!!!

计算机只认识0和1,也就是byte,只能传输byte。

但是别人的博客都说Reader和Writer神马的能传啊?这是理解角度的不同,我就认为不能传字符,爱咋咋地!

好的,我现在就正式介绍这个“能”传字符的Reader(Writer类似,我就不说了)。

先用一个例子说明,如果我们直接用Reader读取文件,会是怎样的?

import java.io.*;  
import java.nio.charset.Charset;  
  
/** 
 * Created by zhaohui on 16-10-14. 
 */  
public class Code {  
    public static void main(String[] args) {  
        try {  
  
            InputStream in = new FileInputStream("/home/zhaohui/tmp/zhaohui");  
            InputStreamReader reader = new InputStreamReader(in, Charset.forName("utf-16"));  
  
            char [] buf = new char[100];  
            int length = reader.read(buf);  
            System.out.println("the length is "+length);  
  
            for (int i =0;i<length;i++){  
                System.out.println("char ["+i+"] is "+buf[i]);  
            }  
  
            System.out.println("the content is " + String.valueOf(buf, 0 ,length));  
  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
  
    }  
}  
输出:the length is 5
char [0] is 你
char [1] is 好
char [2] is 吗
char [3] is ?
char [4] is
the content is 你好吗?

这样一来,是不是清爽了,也就是在你读取文件的时候,使用Reader可以直接指定解码方式,这样就可以直接读字符内容了。关于编码的问题,比较复杂,有兴趣的请参考网上其他内容,比如Java中的char,是两个字节,但是如果你的文件是utf-8,读取字符时可能就会出现问题,因为utf-8的字符是变长的,有的字符是一个字节,有的是两个,有的是三个。
不是说计算机只能传输字节么,为什么这里能直接读取字符了,好,下面我带大家深入剖析一下Reader类。
废话少说,先上类图:
\
Java几乎为每一个InputStream都设计了一个对应的Reader,比如如果你想直接读取文件里的字符,可以用FileReader来代替FileInputStream。BufferedReader也是一个装饰者模式的reader,接收一个Reader作为参数,从而对Reader提供缓存功能。

但是这众多的Reader中,却有一部分没什么用(个人观点),先从Reader的源码看起:
public abstract class Reader implements Readable, Closeable {  
    // 每次读取一个字符  
    public int read() throws IOException {  
        char cb[] = new char[1];  
        if (read(cb, 0, 1) == -1)  
            return -1;  
        else  
            return cb[0];  
    }  
  
    abstract public int read(char cbuf[], int off, int len) throws IOException;  
      
    // ...... 省略  ......  
  
}  

我这里只列出了Reader的两个灵魂函数,即read()和read(char cbuf[], int off, int len)
read()和InputStream中的read()相似,不过这里是只读取一个字符,而这个方法通过调用read(char cbuf[], int off, int len) 来实现,这个方法是抽象方法,Reader的子类通过实现这个方法达到读取不同介质的目的。
接下来就说说这个Reader家族中最重要的实现类,InputStreamReader类。
先看看这个类的结构:
\
接着还是先放部分代码上来。

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 int read() throws IOException {  
        return sd.read();  
    }  
  
    public int read(char cbuf[], int offset, int length) throws IOException {  
        return sd.read(cbuf, offset, length);  
    }  
      
    // ..... 省略 .....  
}  

从上面的代码可以看出,InputStreamReader有一个重要的域,就是这个

private final StreamDecoder sd;

就是这个域帮助InputStreamReader解决了编码的问题。其实这个StreamDecoder 类也是Reader的子类,从后面的read()方法也能看出,InputStreamReader的read()其实就是这个sd的read()方法,在剖析StreamDecoder 之前,我们再看一眼InputStreamReader的构造方法。

InputStreamReader有四个构造函数,我这里只说前两个,第一个接收一个InputStream作为参数。第二个多了一个charsetName,这就是指定了编码方式,第一种为什么不指定?如果不指定就采用系统默认的编码方式。这在后面的StreamDecode的源码中马上就能看出来。

现在我们再看看StreamDecode的源码:

public class StreamDecoder extends Reader {  
  
    // StreamDecoder的静态构造方法,如果不指定编码,就采用默认的编码  
    public static StreamDecoder forInputStreamReader(InputStream var0, Object var1, String var2) throws UnsupportedEncodingException {  
        String var3 = var2;  
        if (var2 == null) {  
            var3 = Charset.defaultCharset().name();  
        }  
  
        try {  
            if (Charset.isSupported(var3)) {  
                return new StreamDecoder(var0, var1, Charset.forName(var3));  
            }  
        } catch (IllegalCharsetNameException var5) {  
            ;  
        }  
  
        throw new UnsupportedEncodingException(var3);  
    }  
  
    public static StreamDecoder forInputStreamReader(InputStream var0, Object var1, Charset var2) {  
        return new StreamDecoder(var0, var1, var2);  
    }  
  
    public int read() throws IOException {  
        return this.read0();  
    }  
  
    // 由于在java中每个字符都是两个字节,因此这里每次读取两个字节,转成char类型  
    private int read0() throws IOException {  
        Object var1 = this.lock;  
        synchronized (this.lock) {  
            if (this.haveLeftoverChar) {  
                this.haveLeftoverChar = false;  
                return this.leftoverChar;  
            } else {  
                char[] var2 = new char[2];  
                int var3 = this.read(var2, 0, 2);  
                switch (var3) {  
                    case -1:  
                        return -1;  
                    case 0:  
                    default:  
                        assert false : var3;  
  
                        return -1;  
                    case 2:  
                        this.leftoverChar = var2[1];  
                        this.haveLeftoverChar = true;  
                    case 1:  
                        return var2[0];  
                }  
            }  
        }  
    }  
}  

这个类的核心就是read()这个方法,由于这里直接操作InputStream进行read(),因此可以读取出2个字节,java中每两个字节转成一个字符。

这就是Reader可以读取字符的原因,只不过是利用InputStream先将字节读取出来,再按照一定的编码方式转码,因此这就是我前面所说的Reader也不能读取字符的原因,因为它只是读字节,转字符而言。

最后再说一说这个BufferedReader,和BufferedInputStream类似,它也是一个装饰者模式的类,接收一个Reader类,提供缓存功能。看看它的源码:

public class BufferedReader extends Reader {  
  
    private Reader in;  
  
    // 如果不指定缓存长度,就使用默认值  
    public BufferedReader(Reader in) {  
        this(in, defaultCharBufferSize);  
    }  
  
    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] == &#39;\n&#39;) {  
                        nextChar++;  
                        continue;  
                    }  
                }  
                return cb[nextChar++];  
            }  
        }  
    }  
  
}  

我们这里只研究它最简单的构造方法,它的构造函数接收一个Reader对象,并建立一个缓存,如果未指定缓存长度,就使用默认的长度。

BufferedReader的灵魂方法read()和BufferedInputStream的read()方法类似,都是采用了一个fill方法,可以参考 java io -- FilterInputStream 与 装饰者模式这篇文章。

如果没有数据就用fill去读取一块数据,放在缓存里,如果缓存里有数据,直接从缓存里读就OK了。

总结:这篇文章总结了Reader类的用法与原理,但是本文没有具体涉及java的编码问题,这是一个较大的话题,有兴趣的可以去网上参考其他文章。

相关文章
最新文章
热点推荐