网站Logo 苏叶的belog

IO流(基本流)

wdadwa
7
2025-12-07

一,IO流概述

IO 流:存储和读取数据的解决方法。用于读写文件中的数据(可以读写文件,或者网络中的数据)

IO 流的分类:

  1. 按照流的方向分为:

    输入流:读取本地文件中的数据。

    输出流:写入本地文件中的数据。

  2. 按照操作文件类型分为:

    字节流:可以操作所有类型的文件。

    字符流:只能操作纯文本文件。

2953321-20240522165745745-1088136384.png

二,IO流的体系

2953321-20230404202338501-1068156792.png

三,字节流

字节流使用场景:拷贝任意类型的文件。

2953321-20230404202340480-703620237.png

3.1 FileOutputStream

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

书写步骤:

  1. 创建字节输出流对象
  2. 写数据
  3. 释放资源
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        FileOutputStream out=new FileOutputStream("a.txt");//这个构造方法可以是File对象,也可也是path路径
        out.write(65);//写入数据
        out.close();//关闭输出传输通道
    }
}

FileOutputStream 第一步创建对象,就是把对应路径的文件和我们所创建的对象建立连接通道

第二部写入数据,就是通过这个数据传输通道把指定数据传输进去

第三步就是关闭这个数据传输通道

书写细节:

  • 创建字节输出流对象:

    细节 1:参数是字符串表示的路径,也可也是 File 对象表示的路径

    细节 2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。

    细节 3:如果文件已经存在,构造方法会情况文件内部数据。

  • 写数据

    细节 1:write 方法的参数是整数,单实际是这个字符在 ASCII 表上的字符

  • 释放资源

    每次使用完流后都要释放资源。

    用处就是解除资源占用。

FileOutputStream 写数据的 3 中方法

方法名说明
void write(int b)一次写一个字节数据
void write(byte[] b)一次写一个字节数组的数据
void write(byte[] b,int off,int len)一次写一个字节数组中从off开始的长度为len的数据

换行写

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        FileOutputStream out=new FileOutputStream("a.txt");
        String str1="hello world \r\n";
        String str2="hi world";
        out.write(str1.getBytes());
        out.write(str2.getBytes());
        out.close();
    }
}

不同的操作系统换行符号不一样:

windows:\r\n

Linux:\n

Mac:\r

但是,在 Windows 操作系统中,java 对回车换行进行了优化,虽然完整的是\r\n但是只需要写一个\r\n即可。

java 在底层会自动补全。

续写

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class IOtext {
    public static void main(String[] args)throws IOException {
        FileOutputStream out=new FileOutputStream("a.txt",true);//在创建对象的时候,把续写开关打开即可
        String str1="hello world \n";
        String str2="hi world";
        out.write(str1.getBytes());
        out.write(str2.getBytes());
        out.close();
    }
}

3.2 FileInputStream

作用:操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中。

和 FileOutputStream 一样,创建对象的时候可以传递 Stream 类型的 oath 地址,或者 File 类型的 path 地址

书写步骤:

  1. 创建字节输入流对象
  2. 读取数据
  3. 释放数据
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        FileInputStream f=new FileInputStream("a.txt");//建立连接通道
        int read = f.read();//读取数据
        System.out.println(read);
        f.close();//关闭连接通道
    }
}

FileInputStream 书写细节

  1. 创建字节输入流对象

    细节:如果文件不存在,则直接报错。

  2. 读取数据

    细节 1:一次读一个字节,读出来是数据在 ASCII 表上对应的数字

    细节 2:读到文件末尾了,read 方法返回 -1

    细节 3:read 表示读取数据,而且读取一个数据就移动一次指针。

  3. 释放资源

    细节:每次使用完流后都需要释放资源

FileInputStream 循环读取

import java.io.FileInputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        FileInputStream f=new FileInputStream("a.txt");
        int b;
        while( (b=f.read()) != -1){
            System.out.print((char)b);
        }
        f.close();
    }
}

3.3 案例:文件拷贝

思路:通过 FileInputStream 和 FileOutputStream 进行文件拷贝。

//1.小文件拷贝
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        //创建对象
        String path1="C:\\Users\\WDADWA\\Desktop\\learn_file\\java\\jdk1.8中文版.CHM";
        String path2="C:\\Users\\WDADWA\\Desktop\\learn_file\\jdk1.8中文版.CHM";
        FileInputStream in=new FileInputStream(path1);
        FileOutputStream out=new FileOutputStream(path2);
        //拷贝
        //核心思想:边读边写
        int b;
        while((b=in.read()) != -1){
            out.write(b);
        }
        //释放资源
        //规则:先开的流最后关闭
        out.close();
        in.close();
    }
}

文件拷贝速度很慢的原因:FileInputStream 一次只读取一个字节,当字节数量为 100 的时候就代表要循环 100 次.

解决方法:FileInputStream 一次性读取多个字节

方法名说明
public int read()一次读取一个字节数据
public int read(byte[] buffer)一次读取一个字节数组的数据
public int read(byte[] buffer,int off,int len)一次读取一个字节数字的数据中起始位置长度为len的数据

public int read(byte[] buffer) 的细节:

  1. 一次读取一个字节数组的数据,每次读取会尽可能把数组装满。

  2. 故数组越长读取越快,但是数组太长会导致内存存不下,导致崩溃。

  3. 一般创建数组会使用1024 的整数倍

  4. read 方法返回的是读取数据的长度,数组直接存储到了 buff 数组中

  5. 在 buff 数组中多次读取的时候,最新一次读取的数据会覆盖掉上一次读取的数据。

    假设读取abcde字符串,且 byte 数组长度为 2

    第一次:ab

    第二次:cd

    第三次:ed(采用覆盖读取,此时只读取到了 e,故覆盖了 0 索引)

    解决方法

    • 使用public int read(byte[] buffer,int off,int len)

    • public int read(byte[] buffer,0,len)这样即可解决第三次出现 d 的情况了

  6. 规定了 read 方法没有读取到数据的时候返回 -1

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        //创建对象
        String path1="C:\\Users\\WDADWA\\Desktop\\learn_file\\java\\java语言学习\\0正则表达式.md";
        String path2="C:\\Users\\WDADWA\\Desktop\\learn_file\\0正则表达式.md";
        FileInputStream in=new FileInputStream(path1);
        FileOutputStream out=new FileOutputStream(path2);
        //拷贝
        //核心思想:边读边写
        int data;
        byte[] buff=new byte[1024*1024*5];//创建了5MB数组
        while((data=in.read(buff)) != -1){
            out.write(data);
        }
        //释放资源
        //规则:先开的流最后关闭
        out.close();
        in.close();
    }
}

四,IO流中不同版本JDK捕获异常的方式

在以后编写代码的时候一般不会使用 throws 抛出异常,而是采用捕获异常的方式处理。

4.1 try...catch异常处理

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        //创建对象
        FileOutputStream fos=null;//这里使用外界定义是因为如果定义在try中那么这个是局部变量,则无法使用在finally中
        try{
            fos=new FileOutputStream("a.txt");
            fos.write(97);
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(fos != null){//这里使用if是因为可能fos找不到对应的文件,则会为null,这样会报错
                try {//这里使用try catch包裹是因为close方法也是存在异常的
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

上述代码过于复杂阅读性很低,故 java 在不同版本中给出了方法来简化。

java 推出了一个AutoCloseable接口

特点:凡是实现了这个接口的类,在特定情况下,可以自动释放资源。

4.2 JDK7方案

try(创建流对象1;创建流对象2;...;创建流对象n){
    可能出现异常的代码;
}catch(异常类名 变量名){
    异常的处理代码
}
//资源用完后会自动释放

上述代码简化为:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        
        try( FileOutputStream f= new  FileOutputStream("a.txt")){
            f.write(97);
        }catch(IOException e){
            e.printStackTrace();
        }
        
    }
}

4.3 JDK9方案

创建流对象1;
创建流对象2;
...
创建流对象n;
try(流1;流2;...;流n){
    可能出现异常的代码;
}catch(异常类名 变量名){
    异常的处理代码;
}
//资源用完后会自动释放

上述代码简化为

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IOtext {
    public static void main(String[] args)throws IOException {
        FileOutputStream f= new  FileOutputStream("a.txt");
        try(f){
            f.write(97);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

五,字符集

5.1 ASCII字符集

在 ASCII 编码表中记录了 128 条数据,这 128 条数据对于欧洲国际来说够用了。这玩意主要给欧美那边用的。

标准 ASCI 使用 1 个字节存储一个字符,首尾是 0,总共可表示 128 个字符。首位是 0 的原因是计算机最小存储单位 1 个字节。

2953321-20230404202342714-2129211065.png

5.2 GB2312字符集

GB2312(国家标准 2312)是中国于 1980 年发布的第一个简体中文字符集国家标准,全称是《信息交换用汉字编码字符集·基本集》。

GB2312 是中文信息化的里程碑,但作为历史编码,在现代开发中应:

  1. 了解其原理:区位码、分区结构、限制
  2. 处理兼容性:与GBK/GB18030/UTF-8的关系
  3. 制定迁移策略:向UTF-8过渡
  4. 使用正确工具:编码检测、转换、验证

5.3 BIG5字符集

BIG5(大五码)是繁体中文社区最常用的双字节字符集,于 1984 年由台湾的五大软件公司(宏碁、神通、佳佳、零壹、大众)共同制定,因此得名 "大五码"。

  • 优点:专为繁体设计、广泛支持、标准明确
  • 缺点:字符数有限、扩展混乱、与国际标准不统一
  • 现状正在被UTF-8全面取代

5.4 GBK字符集

2000 年 3 月 17 日发布,收录 21003 个汉字,包含国家标准 GB13000-1 中的全部中日韩汉字,和 BIG5 编码中的所有汉字。

windows 系统默认使用的就是 GBK,但系统显示:ANSI。因为每个操作系统使用的编码是不同的。

GBK 完全兼容 ASCII,故 GBK 存储英文和 ASCII 一样。

GBK 的规则:

  1. 汉字用两个字节存储,第一个字节叫高位字节,第二个字节叫低位字节。(因为汉字太多了一个字节不够放

  2. 高位字节二进制一定用1 开头,转成十进制后是一个负数(目的是为了和英文区分)、

总结:

  1. BK 中一个英文字母占一个字节,二进制第一位是 0.

  2. GBK 中一个汉字占两个字节,二进制第一位是 1

2953321-20230404202344838-747256968.png

2953321-20230404202353198-423272308.png

5.5 Unicode字符集

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

英文借助 Unicide 字符集存储方式:

Unicode 完全兼容 AscII 字符集,但是编码的时候不一样。常用的分为三种:

  • UTF-8 编码规则:用 1~4 个字节保存(最常用)

  • UTF-16 编码规则:用 2~4 个字节保存

  • UTF-32 编码规则:使用固定 4 个字节保存

2953321-20230404210543609-1330579065.png

UTF-8 编码规则规定:

  1. 如果是ASCII里面的英文字母,统一使用1个字节表示
  2. 如果是叙利亚,阿拉伯等文字,统一使用2个字节表示
  3. 如果是中日韩,中东,东南亚文字,统一使用3个字节表示
  4. 其他语言使用4个字节表示。

UTF-8 对于不同的字节,采取不同的编码规则

  • 1 个字节的首位必须为 0,其他位置为查询 Unicode 对应的二进制数。

  • 2 个字节的首位为 110,第二个字节首位必须为 10,其他位置为查询 Unicode 对应的二进制数。

  • 3 个字节的高位字节首位为 1110,其他字节首位为 10,其他位置为查询 Unicode 对应的二进制数。

  • 4 个字节的高位字节首位为 11110,其他字节首位为 10,其他位置为查询 Unicode 对应的二进制数。

2953321-20230404210551964-834778545.png

故对于英文:

2953321-20230404210558511-231421331.png

对于中文:

2953321-20230404210605446-1290622478.png

红色的是 UTF-8 编码规定的,黑色的由对应编码补齐。

5.6 乱码

乱码出现原因:

  1. 读取数据时没有读完整个汉字。
  2. 编码和解码方式不统一。

防止乱码产生:

  1. 不要用字节流去读取文本文件。
  2. 编码和界面时使用同一个码表,同一个编码方式。

5.7 java中编码和解码的代码实现

  1. java 中编码的方法

    String类中的方法说明
    public byte[] getBytes()使用默认方式进行编码
    public byte[] getBytes(String charsetName)使用指定方式进行编码

    idea 默认使用 Unicode 中的 UTF-8 进行编码

    Eclipse 默认使用 GBK 进行编码,charsetName 是编码的名字,如 UTF-16

  2. java 中解码的方法

    String类中的方法说明
    String(byte[] bytes)使用默认方式进行解码
    String(byte[] bytes,String charsetName)使用指定方式进行解码
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class Text {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //1.编码
        String str="你好a";
        byte[] bytes1 = str.getBytes();
        byte[] bytes2 = str.getBytes("GBK");
        System.out.println(Arrays.toString(bytes1));
        System.out.println(Arrays.toString(bytes2));
        //2. 解码
        String str1=new String(bytes1);
        String str2=new String(bytes2,"GBK");
        System.out.println(str1);
        System.out.println(str2);
    }
}

六,字符流

字符流使用场景:操作文本文件。

  • 乱码产生的原因之一是:读取数据时未读取整个汉字。

  • java 推出了字符流,当读取字母时,一个字节一个字节读取,当读取汉字时,根据对应的编码所决定一次读 2 个字节还是 3 个字节。

字符流的底层就是字节流。

字符类 = 字节流 + 字符集

特点:

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

字符流使用场景:对于纯文本进行读写操作。

字符流继承体系结构

2953321-20230405194408638-2128335748.png

6.1 FileReader

创建方法和使用方法都是和字节流一样的。

使用步骤

  1. 创建字符流输入对象

    构造方法说明
    public FileReader(File file)创建字符输入流关联本地文件
    public FileReader(String pathName)创建字符输入流关联本地文件
    public FileReader(String pathName,Charset charsetName)创建字符输入流关联本地文件并且使用指定字符编码

    第三个 Charset 是一个工具类。(JDK11 新特性)

    new FileReader("a.txt",Charset.forName("GBK"));//这样来指定编码读取数据
    

    如果读取文件不存在,报错。

  2. 读取数据

    成员方法说明
    public int read()每次读取1字节数据,读到末尾时返回-1
    public int read(char[] buffer)每次读取多个数据到buff数组中,读到末尾时返回-1
    public int read(char[] buffer,int off,int len)每次读取多个数据到buff数组中len长度,读到末尾时返回-1

    空参 read 细节:

    1. 默认也是一个字节一个字节的读取,如果遇到中文就一次读取多个字节。
    2. 在读取之后,方法底层还会进行解码并转成十进制。最终把这个十进制作为返回值,这个十进制也表示在字符集上对应得数字。

    有参的 read 细节:

    • 读取数据,解码,强转三步合并了,把强转之后的字符放入char数组中了。
  3. 释放资源

    方法名说明
    public int close()关闭流

6.2 FileWrite

创建方法和使用方法都是和字节流一样的。

使用步骤

  1. 创建字符流输入对象

    构造方法说明
    public FileWriter(File file)创建字符输出流关联本地文件
    public FileWriter(String pathName)创建字符输出流关联本地文件
    public FileWriter(File file,boolean append)创建字符输出流关联本地文件,续写
    public FileWriter(String pathName,boolean append)创建字符输出流关联本地文件,续写
    public FileWriter(String pathName,Charset charsetName)创建字符输出流关联本地文件,续写+使用指定编码
    public FileWriter(String pathName,Charset charsetName,boolean append)创建字符输出流关联本地文件,续写+使用指定编码

    细节 1:参数是字符串表示的路径还是 File 对象都是可以的

    细节 2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的

    细节 3:如果文件已经存在,则会清空文件,如果不想情况就在 append 中写入 true 打开续写开关。

  2. 读取数据

    成员方法说明
    void write(int c)写出一个字符
    void write(String str)写出一个字符串
    void write(String str,int off,int len)写出一个字符串的一部分
    void write(char[] cbuf)写出一个字符数组
    void write(char cbuf,int off,int len)写出一个字符数组的一部分

    如果 write 方法的参数是整数,实际上写到本地文件的是整数在字符集上对应的字符。

  3. 释放资源

    方法名说明
    public int close()关闭流

6.3 字符流原理解析

输入流:

  1. 创建字符输入流对象

    底层:关联文件,并创建缓冲区 (长度为 8192 的字节数组)

  2. 读数据

    底层:

    1. 判断缓冲区中是否有数据可以读取

    2. 缓存区中没有数据:

      • 就从文件中获取数据,装到缓存区中,每次尽可能的装满缓冲区。
      • 如果文件中也没有数据了,就返回-1
    3. 缓存区中有数据:就冲缓存区中读取数据。

      空参的 read 方法,一次读取一个字节,遇到中文一次读取多个字节,把字节解码为十进制返回。

      有参的 read 方法:把读取字节,解码,强转三部合并了,强转之后的字符放到数组中。

FileWriter 的关联会删除文件,但是 reader 同时对这个文件进行读取的时候可以读取到缓冲区中的数据

(必须先 Reader,再关联 writer)

输出流:

  1. 创建字符输出流对象

    底层:关联文件,并创建缓冲区 (长度为 8192 的字节数组)

  2. 写数据

    底层:把所有数据按照 utf-8 进行编码,并先写入缓冲区中。

    三个情况把缓冲区数据写入文件中。

    • 当缓冲区装满了
    • 手动刷新(使用flush方法)
    • 释放资源时

    fluse 和 clone 的区别:flush 刷新之后还可继续往文件中写数据。

动物装饰