由浅到深认识Java语言(36):I/O流

该文章Github地址:https://github.com/AntonyCheng/java-notes【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】

在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】& CSDN文章地址:https://blog.csdn.net/AntonyCheng/article/details/136555245),该模板集成了最常见的开发组件,同时基于修改配置文件实现组件的装载,除了这些,模板中还有非常丰富的整合示例,同时单体架构也非常适合SpringBoot框架入门,如果觉得有意义或者有帮助,欢迎Star & Issues & PR!

上一章:由浅到深认识Java语言(35):File类

44.I/O流(操作文件中的数据)

相关概念

I/O流:Input/Output;

I/O作用是将数据从一个设备中流入另一个设备中,比如从磁盘流向内存,从磁盘流向移动存储设备,从一台计算机流向另一台计算机;

I/O流中的数据都是字节,任何数据文件都由字节组成,字节是计算机中最小的存储单元;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

I/O流对象的分类

按照操作的文件类型分类

  • 文件类型文件 – 选择流对象字符流
    • 什么是文本文件:使用文本工具(记事本,notepad++,editplus等)打开这个文件能够直接“阅读”的文件;
  • 非文件类型文件 – 选择流对象字节流

按照数据流向分类

  • 输入流:Java 程序从其他地方读取数据;
  • 输出流:Java 程序中的数据写入到其他地方;

I/O流对象的分类归纳

  • 字节输出流:OutputStream 抽象超类
  • 字节输入流:InputStream 抽象超类
  • 字符输出流:Writer 抽象超类
  • 字符输出流:Reader 抽象超类

字节流

字节输出流

OutputStream 是所有字节输出流的超类,是一种全能的输出流,可以写入任何类型的文件;

写入字节的方法 write()

  • void write(int b) 写入单个字节,参数给到的是 int ,但是会被转型为 byte ,所以数据范围应该在 -128~127;
  • void write(byte[] b) 写入字节数组;
  • void write(byte[] b,int startIndex,int len) 写入数组的一部分,开始索引,写入的个数;常常用于文件的复制操作;
FileOutputStream 实现类

构造方法:

  • FileOutputStream(File file)
  • FileOutputStream(String file)
    • 创建字节输出流对象,绑定参数就是要写入的数据目的地;
    • 如果数据目的地不存在,那么就会创建这样一个数据目的地并且写入数据,如果数据目的地存在,那么就会覆盖原有的数据目的地中的数据;

JVM 很聪明,任何一个操作系统都具有 IO 设备,但是 JVM 没有,因为它是依靠操作系统来实现 IO 功能的,所以当 IO 对象使用完毕后要记得释放资源(对象名.close());

字节输出流写入文件的步骤:

  • 创建字节输出流对象,构造方法中需要绑定文件的路径;

  • 调用流对象的方法 write() 写入数据;

  • 释放资源;

    示例如下:

    package top.sharehome.Demo;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    public class Demo {
        public static void main(String[] args) throws IOException {
            File f = new File("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
            FileOutputStream fos = new FileOutputStream(f);
            byte[] arr = new byte[]{97,98,99,100,101,102};
            boolean exists = f.exists();
            if (exists){
                fos.write(49);
                fos.write(50);
                fos.write(51);
                fos.write(arr);
                fos.write(arr,0,3);
            }
            fos.close();
        }
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    还可以利用字符串中的 getBytes() 方法将字符串转换成 byte[];

    package top.sharehome.Demo;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    public class Demo {
        public static void main(String[] args) throws IOException {
            File f = new File("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
            FileOutputStream fos = new FileOutputStream(f);
            boolean exists = f.exists();
            if (exists){
                fos.write("你好 Java".getBytes());
            }
            fos.close();
        }
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

追加写入和换行:

  • 追加写入,即 FileOutputStream 构造方法的第二个参数写 true;

    package top.sharehome.Demo;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    public class Demo {
        public static void main(String[] args) throws IOException {
            FileOutputStream fosWrite = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
            FileOutputStream fosCover = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt",true);
            fosWrite.write(97);
            fosWrite.write(98);
            fosWrite.write(99);
            fosCover.write(100);
            fosCover.write(101);
            fosCover.write(102);
            fosCover.close();
            fosWrite.close();
        }
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 换行写入,使用 Windows 系统的换行符号 \r(换行符) \n(回车符)

    示例如下:

    package top.sharehome.Demo;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    public class Demo {
        public static void main(String[] args) throws IOException {
            FileOutputStream fos = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
            byte[] bytes = new byte[]{97,98,99,'\n','\r',100,101,102};
            fos.write(bytes);
            fos.close();
        }
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

字节输出流异常处理

其实以上的示例都存在不恰当异常处理的情况,由于 JVM 借用了 OS 的 IO 功能,所以 JVM 向上抛的异常处理方式并不是一个明智的选择,所以应该选择 try……catch……finally…… 的异常处理方式将其就地处理掉;

异常主要有两个:

  1. FileOutputStream 对象创建失败后的空指针异常;
  2. 以及 FileOutputStream 创建时的 IO 流异常;

示例如下:

package top.sharehome.Demo;

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

public class Demo {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        //这个 try 是为了处理IO流异常
        try {
            fos = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
            fos.write("字节输出流异常处理".getBytes());
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //这个 try 也是为了处理IO流异常
            try {
                //这个 if 是为了防止空指针异常输出
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

字节输入流

InputStream 是所有字节输入流的超类,是一种全能的输入流,可以读取任何类型的文件;

读取字节的方法 read()

  • int read() 读取单个字节,同一个对象的连续 read() 方法会接连读取内容,每次读取返回一个 int 类型值,如果读取到流的末尾就返回 -1,该方法不能正常读取中文,因为一个中文字符所占大小不止一个字节
  • int read(byte[] b) 读取字节数组,同一个对象的连续 read(byte[] b) 方法会接连读取内容并返回读取到的字节个数,同时 byte[] 数组中的会被新读取到的内容所覆盖,如果读取到流的末尾就返回 -1,通常我们要将 byte[] 数组设置成 1024 的整数倍
FileInputStream 实现类

构造方法:

  • FileInputStream(File file)
  • FileInputStream(String file)
    • 创建字节输入流对象,绑定参数就是要读取的数据源文件;
    • 字节流输入流和输出流的方法使用几乎一样;

方法示例如下:

  • int read() 读取单个字节,返回一个 int 类型值;

    (文件中内容是 abc)

    package top.sharehome.Demo;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    
    public class Demo {
        public static void main(String[] args) {
            FileInputStream fis = null;
            try {
                fis = new FileInputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
                int r = 0;
                while ((r = fis.read()) != -1) {
                    System.out.println("r= "+r);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • int read(byte[] b) 读取字节数组;

    (文件中内容是 abcdefghi)

    package top.sharehome.Demo;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    
    public class Demo {
        public static void main(String[] args) {
            FileInputStream fis = null;
            byte[] arr = new byte[1024];
            try {
                fis = new FileInputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
                int i;
                while ((i = fis.read(arr)) != -1) {
                    System.out.println("i = " + i);
                    System.out.println("new String(arr) = " + new String(arr));
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    由于原理是 byte[] 数组覆盖后输出,最后一次只读取到了一个 i 字母,所以仅仅只覆盖上一次所读取到的 g 的位置,而 h 的位置保持不变;

    为了不出现以上的情况,我们又要运用 String 类当中的重载方法 String(转换的数组,开始索引,转换的个数)

    package top.sharehome.Demo;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    
    public class Demo {
        public static void main(String[] args) {
            FileInputStream fis = null;
            byte[] arr = new byte[1024];
            try {
                fis = new FileInputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test.txt");
                int i;
                while ((i = fis.read(arr)) != -1) {
                    System.out.println("i = " + i);
                    System.out.println("new String(arr) = " + new String(arr, 0, i));
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    打印效果如下:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

文件复制

实现文件的复制功能,和操作系统中的 Ctrl+c,Ctrl+v 一样,原理上就是字节的流动;

我们可以用单个字节输入输出流完成它,假设从 test1.txt 提取内容 123test2.txt 中:

package top.sharehome.Demo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test1.txt");
            fos = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test2.txt");
            int flag = 0;
            while ((flag = fis.read()) != -1) {
                fos.write(flag);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是这种单个字节复制文件方法是不明智的,较小的文件可以这样,但是一旦文件内存较大,该方法的效率就显得极其低下;

举例如下:

以下是 test3.txt 文件内容,有20000+行内容,我们写程序来验证一下所花费时间;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

package top.sharehome.Demo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test3.txt");
            fos = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test2.txt");
            int flag = 0;
            while ((flag = fis.read()) != -1) {
                fos.write(flag);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("end-start = " + (end - start));
    }
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

足足用了 13 秒,效率极低!所以我们要考虑运用到字节数组去完成这一复制动作!

package top.sharehome.Demo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        byte[] arr = new byte[1024];
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test3.txt");
            fos = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test2.txt");
            int flag = 0;
            while ((flag = fis.read(arr)) != -1) {
                fos.write(arr,0,flag);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("end-start = " + (end - start));
    }
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

显然,速度要快很多,这样的数组也就是字节流缓冲流的原理;

字节流的缓冲流

使用字节流的缓冲流,提高原有流对象的读写性能;

字节流缓冲区流,流的本质上也是字节流;

  • BufferedOutputStream 继承自 OutputStream;
    • 方法 write() 写入单个的字节,或者是字节数组;
  • BufferedInputStream 继承自 InputStream;
    • 方法 read() 读取单个的字节,或者是字节数组;

Buffered 开头的流被称为缓冲流,FileOutputStream 基础流;这些类的构造器需要传入基础流,传入什么基础流,那就对什么基础流进行高效处理,如果没有基础流,那么这个类就无意义,这样的类设计也有一个特殊的设计模式名称——装饰者,关闭流时仅关闭装饰者即可;

BufferedOutputStream 构造方法

new BufferedOutputStream(OutputStream out) 传递字节输出流;

BufferedInputStream 构造方法

new BufferedInputStream(InputStream in) 传递字节输入流;

字节缓冲流实现文件复制
package top.sharehome.Demo;

import java.io.*;

public class Demo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        FileInputStream fis = null;
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            fis = new FileInputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test3.txt");
            fos = new FileOutputStream("d:\\大学课程学习文档\\java\\Practice\\IO\\test2.txt");
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            byte[] arr = new byte[1024];
            int flag = 0;
            while ((flag = bis.read(arr)) != -1) {
                bos.write(arr, 0, flag);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("(end-start) = " + (end - start));
    }
}

打印效果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再次相比普通数组的文件复制,这又是一次巨大的优化;

下一章:由浅到深认识Java语言(37):I/O流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值