java io --- Reader类

在前几篇文章中一直讲的都是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();
        }

    }
}

输出:

the length of bytes is 16

the content is 你好吗?

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

 

答案是:不能!!!

计算机只认识01,也就是byte,只能传输byte

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

 

好的,我现在就正式介绍这个传字符的ReaderWriter类似,我就不说了)。

先用一个例子说明,如果我们直接用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来代替FileInputStreamBufferedReader也是一个装饰者模式的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] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];
            }
        }
    }

}

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

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

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

 

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














  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值