Java基础知识回顾:字节流与字符流

1.引入

File类虽然可以操作文件,但是不能操作文件内容,如果要进行文件内容的操作只有通过两种途径完成:字节流、字符流。

如果要进行输入、输出操作一般都会按照下面的步骤进行(以文件操作为例):

  1. 通过File类定义一个要操作文件的路径;
  2. 通过字节流或字符流的子类对象为父类对象实例化;
  3. 进行数据的读(输入)、写(输出)操作;
  4. 数据流属于资源操作,资源操作必须关闭。

在java.io包中定义了两类流:

  • 字节流:InputStream、OutputStream;
  • 字符流:Reader、Writer

2.字节流

2.1 字节输出流:OutputStream

OutputStream类是一个专门进行字节数据输出的一个类,它的定义:

public abstract class OutputStream extends Object implements Closeable, Flushable

OutputStream是jdk1.0就开始有的类,在jdk1.5和1.7之后对其定义进行了一些细微的改变,在jdk1.5的时候新定义了Closeable和Flushable两个接口,在jdk1.7之后定义了AutoCloseable接口,在这些新的接口中所作的工作都是对关闭流和清空流的操作,只需要关注其中的方法即可,即:close()和flush()

java在使用流时,都会有一个缓冲区,按一种它认为比较高效的方法来发数据:把要发的数据先放到缓冲区,缓冲区放满以后再一次性发过去,而不是分开一次一次地发. 而flush()表示强制将缓冲区中的数据发送出去,不必等到缓冲区满.

在OutputStream中提供有三个输出的方法:

  • 输出单个字节:public abstract void write(int b) throws IOException
  • 输出全部的字节数组:public void write(byte[] b) throws IOException
  • 输出部分字节数组:public void write(byte[] b,int off,int len) throws IOException

OutputStream是一个抽象类,要想获取它的对象只能从其子类获取,我们以文件操作为例,那么就可以使用它的直接子类FileOutputStream。这个子类中的构造方法:

  • 创建或覆盖已有文件:public FileOutputStream(File file) throws FileNotFoundException
  • 文件追加:public FileOutputStream(File file, boolean append) throws FileNotFoundException
package language;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

/**
 * @Author beifengtz
 * @Site www.beifengtz.com
 * @Date Created in 13:33 2019/1/23
 * @Description:
 */
public class StreamTest {
    public static void main(String[] args) throws Exception{

        // 定义一个输出文件路径,C:\Users\TZplay\desktop\Stream Test\test.txt
        File file = new File("C:"+File.separator+"Users"+File.separator+"TZplay"+File.separator+"desktop"+File.separator+"Stream Test"+File.separator+"test.txt");
        // 因为这个文件夹在桌面并不存在,所以需要对其文件夹进行创建
        if ( !file.getParentFile().exists()){   // 文件目录不存在
            file.getParentFile().mkdirs();  //创建目录
        }
        // 此时目录存在,但是文件不存在
        OutputStream output = new FileOutputStream(file);
        // 进行文件内容输出
        String str = "这是测试的内容";
        // 因为OutputStream不能接受字符串,只能以字节接收,所以需要将字符串转化为字节数组。
        byte data[] = str.getBytes();
        // 将内容输出
        output.write(data);
        // 强制清空缓存区
        output.flush();
        // 关闭输出流
        output.close();
    }
}

上面方法是使用字节数组进行输出,执行后在桌面上就会创建了一个“Stream Test”文件夹,在其下有一个“test.txt”文件,我们的内容已经成功的写入了该文件。如果要以单个字节的方式进行输出的话代码如下:

如果是输出部分内容:

如果是内容的追加代码如下:

只要是输出内容,都可以利用OutputStream类。

2.2 字节输入流:InputStream

如果程序要进行数据的读取操作,可以利用InputStream类进行实现,先看看它的定义:

在此类中主要提供有如下方法:

  • 跳过某长度(相当于移动开始读取的标志位):public long skip(long n) throws IOException
  • 读取单个字节:public abstract int read() throws IOException
    • 返回值:返回读取的字节内容,如果现在没有内容则返回-1。
  • 将读取的数据保存在字节数组里:public int read(byte[] b) throws IOException
    • 返回值:返回读取的数据长度,如果已经到结尾了返回-1。
  • 将读取的数据保存在部分字节数组里:public int read(byte[] b, int off, int len) throws IOException
    • 返回值:读取部分数据的长度,如果已经到结尾了返回-1。

InputStream是一个抽象类,同样需要其子类来获得本类对象,这里以文件测试,使用FileInputStream。

  • public FileInputStream(File file) throws FileNotFoundException

2.2.1 示例:向数组中读取数据:

最后我们成功读取到了文件的内容,但是有一个问题,我的test.txt中只有5个汉字换算下来也才10个字节,但是开辟的数组空间有1024,在获取字符串是有1014个字节的空间都是浪费的,下面是String类的构造方法。那么在这个时候我们应该优化一下代码

优化后的代码如下:

运行结果:

【读取内容开始】这是测试的内容【读取内容结束】
未优化转化的内容长度:1008
优化转化后的内容长度:8

2.2.2 示例:单个字节读取

由于一个文件有很多字节数据,如果要读取只能采用循环的方式,并且不确定循环次数,一般使用while循环,实现代码如下:

最后能正常读取文件内的内容。

3. 字符流

3.1字符输出流:Writer

Writer类是jdk1.1开始增加的,先看看它的类定义:

我们看到它相对于OutputStream类而言多了一个Appendable接口的实现,其他差不多一样。我们先来看看Appendable接口(jdk1.5开始)的定义:

在Appendable接口里面定义了追加的操作,并且在追加的数据都是字符或者是字符串

在Writer类里面定义的输出方法(部分):

  • 输出全部字符数组:public void write(char[] cbuf) throws IOException
  • 输出字符串:public void write(String str) throws IOException

Writer类是一个抽象类,需要用其子类来获取本类对象,这里同样用到文件输出操作,此时应使用FileWriter类,其构造方法和OutputStream类似,主要有两个构造方法:

  • public FileWriter(File file) throws IOException
  • public FileWriter(File file, boolean append) throws IOException

示例:

Writer作为字符输出流,可以直接进行字符串的输出,这一点上来说,功能比OutputStream强大一点,毕竟谁都想走捷径。

3.2 字符输入流:Reader

Reader是进行字符数据读取的输入流,其本身也是一个抽象类,先看一下它的定义:

其操作和InputStream非常类似,其中提供了很多read()的方法:

  • 一次性读取单个字符:public int read() throws IOException
    • 返回值:返回读取的字节内容,如果现在没有内容则返回-1。
  • 读取内容到字符数组:public int read(char[] cbuf) throws IOException
    • 返回值:表示读取的数据的长度,如果读取到结尾返回-1。
  • 读取部分内容到字符数组:public abstract int read(char[] cbuf, int off, int len) throws IOException
    • 读取部分数据的长度,如果已经到结尾了返回-1。
  • 检查是否准备好读取:public boolean ready() throws IOException
  • 重置流:public void reset() throws IOException 在其中发现在Reader类中没有能够返回字符串的方法,看似和Writer类不太一致,必将Writer类可以一次输出字符串。至于为什么呢?可以这样理解,如果我这里需要读取的文件是500G的,如果一次性读完,一般的计算机没有那么大的内存来支撑程序的,很明显会造成程序崩溃,而Writer类之所以可以一次性写入字符串,在写的内容肯定是程序已经允许的文件大小,如果太大程序就已经崩了,如果仍然可以写说明是支持将那么大的内容写入文件的。

这里同样以文件作为示例,使用FileReader类:

示例:

它与字节输入流想必结构几乎是一样的,只是数据类型由byte更换为了char而已。

4. 字节流与字符流的区别

字节流与字符流最大的区别是,字节流直接与终端进行数据的交互,而字符流需要将数据经过缓存区处理后才可以输出。

在使用OutputStream的时候最后即使最后没有调用close()来关闭输出流,内容也可以正常输出。如果使用的字符输出流Writer没有调用close()关闭输出流,那么就表示在缓存区之中处理的内容不会被强制性的清空,所以就不会输出数据。如果有特殊情况不能关闭字符输出流可以使用flush()方法强制清空缓存区。

在开发之中,对于处理字节数据的情况是比较多的,比如图片、视频、音乐、文字等数据。而对于字符流最大的好处是可以进行中文的有效处理。有时候字节流可能会出现中文乱码的情况,而字符流可以避免这样的操作。

如果要处理中文的时候优先考虑字符流,如果没有中文问题,建议使用字节流。

5. 转换流

实现字节流和字符流操作的转换。

字符流虽然需要缓冲区的处理,但是有一个特点,字符输出流可以直接输出字符串数据,所以有些时候才不得不进行字节流和字符流之间的转换。

在java.io包里面提供有两个类:InputStreamReader、OutputStreamWriter。这两个类的定义和构造方法:

  1. InputStreamReader

定义

public class InputStreamReader extends Reader

构造方法

public InputStreamReader(InputStream in)
  1. OutputStreamWriter

定义

public class OutputStreamWriter extends Writer

构造方法

public OutputStreamWriter(OutputStream out)

字节输出流转换为字符输出流示例:

文件保存在磁盘上,磁盘上能够保存的文件形式都是以字节的方式保存的,而在使用字符流读取的时候,实际上也是针对于字节数据进行读取,只不过转换过程是在缓冲区里面进行的。也就是说,任何数据归根结底都是字节数据,字符数据是字节数据经过处理后得到的新的数据。

原文发布于微信公众号 - 北风IT之路(beifengtz)

原文发表时间:2019-05-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券