浅析java字符流、字节流

915 阅读5分钟

这是我参与更文挑战的第7天,活动详情查看: 更文挑战

书接上文,我们已经看了字节和字符的概念,没看的请先看浅析String字节和字符,然后我们再来看下字节流和字符流,首先我们得了解一个概念:一切计算机传输数据都是字节流形式的信息交互,而所谓的字符流只不过是编码处理字节后的结果;

既然字符流是字节流编码后的结果,那先看下字节流吧,一步一步的慢慢走;

首先把我下面将要测试的代码贴上来,怕到时候乱了**(代码只是为了方便阅读源码)**

InputStream fileInputStream = new FileInputStream("C:\\Users\\Desktop\\new 50.txt");
            BufferedInputStream bf = new BufferedInputStream(fileInputStream);

            InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
            char[] a =new char[1024];
            byte[] b = new byte[1024];
            int len;
            while((len = inputStreamReader.read(a))!=-1){
                System.out.println(new String(a,0,len));

            }
            while((len = fileInputStream.read(b))!=-1){
                System.out.println(new String(b,0,len));

            }

字节流InputStream

字节流InputStream在我们默认情况下read()方法读取文件流,由于我们的对象类型是FileInputStream,所以调用抽象类InputStream.read()时我们得去找FileInputStream里的read方法,它调用的是底层的本地方法(一个一个读取,因为下文有批量读取)

当我们给read()方法加上数组后它会调用

private native int readBytes(byte b[], int off, int len) throws IOException;

这样的话,就相当于字节流有了一个缓冲区,而这个缓冲区就是你的入参大小,当你的字节数组给的过小的时候,控制台恭喜你,打出来的数据部分是乱码的,所以我们能看到通常我们的Demo一般给1024个字节,因为你需要将所有的字节放在一起。如果不放一起,则字符只有一半字节就无法解析;(至于平时为什么你很少遇到乱码,我们稍后字符说)PS:(比如read方法不加参数读出来就全是乱码,为什么呢?因为汉字占两个字节,我们可以把它写出到文件,在这过程中给个休眠时间,看看不同时间下文件一会乱码一会正常的情况

字节缓冲流BufferedInputStream

我们看下BufferedInputStream初始化对象时对InputStream对象做了什么

InputStream fileInputStream = new FileInputStream("C:\\Users\\Desktop\\new 50.txt");
BufferedInputStream bf = new BufferedInputStream(fileInputStream);

它在BufferedInputStream.java里调用他的this构造函数,将字节流保存,并定义一个8192字节长度的数组

public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

这样你调用它的read()方法时它就可以去缓冲区去读了,如果你加了参数read(byte),他依然是重复执行read方法,直到参数的长度为止;

**注意:****new FileInputStream()**只是打开文件

相信这里大家已经看出两者之间的区别了,没用BufferedInputStream包裹的话则是直接读取磁盘文件(长度根据你下面的read参数来),如果包裹的话会一次可以取8192字节大小放入内存,然后从内存读,还有,不论你有没有用缓冲区,实际上只要你的read加参数了,你就用到了缓存;

综上,文件越大,缓冲效果越好;

字符流InputStreamReader

为啥会出现字符流这玩意,按理说字节流就够了啊,回答就是乱码问题,先看下字符流的编码流程,先读取字节流文件,然后进行转码操作,如果再存入磁盘,此时还需将字符转为字节,然后存入,前面已经说了,计算机里任何东西都是字节交互,这个流程我们也可以看出字符流适合文本文件,对于非文本文件如果使用字符流在字节转字符过程找不到的话,那么就是乱码,再转字节依然是一个未知数,就乱了;

InputStreamReader在初始化时会将InputStream数据用Object对象接收(BufferedInputStream缓冲字节流会将数据用InputStream对象接收),InputStreamReader默认的编码方式是UTF-8,或者你可以自己指定编码格式

InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_16);

 public static Charset defaultCharset() {
        if (defaultCharset == null) {
            synchronized (Charset.class) {
                String csn = AccessController.doPrivileged(
                    new GetPropertyAction("file.encoding"));
                Charset cs = lookup(csn);
                if (cs != null)
                    defaultCharset = cs;
                else
                    defaultCharset = forName("UTF-8");
            }
        }
        return defaultCharset;
    }

然后看下他执行无参的read()方法,

嘿,有没有那个味了,每次读两个字节来给char生成一个字符,可为什么char要给两个呢?每次不就读一个字符吗?,想不明白的去看我上篇文章去。。。

其实就是BMP外中文或者其他某些符号他们可能需要四个字节来表达也就需要两个char来;

然后执行数据操作,read(char[] a)其实就是一次读取a长度个字符。

字符缓冲流BufferedReader

上面说了那么多,其实这个也就类似了,一个object来接收inputStreamReader的字符流,

然后创建一个使用指定大小(8192)的输入缓冲区的缓冲字符输入流,然后read()方法读取单个字符,read(char[] a)读取a长度的字符,上面的弄懂了,这个我觉得就没啥说的了;

总结

总结一波,对于非文本文件,且不需对数据进行操作,只是读取的话,就用字节流,如果文件过大,就用字节缓冲流;对于文本文件需要操作数据的话就用字符流,如果文件过大就用字符缓冲流