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字节,再进行转码,麻烦,能不能直接传字符呢?????

答案是:不能!!!

计算机只认识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] == '\n') {
                        nextChar++;
                        continue;
                    }
                }
                return cb[nextChar++];
            }
        }
    }

}

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

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

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

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

时间: 2024-10-27 01:41:10

java io --- Reader类的相关文章

【java】io流之字符输入流:java.io.Reader类及子类的子类java.io.FileReader

1 package 文件操作; 2 3 import java.io.File; 4 import java.io.FileReader; 5 import java.io.IOException; 6 import java.io.Reader; 7 8 public class TestReader { 9 public static void main(String[] args) throws IOException { 10 File file=new File("D:"+F

Java IO: Reader和Writer

作者: Jakob Jenkov 译者: 李璟([email protected]) Reader 原文链接 Reader是Java IO中所有Reader的基类.Reader与InputStream类似,不同点在于,Reader基于字符而非基于字节.换句话说,Reader用于读取文本,而InputStream用于读取原始字节. 请记住,Java内部使用UTF8编码表示字符串.输入流中一个字节可能并不等同于一个UTF8字符.如果你从输入流中以字节为单位读取UTF8编码的文本,并且尝试将读取到的字

Java IO: Reader And Writer

原文链接 作者: Jakob Jenkov  译者: 李璟([email protected]) Java IO的Reader和Writer除了基于字符之外,其他方面都与InputStream和OutputStream非常类似.他们被用于读写文本.InputStream和OutputStream是基于字节的,还记得吗? Reader Reader类是Java IO中所有Reader的基类.子类包括BufferedReader,PushbackReader,InputStreamReader,St

利用java.io.File类实现遍历本地磁盘上指定盘符或文件夹的所有的文件

2016-11-18 这是本人的第一篇随笔博客,纠结了半天还是选择自己学的时候比较用心的一些知识点上.利用java.io.File类指定本地的文件夹进行遍历所有的文件. package org.lxm.filedemo; import java.io.File; import java.util.Scanner; /* * 本程序是将某个盘的所有文件夹及其文件全部调出来的操作 */ public class FileAllDemo { public static void main(String

33.JAVA编程思想——JAVA IO File类

33.JAVA编程思想--JAVA IO File类 RandomAccessFile用于包括了已知长度记录的文件.以便我们能用 seek()从一条记录移至还有一条:然后读取或改动那些记录. 各记录的长度并不一定同样:仅仅要知道它们有多大以及置于文件何处就可以. 首先.我们有点难以相信RandomAccessFile 不属于InputStream 或者OutputStream 分层结构的一部分.除了恰巧实现了DataInput 以及DataOutput(这两者亦由 DataInputStream

java IO,bufferedReader类

1,掌握bufferedreader类作用 2,掌握键盘输入的基本格式. Buffer:表示缓冲区,之前的StringBuffer,缓冲区中的内容可以更改,可以提高效率. 如果要想接收任意长度的数据,而且避免乱码的产生,就可以使用bufferedreader. public class BufferedReaderextends Reader 因为输入的数据可能出现中文,所以此处使用字符流完成. System.in本身表示的是InputStream(字节流),现在要求接收的是字符流,需要将字节流

[Java IO]01_File类和RandomAccessFile类

File类 File类是java.io包中唯一对文件本身进行操作的类.它可以进行创建.删除文件等操作. File类常用操作 (1)创建文件 可以使用 createNewFille() 创建一个新文件. 注意: Windows 中使用反斜杠表示目录的分隔符"\". Linux 中使用正斜杠表示目录的分隔符"/". 最好的做法是使用 File.separator 静态常量,可以根据所在操作系统选取对应的分隔符. (2)删除文件 可以使用 delete() 删除一个文件.

【java】io流之字节输入流:java.io.InputStream类及子类java.io.FileInputStream

1 package 文件操作; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 8 public class TestInputStream { 9 public static void main(String[] args) throws IOException { 10 File file=new F

java Io 流类详解

关于java  流类的复习:习惯性的复习按照图结构一层层往下深入去了解去复习,最后通过代码来实现感觉印象会更深刻一些: 关于 I/O流:IO可以理解为JAVA用来传递数据的管道,创建一个IO,就相当于将管道与某个数据源连接到一起了. 字节流:数据流中最小的数据单元是字节.  字节:字节是一个八位的二进制数是一个很具体的存储空间: 字符流:数据流中最小的数据单元是字符:  字符:是一个抽象的符号,例如 1.2.3 .人  等   (并不是说直接传输字符,而是将字符按照指定的编码规则,转成对应的字节