JavaSE——IO流

1. IO流概述 

  • File:表示系统中的文件或者文件夹的路径。
    • 注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。
  • IO流:读写文件/网络中的数据。
    • 读=input=将文件/网络中的数据,读进内存
    • 写=output=将内存中的数据,写进文件/网络
  • IO流的分类:
    • 按照流的方向
      • 输入流:读取
      • 输出流:写出
    • 按照操作文件类型
      • 字节流:图片,视频,音频,文本...所有类型文件
      • 字符流:纯文本文件(.txt, .md)

2. 字节输出流OutputStream

以字节为单位,一个字节一个字节输出。 OutputStream是一个接口,下面有很多个实现类:

  • FileOutputStream
  • BufferedOutputStream

2.1 基本语法

FileOutputStream: 操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。

import java.io.FileOutputStream;
import java.io.IOException;public class MyIODemo {public static void main(String[] args) throws IOException {// 1. 创建对象FileOutputStream fos = new FileOutputStream("src/com/gtc/data/a.txt");// 2. 写出数据fos.write(97);// 3. 释放资源fos.close();}
}
  • 1.创建对象
    • 参数是字符串表示的路径或者File对象
    • 如果文件不存在会创建一个新的文件,但是要保证父级路径存在
    • 如果文件存在,则会清空文件
  • 2.写数据
    • write方法的参数是整数,但是实际上写到本地文件是对应的ASCII字符
    • 97对应a,98对应b,57对应9,55对应7
  • 3.释放资源
    • 每次使用完流之后,都要释放资源
    • 如果不释放资源,别的程序就无法对该文件进行操作
    • 目的:解决资源的占用

2.2 字节输出流写出数据的三种方式

public class MyIODemo {public static void main(String[] args) throws IOException {MyIODemo demo = new MyIODemo();demo.test01();demo.test02();demo.test03();}public void test01() throws IOException {// 1. 创建对象OutputStream os = new FileOutputStream("src/com/gtc/data/a.txt");// 2. 一次写一个字节数据os.write(97);// 3. 释放资源os.close();}public void test02() throws IOException {// 1. 创建对象OutputStream os = new FileOutputStream("src/com/gtc/data/b.txt");// 2. 一次写一个字节数组数据byte[] bytes = {97,98,99,100};os.write(bytes);// 3. 释放资源os.close();}public void test03() throws IOException {// 1. 创建对象OutputStream os = new FileOutputStream("src/com/gtc/data/c.txt");// 2. 一次写一个字节数组的部分数据byte[] bytes = {97,98,99,100};os.write(bytes,1,2); // 从索引1开始写,长度为2// 3. 释放资源os.close();}
}

2.3 换行和续写

  • Windows的换行符: \r\n
  • Linux的换行符: \n
  • MacOS的换行符: \r
  • 注意:在Windows操作系统中,Java对回车进行了优化。虽然完整的是\r\n,但是我们写其中一个\r或者\n,Java也可以实现换行,因为Java在底层会补全。
  • 续写:如果想要续写,打开续写开关即可。开关位置在创建对象的第二个参数,默认为false,表示关闭续写。

第二个参数为true表示打开了续写开关,不会清空源文件。

3. 字节输入流InputStream

3.1 基本语法

public class MyIODemo {public static void main(String[] args) throws IOException {// 1. 创建对象InputStream is = new FileInputStream("src/com/gtc/data/c.txt");// is.read()一次读取一个字节, 读到末尾返回-1while (true) {int c = is.read();if (c == -1) {break;}System.out.print((char) c);}is.close();}
}
  • 创建输入流对象
    • 如果文件不存在,就直接报错

3.2 文件拷贝

通过InputStream读取文件字节到内存,通过OutputStream将字节流写到另一个文件夹。

 

public class MyIODemo {public static void main(String[] args) throws IOException {// 1. 创建对象InputStream in = new FileInputStream("movie/demo.mp4");OutputStream out = new FileOutputStream("movie/copy.mp4");// 2. 读一个字节,写一个字节int b;while ((b = in.read()) != -1) {out.write(b);}// 3. 释放资源out.close();in.close();}
}

3.3 文件拷贝的弊端 

一次读取一个字节,太慢了。因为IO很费时间。

 大文件拷贝:创建数组5M~10M。

5M = 1024 byte * 1024 * 5

public class MyIODemo {public static void main(String[] args) throws IOException {// 1. 创建对象InputStream in = new FileInputStream("movie/demo.mp4");OutputStream out = new FileOutputStream("movie/copy.mp4");// 2. 一次读取5*1024*1024字节(5MB)byte[] buf = new byte[ 5 * 1024 * 1024]; // 5MB的bufint len; // 返回值: 本次读取到了多少个字节数据while ((len = in.read(buf)) != -1) {out.write(buf, 0, len);}// 3. 释放资源out.close();in.close();}
}

4. 字符集

确定一个文本文件里面保存的什么内存,总结就两点:

  • 确定字符集(例如:Unicode字符集)
  • 确定编码解码规则(例如:utf-8编码解码规则)

Unicode字符集 = {'字符1': 码点1,'字符2': 码点2,.......} 

utf-8编码规则 = {'码点1': 编码1,'码点2': 编码2,.......} 

utf-8解码规则 = {编码1: 码点1',编码2:'码点2',.......} 

4.1 ASCII字符集

一个字节可以表示256 种不同的值,范围是从 0255。而ASCII表只定义了128个字符(从 0127),这意味着使用一个字节来存储ASCII字符时,实际上有一半的可能值(128255)并未被标准ASCII所使用。

ASCII表给英文国家用够用了。但是其他国家不够用。

计算机最小的存储单位是字节。ASCII编码的第一位是0

4.2 GBK字符集 

GBK=ASCII+其他

GB: 国标

K: 扩展

"ANSI 编码" 是微软 Windows 系统中的一个术语,并不是某个具体的字符集标准(如 ASCII 或 UTF-8),而是指:

当前操作系统区域设置(Locale)所使用的默认编码方式。

也就是说,它不是一个固定的编码,而是一个“变量”,取决于你操作系统的语言和地区设置。

我们使用的是简体中文Windows操作系统,在大陆,ANSI默认编码规则是GBK。

GBK完全兼容ASCII。

GBK的英文编码解码:

同ASCII编码解码规则(一个字节)

GBK的中文编码解码:

  • 一个汉字用两个字节存储
  • 高位字节二进制一定以1开头,转成十进制之后是一个负数

4.3 Unicode字符集

国际标准字符集,它将世界各种语言的每一个字符定义一个唯一的编码,以满足跨语言、跨平台文本信息转换。

  • UTF-8编码规则:用1-4个字节保存
  • UTF-16编码规则:用2-4个字节保存
  • UTF-32编码规则:固定用4个字节保存

Unicode是字符集,UTF-8/16/32是Unicode字符的编码解码方式。

UTF-8 = ASCII + 其他

UTF-8的英文编码解码:

一个字节表示,和ASCII编码解码一样。

UTF-8的中文编码解码:

4.4 为什么会有乱码?

4.5 如何不产生乱码?

  • 不要用字节流读取文本文件
  • 编码解码使用同一个码表,同一个编码方式

4.6 Java中编码和解码的方法

public class MyIODemo {public static void main(String[] args) throws IOException {// 1. 默认方式编码String str = "你好aaabbb";byte[] bytes = str.getBytes(); // IDEA默认使用utf-8编码(一个中文三个字节,一个字母一个字节)System.out.println(Arrays.toString(bytes));// 2. 指定方式编码String str2 = "你好aaabbb";byte[] bytes2 = str.getBytes("GBK"); // GBK一个中文2个字节,一个字母一个字节)System.out.println(Arrays.toString(bytes2));// 3. 默认解码String destr = new String(bytes);System.out.println(destr);// 4. 指定方式解码String destr2 = new String(bytes,"GBK");System.out.println(destr2);}
}

 4.7 码点

Unicode字符集某个字符的码点 不等于 utf-8编码后的数据

5. 字符输入流Reader

字符流=字节流+字符集

  • 输入流:一次读取一个字节,遇到中文时,一次读取多个字节。
  • 输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。

5.1 为什么Java中的char能表示utf-8编码的汉字?

磁盘中存储的是 UTF-8 编码的字节(3 字节),Java 在读取时会将它们解码为 Unicode 码点,并用 char(2 字节)来表示这个字符。

1. 磁盘存储:UTF-8 编码的字节

  • 文件中的内容是以某种编码格式(如 UTF-8)写入的。
  • 比如中文字符 '汉' 的 Unicode 码点是 U+6C49,在 UTF-8 中被编码为 3 个字节
    E6 B1 89 (十六进制)

2. 读取文件:加载 3 字节数据到内存

  • 使用 FileInputStream 或 Reader 读取文件时,系统会从磁盘读取原始字节。
  • 对于 InputStreamReader 或 FileReader,它会根据指定或默认的编码(如 UTF-8)对这些字节进行解码。

3. 解码:识别这是一个中文字符

  • 解码器(如 UTF-8 解码器)会分析这 3 个字节的结构,判断出它是一个完整的 UTF-8 字符。
  • 然后将其转换为对应的 Unicode 码点:U+6C49(十进制 27721)。

4. 内存存储:使用 char 表示该字符

  • Java 中的 char 是一个 16 位无符号整数,正好能容纳 0 ~ 65535 范围内的所有 Unicode 码点(即 BMP 平面)。
  • 所以 '汉' 的码点 U+6C49(十进制 27721)可以直接用一个 char 表示:
    char ch = '汉'; // 实际上存储的是 0x6C49(十进制 27721)
阶段存储形式占用空间描述
磁盘UTF-8 字节3 字节'汉' 在 UTF-8 中是 E6 B1 89
内存Unicode 码点2 字节用 char 表示,值为 0x6C49(27721)

5.2 基本语法

public class MyIODemo {public static void main(String[] args) throws IOException {Reader reader = new FileReader("src/com/gtc/data/d.txt");int ch; // int四个字节,能够表示Unicode字符集的所有码点while ((ch = reader.read()) != -1) {System.out.println((char) ch); // 根据码点,找字符}reader.close();}
}
  • read方法不是直接读取原二进制数据到内存,而是先读取再解码

5.3 字符输入流原理

本质:一次读取8192字节到内存,然后一个字符一个字符读取,避免频繁的磁盘IO。

  • 最初读进内存的是编码之后的字节数据(即文件在磁盘上存储的形式)
  • 然后这些字节被解码成字符数据(char),供程序使用。 

第一次调用fr.read(),并不是读一个字符到内存,而是读取8192字节数据到内存。

6. 字符输出流Writer

6.1 基本语法

FileWriter类的构造方法

FileWrite类的成员方法

public class MyIODemo {public static void main(String[] args) throws IOException {Writer writer = new FileWriter("src/com/gtc/data/test.txt", true);writer.write("你好hello");writer.close();}
}

6.2 字符流输出原理