Java基础之IO流详解及使用方式(建议收藏)

前言

首先感谢您的阅览,个人水平有限,不足之处多多包涵,也欢迎您的指正与补充,最后也希望本篇文章能为您解除疑惑


一.初识IO流

在这里插入图片描述
File类详细介绍跳转地址

注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。

而IO流可以进行数据读取,写入,传输

IO流:用于读写文件中的数据(可以读写文件,或网络中的数据…)


二.流的分类

在这里插入图片描述
注:word,excel不属于纯文本文件


IO流体系结构
在这里插入图片描述


三.基本字节流

一个字节一个字节读取,遇到中文,可能会乱码,中文在UTF-8编码格式中是占3个字节

3.1 FileOutputStream

字节输出流,把程序中的数据写到本地文件上

原理

建立连接

在这里插入图片描述


写入数据

在这里插入图片描述


关闭连接

在这里插入图片描述


字节输出流写出数据的细节

1.创建字节输出流对象

细节1:参数是字符串表示的路径或者File对象都是可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
细节3:如果文件已经存在,则会清空文件

2.写数据

细节: write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
比如上面的97,对应写入a.txt文件中就是字母a

3.释放资源

细节:每次使用完流之后都要释放资源,解除了资源的占用

写数据方式

FileOutputStream写数据的3种方式

在这里插入图片描述

第三个方法中,第一个参数是数组,第二个参数是起始索引,第三个参数是写入个数

示例:
在这里插入图片描述


续写:
如果想要续写,打开续写开关即可;开关位置:创建对象的第二个参数
默认false:表示关闭续写,此时创建对象会清空文件;手动传递true:表示打开续写,此时创建对象不会清空文件
换行符
再次写出一个换行符就可以了,各个操作系统表示换行符不一致
windows: \r\n
Linux : \n
Mac : \r

下面是续写和换行使用的代码

public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\hello.txt",true); //这第二个参数就是续写开关

        String str1 = "hello";
        byte[] bytes1 = str1.getBytes();
        fos.write(bytes1);

        String wrap = "\r\n";
        byte[] bytes = wrap.getBytes();
        fos.write(bytes);

        String str2 = "Java Girl";
        byte[] bytes2 = str2.getBytes();
        fos.write(bytes2);

        fos.close();
    }

FileOutputStream的作用

可以把程序中的数据写到本地文件上,是字节流的基本流。


3.2 FileInputStream

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

使用步骤及注意细节
1.创建字节输入流对象

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

2.读取数据

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

细节2:读到文件末尾了, read方法返回-1。read :表示读取数据,而且是读取一个数据就移动一次指针

3.释放资源

细节1∶每次使用完流必须要释放资源。

循环读取

    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\hello.txt");
        int b = 0;
        //read :表示读取数据,而且是读取一个数据就移动一次指针
        while ((b = fis.read()) != -1){//注意要用变量接收
            System.out.print((char) b);
        }
        fis.close();
    }

四.文件拷贝

边读边写,下面这种方式只适用于小文件拷贝

    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
        FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");

        //边度边写
        int b = 0;
        while (( b = fis.read() ) != -1){
            fos.write(b);
        }
        //释放资源注意:先开的最后关闭
        fos.close();
        fis.close();
    }

耗时如下
在这里插入图片描述


这种方式拷贝慢的原因

是因为FilelnputStream一次读写一个字节,拷贝文件有多少个字节,这里就要循环多少次
在这里插入图片描述


解决方法

一次读取多个数据

在这里插入图片描述
注意:一次读一个字节数组的数据,每次读取会尽可能把数组装满

    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
        FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");

        int len = 0;
        byte[] bytes = new byte[1024 * 1024];
        while ((len = fis.read(bytes)) != -1){
            //由于数组每次元素不会清空,只是在读时,把读到的元素覆盖到数组对应索引中,若没读满的情况,数组剩余位置元素还是老数据没清除
            fos.write(bytes, 0, len); //读多少,写多少
        }
        fos.close();
        fis.close();
    }

耗时如下
在这里插入图片描述

这个用数组拷贝比上面的单个字节拷贝快了近80多倍


五.字符集

ASCII字符集存储英文的情况,ASCII表中对应的最大数字时127,所以英文都可以刚好用一个字节就能表示出来
在这里插入图片描述

ASCII表中是没有汉字的,所以存储汉字,也需要有一个像ASCII表那样的存储规则,然后GBK和Unicode出手了。

汉字之所以是用两个字节(16位二进制)存储,是因为一个字节只能表示8位二进制,也就是256个汉字,明显不够存储所有汉字种类,所以规定用2个字节存储,也就是2的16次方, 转十进制为65535,6万个已经够表示全部汉字种类了。

GBK完全兼容ASCII字符集,为了和英文区别开

核心1:GBK中,一个英文字母一个字节,二进制第一位是0
核心2:GBK中,一个中文汉字两个字节,二进制第一位是1

在这里插入图片描述


Unicode字符集的UTF-8编码格式
一个英文占一个字节,二进制第一位是0,转成十进制是正数
一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数


六.乱码原因

原因1:读取数据时未读完整个汉字
以字节流读取为例,UTF-8中汉字占3个字节,字节流每次读取一个字节,当然会乱码

在这里插入图片描述


原因2:编码和解码时的方式不统一

在这里插入图片描述


上面既然说到了乱码原因,那么解决方法也随之而来,第二个原因就不说了,手动统一格式就完了,主要是原因一的,下面字符流就带着解决方法来了


七.基本字符流

一个字符一个字符读取,可以读取中文而不乱码

在这里插入图片描述

主要是对纯文本文件进行读写操作

在这里插入图片描述


7.1 FileReader

空参Read方法使用如下

    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("IO\\src\\main\\resources\\go.txt");

        int ch;
        while ((ch = fr.read()) != -1){
            System.out.print((char) ch);
        }
        fr.close();
    }

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

英文:文件里面二进制数据0110 0001
read方法进行读取,解码并转成十进制97

中文:文件里面的二进制数据11100110 10110001 100010日1
read方法进行读取,解码并转成十进制27721


字节输入流除了一个字节一个字节的,还可以通过数组读;当然,这个字符输入流也不输它,字符输入流也可以使用数组读,代码格式如下

        FileReader fr = new FileReader("IO\\src\\main\\resources\\go.txt");
        
        char[] chars = new char[2];//用数组的方式,每次读两个
        int len = 0;
        while ((len = fr.read(chars)) != -1){
            System.out.println(new String(chars, 0, len));
        }
        fr.close();

read带参方法底层实现
read(chars):读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中
相当于空参的read +强转类型转换


字符输入流底层原理解析

读取数据,会先放入缓冲区,缓冲区读满后还在继续读数据,就会再从头开始覆盖缓冲区前面的数据
注意:字节流没有缓冲区,字符流有缓冲区

在这里插入图片描述


7.2 FileWriter

构造方法

在这里插入图片描述

成员方法

在这里插入图片描述

各种细节规则和字节输出流的基本一致

在这里插入图片描述

成员方法使用范例
在这里插入图片描述


字符输出流底层原理解析

字符输出流,写出数据,是先写到缓冲区的,如下图

在这里插入图片描述

上面漏了一条,第三种情况,close关流时也会把缓冲区的数据保存到本地的

flush刷新:刷新之后,还可以继续往文件中写出数据——fw.flush()

close关流:断开通道,无法再往文件中写出数据——fw.close()

下面进行示例展示,毕竟实践才是检验真理的唯一标准

向文件写入数据

    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("IO\\src\\main\\resources\\go.txt");

        fw.write("敲你吗?");
        fw.close();
    }

debug断点可以看到,确实有缓冲区,大小8192个字节

在这里插入图片描述

接下来往下走一步
缓冲区写入了10个字节,上面写入了3个汉字,一个英文问号,在UTF-8编码方式中,一个汉字3个字节
在这里插入图片描述

此时,打开目的文件里面空空如也

在这里插入图片描述

再往下走一步,走到close方法,会发现目的文件数据写入了。


经典案例Demo

案例:拷贝文件夹及其子文件到目标文件夹中

 public static void main(String[] args) throws IOException {
        /*
        文件夹拷贝
         */
         //创建源文件对象和目标文件对象
        File src = new File("D:\\a");
        File dest = new File("D:\\b");

        copyDir(src,dest);
    }
    public static void copyDir(File src, File dest) throws IOException {
        dest.mkdirs();

        File[] files = src.listFiles();
        if (files == null || files.length == 0){
            System.out.println("没有访问权限");
        }
        for (File file : files) {
            if (file.isFile()){
                int len = 0;
                byte[] bytes = new byte[1024];
                FileInputStream fis = new FileInputStream(file);
                FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));

                while ((len = fis.read(bytes)) != -1){
                    fos.write(bytes, 0, len);
                }
                fos.close();
                fis.close();
            }else {
                copyDir(file, new File(dest, file.getName()));
            }
        }
    }

案例:文件加密与解密

    public static void JiaMi() throws IOException {
        FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
        FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");
        //加密处理,进行异或
        int b = 0;
        while((b = fis.read()) != -1){
            fos.write(b^2);
        }
        
        fos.close();
        fis.close();
    }

对文件中的数字进行排序

    public static void main(String[] args) throws IOException {
        //对test.txt中的数字排序 2-4-5-1-3
        //变成1-2-3-4-5

        FileReader fr = new FileReader("IO\\src\\main\\resources\\test.txt");
        StringBuffer sb = new StringBuffer();
        int ch;
        while ((ch = fr.read()) != -1){
            sb.append((char)ch);//用StringBuffer来接收读入的数据
        }
        fr.close();
        System.out.println(sb);
        //转为字符串
        String str = sb.toString();
        //进行切割,获取数字
        String[] strings = str.split("-");
        //存入list集合排序
        ArrayList<Integer> list = new ArrayList<>();
        if (strings == null || strings.length == 0){
            System.out.println("文件中没有数据");
        }
        for (String s : strings) {
            int i = Integer.parseInt(s);
            list.add(i);
        }
        Collections.sort(list);
        System.out.println(list);
        //把排序后的数据写出
        FileWriter fw = new FileWriter("IO\\src\\main\\resources\\test.txt");
        for (int i=0; i < list.size(); i++){
            if (i == list.size() - 1){
                fw.write(list.get(i) + "");
            }else {
                fw.write(list.get(i) + "-");
            }
        }
        fw.close();
    }

八.高级流

由于基本输入输出流的效率太低,所以基于基本流又出现了缓冲流,转换流,序列化流,打印流等等

8.1 缓冲流

在这里插入图片描述


8.1.1 字节缓冲流

在这里插入图片描述
在这里插入图片描述

真正读写数据的还是原来的基本流

下面是字节缓冲流的基本使用

利用字节缓冲流拷贝文件
在这里插入图片描述


字节缓冲流提高效率的底层原理

在这里插入图片描述


8.1.2 字符缓冲流

它也是底层自带了长度为8192的缓冲区提高性能,但是基本的字符流已经自带缓冲区了,所以这个字符缓冲流效率提升的不是很明显。

在这里插入图片描述

特有方法,读取一行的使用示例

    public static void main(String[] args) throws IOException {
        //创建字符缓冲流对象
        BufferedReader br = new BufferedReader(new FileReader("IO\\src\\main\\resources\\hello.txt"));
        //读取数据
        //readline方法在读取的时候,一次读一整行,遇到回车换行自动结束读取
        //但是它不会把回车换行读到内存当中
        String line;
        while ((line = br.readLine()) != null){
            System.out.println(line);
        }
        br.close();
    }

特有方法,跨平台换行

        //创建字符缓冲输出流对象,注意看续写true放的位置
        BufferedWriter bw = new BufferedWriter(new FileWriter("IO\\src\\main\\resources\\hello.txt",true));
        //写出数据
        bw.write("你歪嘴的样子,龙王自愧不如");
        //加一个所有系统通用的换行符
        bw.newLine();

        bw.write("如果我结婚了,你一定要来哦,没有新娘我会很尴尬");
        bw.newLine();
        //释放资源
        bw.close();

细节1:字符缓冲流的缓冲区大小为16k
细节2:输出流关联文件时,文件存在,若没有添加续写开关,内容即清空


8.2 转换流

作用:可以避免乱码,字节流也可以使用字符流中的特定方法

在这里插入图片描述

读取GBK文件

    public static void main(String[] args) throws IOException {
        //1.创建转换流对象,并指定字符编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream("GBK格式文件路径"), "GBK");//字节流转字符流

        //2.读取数据
        int ch;
        while ((ch = isr.read()) != -1){
            System.out.println((char) ch);
        }
        isr.close();
    }

案例
字节流使用字符流的独有方法,比如readLine()

利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
1.字节流在读取中文的时候,是会出现乱码的,但是字符流可以搞定
2.字节流里面是没有读一整行的方法的,只有字符缓冲流才能搞定

字节流——>字符流——>字符缓冲流
在这里插入图片描述


8.3 序列化流

序列化流是高级流的一种,它也是字节流

在这里插入图片描述


序列化流/对象操作输出流

可以把Java中的对象写到本地文件中,正常方式打开显示的是乱码
作用:就是把对象存储本地文件,避免其他人篡改数据

序列化流也是高级流,所以也需要关联基本流,让基本流干活,它只是包装

在这里插入图片描述

        //1.创建对象
        //2.创建序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("把对象写入哪个文件的路径"));
        //3.写出数据,对象放进去
        //oos.writeObject();
        //4.释放资源
        oos.close();

序列化流的小细节

使用对象输出流将对象保存到文件时会出现NotSerializableException异常
解决方案:需要让Javabean类实现Serializable接口


反序列化流/对象操作输入流

可以把序列化到本地文件中的对象,读取到程序中来

在这里插入图片描述

使用方式如下

在这里插入图片描述

读取的操作类似不再展示,先写再读,序列号要在对象属性创建完毕后,最后一步再生成序列号,因为序列号是根据类中所有内容计算出来的。

为了方便读取,一般先把所有对象都add到list集合中,写的时候,直接传list进去,读取的时候也可以用list接收读取的数据,增强for遍历出来


细节汇总

在这里插入图片描述


8.4 打印流

注意打印流不能读,只能写,所以只有字节,字符的输出流,为了方便记忆,就认为它是流中的唯一单身狗,其他都是成双成对。

在这里插入图片描述


8.4.1 字节打印流

字节打印流的构造方法

在这里插入图片描述


字节打印流成员方法

在这里插入图片描述


使用范例

    public static void main(String[] args) throws IOException {
        //1.创建字节打印流对象
        PrintStream ps = new PrintStream(new FileOutputStream("IO\\src\\main\\resources\\printlnTest.txt"),true,"UTF-8");
        //2.写出数据——>三个功能:写出+自动刷新+换行
        ps.println(97);
        ps.println(true);
        ps.printf("%s 爱上了 %s","阿珍","阿强");//%s是字符串占位符
        ps.close();
    }

8.4.2 字符打印流

字符打印流的构造方法

在这里插入图片描述


字符打印流成员方法

在这里插入图片描述

使用范例
在这里插入图片描述


8.5 解压缩流/压缩流

Java只能识别zip后缀的

在这里插入图片描述

解压本质就是把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地

ZipEntry表示当前在压缩包里的每一个文件或文件夹


解压缩代码如下

    public static void unzip(File src, File dest) throws IOException{
        //解压本质就是把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地
        
        //创建一个解压缩流用来读取压缩包中的数据
        ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
        //要先获取到压缩包里面的每一个zipentry对象
        
        //表示当前在压缩包中获取的文件或文件夹
        ZipEntry entry;
        while ((entry = zip.getNextEntry()) != null){
            System.out.println(entry);
            if (entry.isDirectory()){
                //文件夹,需要在目的地创建一个同样的文件夹
                File file = new File(dest, entry.toString());
                file.mkdirs();
            }else {
                //文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
                FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
                int b;
                while ((b = zip.read()) != -1){
                    //写到目的地
                    fos.write(b);
                }
                fos.close();
            }
        }
        zip.close();
    }

压缩代码

    public static void toZip(File src, File dest) throws IOException{
        //1.创建压缩流关联压缩包                                               						或者文件名.zip
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, src.getName().split("\\.")[0] + ".zip")));
        //2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
        ZipEntry entry = new ZipEntry("要压缩的文件");
        //3.把ZipEntry对象放到压缩包当中
        zos.putNextEntry(entry);
        //4.把src文件中的数据写到压缩包当中
        FileInputStream fis = new FileInputStream(src);
        int b;
        while ((b = fis.read() )!= -1){
            zos.write(b);
        }
        zos.closeEntry();
        zos.close();
    }

九.Commons-io工具包

简化代码编写,相关操作只需要直接调用工具包中的api接口。

常见方法
在这里插入图片描述


在这里插入图片描述


使用示范:
在这里插入图片描述


十.Hutool工具包

国内大牛开发的,解释都是中文,简明易懂。其中也有关于io和file的工具包,导包后可以直接调用它对应的api,简化开发。

官网:https://hutool.cn/

API文档:https://apidoc.gitee.com/dromara/hutool/

中文使用文档:https://hutool.cn/docs/#/


至此,IO流基础知识也写的差不多了,后续我有新的认知会继续补充这段笔记,感谢您的阅览

  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随身携带的笑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值