字节流与字符流

/ Java SE / 0 条评论 / 437浏览

引入

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

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

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

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

字节流

字节输出流: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中提供有三个输出的方法:

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

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”文件,我们的内容已经成功的写入了该文件。如果要以单个字节的方式进行输出的话代码如下:

for (int i=0;i<data.length;i++){
    // 将内容输出
    output.write(data);
}

如果是输出部分内容:

output.write(data,0,15);    // 0为开始点,15为长度,一个中文占两个字节

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

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,true);
        // 进行文件内容输出,如果要换行需要用‘\r\n’
        String str = "这是测试的内容\r\n";
        // 因为OutputStream不能接受字符串,只能以字节接收,所以需要将字符串转化为字节数组。
        byte data[] = str.getBytes();
        // 将内容输出
        output.write(data);
        // 强制清空缓存区
        output.flush();
        // 关闭输出流
        output.close();
    }
}

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

字节输入流:InputStream

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

public abstract class InputStream extends Object implements Closeable

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

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

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

package language;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

/**
 * @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.exists()){ // 文件存在
            // 使用InputStream读取
            InputStream input = new FileInputStream(file);
            // 准备出一个1024的数组(相当于容器)
            byte data[] = new byte[1024];
            //  将内容保存到字节数组之中
            input.read(data);
            //  关闭输入流
            input.close();
            System.out.println("【读取内容开始】"+new String(data)+"【读取内容结束】");
        }else {
            throw new FileNotFoundException("文件不存在!");
        }
    }
}

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

public String(byte bytes[]) {
    this(bytes, 0, bytes.length);
}

优化后的代码如下:

package language;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

/**
 * @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.exists()){ // 文件存在
            // 使用InputStream读取
            InputStream input = new FileInputStream(file);
            // 准备出一个1024的数组(相当于容器)
            byte data[] = new byte[1024];
            //  将内容保存到字节数组之中,并且将读取到的内容长度存储到length中
            int length = input.read(data);
            //  关闭输入流
            input.close();
            System.out.println("【读取内容开始】"+new String(data,0,length)+"【读取内容结束】");
            System.out.println("未优化转化的内容长度:"+new String(data).length());    // 将byte数组内所有内容转化为字符串
            System.out.println("优化转化后的内容长度:"+new String(data, 0, length).length()); // 仅将byte数组内可用内容转化为字符串
        }else {
            throw new FileNotFoundException("文件不存在!");
        }
    }
}

运行结果:

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

示例:单个字节读取

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

package language;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

/**
 * @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.exists()){ // 文件存在
            // 使用InputStream读取
            InputStream input = new FileInputStream(file);
            // 准备出一个1024的数组(相当于容器)
            byte data[] = new byte[1024];
            //  设定byte脚标
            int foot = 0;
            //  每次接收到的字节数组
            int temp = 0;
            while((temp = input.read()) != -1){
                data[foot ++] = (byte) temp;
            }
            //  关闭输入流
            input.close();
            System.out.println("【读取内容开始】"+new String(data,0,foot)+"【读取内容结束】");
        }else {
            throw new FileNotFoundException("文件不存在!");
        }
    }
}

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

字符流

字符输出流:Writer

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

public abstract class Writer extends Object implements Appendable, Closeable, Flushable

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

public interface Appendable {
    Appendable append(CharSequence csq) throws IOException;
    Appendable append(CharSequence csq, int start, int end) throws IOException;
    Appendable append(char c) throws IOException;
}

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

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

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

示例:

package language;

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

/**
 * @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();
        }
        Writer writer = new FileWriter(file);
        String str = "这是通过Writer输入的字符串";
        writer.write(str);
        writer.flush();
        writer.close();
    }
}

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

字符输入流:Reader

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

public abstract class Reader extends Object implements Readable, Closeable

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

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

示例:

package language;

import java.io.*;

/**
 * @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.exists()){
            Reader reader = new FileReader(file);
            char data[] = new char[1024];
            int len = -1;
            if (reader.ready()){    // 等待输入流准备完毕
                 len = reader.read(data);
                reader.close();
            }
            System.out.println(new String(data,0,len));
        }else{
            throw new FileNotFoundException("文件未找到!");
        }

    }
}

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

字节流与字符流的区别

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

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

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

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

转换流

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

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

在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)

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

package language;

import java.io.*;

/**
 * @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{
        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);       // 字节流
        // 将OutputStream类对象传递给OutputStreamWriter类对象的构造方法,而后向上转型为Writer
        Writer writer = new OutputStreamWriter(output);         // 将字节流转换为字符流
        writer.write("Hello World!");
        output.flush();
        output.close();
    }
}

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