Java IO流:字节流与字符流

好的,各位观众老爷们,欢迎来到“老司机带你飞:Java IO流的那些事儿”讲座现场!我是今天的主讲人,江湖人称“代码界的段子手”——老码农。今天咱们不搞那些高深莫测的理论,就用最接地气的方式,聊聊Java IO流这玩意儿。

开场白:IO流,程序与世界的桥梁

想象一下,你的程序就像一个住在深闺的小公举,而外面的世界,互联网也好,硬盘也好,就像一个充满诱惑的花花世界。小公举想出去浪,看看外面的风景,或者想把自己的美照分享到朋友圈,怎么办? 这时候,就需要一条通道,一座桥梁,连接程序和外部世界。

而IO流,就是这座桥梁,这条通道。它负责把数据从程序内部搬运到外部,或者把外部的数据搬运到程序内部。

第一幕:字节流,数据的原始搬运工

首先登场的是我们的老大哥——字节流。 它们是数据世界的原始搬运工,啥也不挑,啥都能搬,不管是图片、视频、文字、还是二进制文件,统统来者不拒。

  • 什么是字节?

    字节(byte)是计算机存储数据的基本单位,一个字节等于8个比特(bit)。你可以把它想象成一个最小的包裹,里面可以装8个开关,每个开关要么开(1),要么关(0)。

  • 字节流的种类

    字节流分为输入流(InputStream)和输出流(OutputStream)两大类。

    • InputStream(输入流): 负责从外部世界读取数据到程序内部。你可以把它想象成一个快递员,从快递站把包裹送到你家。

      常用的InputStream子类:

      • FileInputStream: 从文件中读取数据。
      • ByteArrayInputStream: 从字节数组中读取数据。
      • BufferedInputStream: 带缓冲的输入流,提高读取效率。
      • ObjectInputStream: 从输入流中读取对象。
    • OutputStream(输出流): 负责把程序内部的数据写入到外部世界。 你可以把它想象成一个邮递员,把你家的包裹寄到快递站。

      常用的OutputStream子类:

      • FileOutputStream: 将数据写入到文件。
      • ByteArrayOutputStream: 将数据写入到字节数组。
      • BufferedOutputStream: 带缓冲的输出流,提高写入效率。
      • ObjectOutputStream: 将对象写入到输出流。
  • 字节流的使用姿势

    使用字节流的一般步骤:

    1. 创建流对象: 根据你的需求,选择合适的InputStream或OutputStream子类,并指定数据源或目标。
    2. 读取/写入数据: 使用read()方法从输入流读取数据,或者使用write()方法将数据写入到输出流。
    3. 关闭流: 使用close()方法关闭流,释放资源。 这是一个非常重要的步骤,否则可能会导致资源泄露,甚至文件损坏!

    举个栗子:

    import java.io.*;
    
    public class ByteStreamDemo {
        public static void main(String[] args) {
            // 从文件读取数据
            try (FileInputStream fis = new FileInputStream("input.txt");
                 FileOutputStream fos = new FileOutputStream("output.txt")) { //try-with-resources 自动关闭流
                int data;
                while ((data = fis.read()) != -1) {
                    // 处理读取到的数据
                    System.out.print((char) data);
                    fos.write(data); //写入到文件
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    在这个例子中,我们使用FileInputStream从 "input.txt" 文件中读取数据,然后使用FileOutputStream将数据写入到 "output.txt" 文件中。 try-with-resources语句保证了流会被自动关闭,即使发生异常。

第二幕:字符流,文字的优雅使者

接下来登场的是我们的优雅使者——字符流。 它们专门负责处理字符数据,比如文本文件、网页等等。 字符流在字节流的基础上,增加了字符编码的支持,可以更好地处理各种语言的文字。

  • 什么是字符?

    字符(character)是人类可读的符号,比如字母、数字、汉字等等。 在计算机中,字符通常用Unicode编码表示。

  • 字符流的种类

    字符流也分为输入流(Reader)和输出流(Writer)两大类。

    • Reader(字符输入流): 负责从外部世界读取字符数据到程序内部。 可以把它想象成一个翻译官,把外文翻译成中文。

      常用的Reader子类:

      • FileReader: 从文件中读取字符数据。
      • BufferedReader: 带缓冲的字符输入流,提高读取效率。
      • InputStreamReader: 将字节输入流转换为字符输入流,可以指定字符编码。
    • Writer(字符输出流): 负责把程序内部的字符数据写入到外部世界。 可以把它想象成一个作家,把你的想法写成文字。

      常用的Writer子类:

      • FileWriter: 将字符数据写入到文件。
      • BufferedWriter: 带缓冲的字符输出流,提高写入效率。
      • OutputStreamWriter: 将字节输出流转换为字符输出流,可以指定字符编码。
      • PrintWriter: 具有自动flush功能的字符输出流,方便输出各种类型的数据。
  • 字符流的使用姿势

    使用字符流的一般步骤和字节流类似:

    1. 创建流对象: 根据你的需求,选择合适的Reader或Writer子类,并指定数据源或目标。
    2. 读取/写入数据: 使用read()方法从输入流读取字符数据,或者使用write()方法将数据写入到输出流。
    3. 关闭流: 使用close()方法关闭流,释放资源。

    举个栗子:

    import java.io.*;
    
    public class CharStreamDemo {
        public static void main(String[] args) {
            // 从文件读取字符数据
            try (BufferedReader br = new BufferedReader(new FileReader("input.txt"));
                 BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
    
                String line;
                while ((line = br.readLine()) != null) {
                    // 处理读取到的数据
                    System.out.println(line);
                    bw.write(line);
                    bw.newLine(); //写入一个换行符
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    在这个例子中,我们使用BufferedReader从 "input.txt" 文件中读取字符数据,然后使用BufferedWriter将数据写入到 "output.txt" 文件中。 BufferedReader的readLine()方法可以一次读取一行数据,BufferedWriter的newLine()方法可以写入一个换行符。

第三幕:字节流 vs 字符流,谁更胜一筹?

既然有了字节流,为什么还需要字符流呢? 它们之间到底有什么区别? 哪个更厉害?

咱们来做个对比:

特性 字节流 字符流
处理数据 字节(byte) 字符(character)
适用场景 所有类型的数据,包括二进制文件 文本文件、网页等字符数据
编码支持 不支持 支持,可以指定字符编码
效率 理论上更高(因为处理的数据更原始) 某些情况下可能更高(因为有缓冲和编码优化)
使用便捷性 相对复杂(需要自己处理字符编码) 相对简单(自动处理字符编码)

简单来说:

  • 如果你要处理的是二进制文件,比如图片、视频、压缩包等等,那就用字节流。
  • 如果你要处理的是文本文件,比如txt、html、java源代码等等,那就用字符流。
  • 如果你不确定要处理什么类型的文件,那就用字节流,因为它更通用。

第四幕:缓冲流,IO的加速器

无论是字节流还是字符流,每次读取/写入数据都需要和磁盘或网络进行交互,这个过程非常耗时。 为了提高IO效率,Java提供了缓冲流。

  • 什么是缓冲?

    缓冲(buffer)是一块内存区域,用来临时存储数据。 缓冲流在读取/写入数据时,会先将数据存储到缓冲区中,当缓冲区满了之后,才会一次性地将数据写入到磁盘或网络,或者从磁盘或网络读取一批数据到缓冲区。 这样可以减少IO操作的次数,从而提高IO效率。

  • 缓冲流的种类

    • BufferedInputStream: 带缓冲的字节输入流。
    • BufferedOutputStream: 带缓冲的字节输出流。
    • BufferedReader: 带缓冲的字符输入流。
    • BufferedWriter: 带缓冲的字符输出流。
  • 缓冲流的使用姿势

    使用缓冲流很简单,只需要在已有的流对象的基础上,套上一层缓冲流即可。

    举个栗子:

    import java.io.*;
    
    public class BufferedStreamDemo {
        public static void main(String[] args) {
            // 使用缓冲流从文件读取数据
            try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"));
                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
    
                byte[] buffer = new byte[1024]; // 缓冲区大小
                int bytesRead;
                while ((bytesRead = bis.read(buffer)) != -1) {
                    // 处理读取到的数据
                    bos.write(buffer, 0, bytesRead);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    在这个例子中,我们使用BufferedInputStream和BufferedOutputStream分别对FileInputStream和FileOutputStream进行了包装,从而提高了IO效率。

第五幕:转换流,编码的魔法师

有时候,我们需要在字节流和字符流之间进行转换。 比如,我们从网络上接收到的数据是字节流,但我们需要将其转换为字符流进行处理。 这时候,就需要用到转换流。

  • 转换流的种类

    • InputStreamReader: 将字节输入流转换为字符输入流,可以指定字符编码。
    • OutputStreamWriter: 将字节输出流转换为字符输出流,可以指定字符编码。
  • 转换流的使用姿势

    使用转换流也很简单,只需要将字节流对象作为参数传递给转换流的构造方法即可。

    举个栗子:

    import java.io.*;
    
    public class ConvertStreamDemo {
        public static void main(String[] args) {
            // 使用转换流从字节流读取数据,并指定字符编码
            try (FileInputStream fis = new FileInputStream("input.txt");
                 InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); // 指定UTF-8编码
                 BufferedReader br = new BufferedReader(isr)) {
    
                String line;
                while ((line = br.readLine()) != null) {
                    // 处理读取到的数据
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    在这个例子中,我们使用InputStreamReader将FileInputStream转换为字符输入流,并指定了UTF-8编码。 这样就可以正确地读取包含中文的文本文件了。

总结:IO流,Java的基石

IO流是Java编程中非常重要的一个概念,它连接了程序和外部世界,使得程序可以读取和写入各种类型的数据。 掌握IO流的使用,是成为一名合格的Java程序员的必备技能。

  • 字节流: 数据的原始搬运工,适用于所有类型的数据。
  • 字符流: 文字的优雅使者,适用于文本文件等字符数据。
  • 缓冲流: IO的加速器,可以提高IO效率。
  • 转换流: 编码的魔法师,可以在字节流和字符流之间进行转换。

希望今天的讲座能够帮助大家更好地理解Java IO流。 记住,学好IO流,走遍天下都不怕!

彩蛋:IO流的常见坑

  • 忘记关闭流: 这是一个非常常见的错误,会导致资源泄露,甚至文件损坏。 务必使用try-with-resources语句或者在finally块中关闭流。
  • 字符编码问题: 如果你的程序需要处理中文或其他非ASCII字符,一定要注意字符编码的问题。 确保你的程序使用的字符编码和文件使用的字符编码一致。
  • 缓冲区溢出: 如果你使用缓冲流,一定要注意缓冲区的大小。 如果缓冲区太小,可能会导致频繁的IO操作,降低效率。 如果缓冲区太大,可能会浪费内存。
  • 文件路径问题: 在指定文件路径时,一定要注意相对路径和绝对路径的区别。 相对路径是相对于当前工作目录的路径,绝对路径是文件的完整路径。

好了,今天的讲座就到这里,感谢各位观众老爷的捧场! 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注