JAVA基础之I/O流

JAVA基础之I/O流

一、什么是I/O流?

1、从计算机结构的角度来解读一下 I/O。

根据冯.诺依曼结构,计算机结构分为 5 大部分:运算器、控制器、存储器、输入设备、输出设备。

image-20220915153643089

IO 即 Input/Output,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。

2、字节流,字符流

Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
  • 由于以上4个都为抽象类,使用不能直接创建,使用时只能通过创建它的实现子类

image-20220915153626850

3、节点流和处理流

  • 节点流可以从特定数据源读取数据,如FileReader、FileWriter
  • 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

image-20220915153612851

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

个人认为主要有两点原因:

  • 字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时;
  • 如果我们不知道编码类型的话,使用字节流的过程中很容易出现乱码问题。

二、BIO、NIO和AIO的区别

从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。

UNIX 系统下, IO 模型一共有 5 种: 同步阻塞 I/O同步非阻塞 I/OI/O 多路复用信号驱动 I/O异步 I/O

1、BIO(同步阻塞io)

BIO 属于同步阻塞 IO 模型

同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

image-20220915153652781

在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

2、NIO(Non-blocking/New I/O)

Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , SelectorBuffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。

Java 中的 NIO 可以看作是 I/O 多路复用模型。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。

跟着我的思路往下看看,相信你会得到答案!

我们先来看看 同步非阻塞 IO 模型

image-20220915153707703

同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。

相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。

但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。

这个时候,I/O 多路复用模型 就上场了。

image-20220915153722905

IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。

目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,目前几乎在所有的操作系统上都有支持。

  • select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
  • epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。

IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。

Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

image-20220915153731114

3、AIO(异步 IO 模型)

AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。

异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

image-20220915153746294

目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。

最后,来一张图,简单总结一下 Java 中的 BIO、NIO、AIO。

image-20220915153754145

三、I/O流的使用(节点流)

1、文件专属:
java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)
java.io.FileReader
java.io.FileWriter
2、转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
3、缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
4、数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
5、标准输出流:
java.io.PrintWriter
java.io.PrintStream(掌握)
6、对象专属流:
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)
7、File文件类:
java.io.File

1、File类

File的构造方法

构造方法摘要
File(File parent, String child)
根据parent抽象路径名和child路径名字符串创建一个新File实例
File(String pathname)
通过通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
File(File parent, String child)
根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
File(URI uri)
通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。

image-20220915153808127

image-20220915153821495

image-20220915153839444

vimage-20220915153849472

    @Test
    public void FileCreate01(){
        String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\a.txt";
        File file = new File(filePath);

        try {
            file.createNewFile();
            System.out.println("文件创建成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void FileCreate02(){
        String parentPath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\";
        String childPath = "b.txt";
        File file = new File(parentPath, childPath);
        try {
            file.createNewFile();
            System.out.println("文件创建成功2");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

image-20220915153904007

2、字节流Inpustream/Outputsteam

image-20220915153911408
2.1、FileInputStream类

image-20220915153923668

    //    测试read方法
    @Test
    public void fist() throws IOException {
        String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\a.txt";
        File file = new File(filePath);

        FileInputStream fileInputStream = new FileInputStream(file);
        int readDate = 0;
        while ((readDate = fileInputStream.read())!= -1){
            System.out.print((char) readDate);
        }
        fileInputStream.close();
    }
    //    测试read(byte[])方法
    @Test
    public void fist_read() throws IOException {
        String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\a.txt";
        File file = new File(filePath);
        byte[] br = new byte[8];
        int len;
        FileInputStream fileInputStream = new FileInputStream(file);
        int readDate = 0;
    //    返回-1表示读取完毕,正常读取返回读取的个数
        while ((len = fileInputStream.read(br))!= -1){
            System.out.print(new String(br,0, len));
        }
        fileInputStream.close();
    }
2.2、FileOutputStream类

1、这种方式写入的是直接覆盖原来文件的内容

new FileOutputStream(file);

2、不覆盖原来的内容用

new FileOutputStream(file,ture);

image-20220915153938780

    @Test
    //字符输入
    public void fos1() throws IOException {
        String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\b.txt";
        File file = new File(filePath);
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write('h');//char->int  事实上传入的是int
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            fileOutputStream.close();
        }
    }

    @Test
    //数组输入
    public void fos2() throws IOException {
        String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\b.txt";
        File file = new File(filePath);
        FileOutputStream fileOutputStream = null;
        char[] b = {'a', 'b', 'c', 'd'};
        String a = "Is't fanny!";
        try {
            fileOutputStream = new FileOutputStream(file);
            //写入一个字节
//          fileOutputStream.write('h');//char->int  事实上传入的是int
            fileOutputStream.write(a.getBytes());//通过getBytes(),将String转为char数组
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            fileOutputStream.close();
        }
    }
2.3、利用字节流进行文件复制
    @Test
    public void testcopy() throws IOException {
        String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\becopy.jpg";
        String copyPath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\myphoto.jpg";
        File photo = new File(filePath);
        File copy = new File(copyPath);
//        利用字节数组提升效率
        byte[] br = new byte[1024];
        int len;

        FileOutputStream fileOutputStream = new FileOutputStream(copy);
        FileInputStream fileInputStream = new FileInputStream(photo);
        while ((len=fileInputStream.read(br))!=-1){
            fileOutputStream.write(br,0,len);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }

image-20220915153951391

3、字符流Reader/Writer

字符流操作的对象唯char或char[]

image-20220915154010755
3.1、FileReader

image-20220915154023478

FileReader相关方法:

  • new FileReader(File/String)
  • read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
  • read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1.

相关API:

  • new String(char[]): 将char[]转换成String
  • new String(char[],off,len): 将char[]指定的部分转换成String
3.2、FileWriter类
image-20220915154035256

image-20220915154046578

FileWriter常用方法

  • new FileWriter(File/String):覆盖模式,相当于流的指针在首端
  • new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
  • write(int):写入单个字符
  • write(char[]):写入指定数组
  • write(char[],off,len):写入指定数组的指定部分
  • write (string) :写入整个字符串
  • write(string,off,len):写入字符串的指定部分

相关APl:

String类: toCharArray:将String转换成char[]

注意:
FieWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!

3.3、字符流实现文件复制
    @Test
    public void cp() throws IOException {
        String path = "D:\\java\\savegit\\javaIOTest\\theTestBag\\teseReaderAndWriter.txt";
        File file = new File(path);
        File becp = new File("D:\\java\\savegit\\javaIOTest\\theTestBag\\copyReaderAndWriter.txt");
        char[] br = new char[8];
        int len;

        FileWriter fileWriter = new FileWriter(becp);
        FileReader fileReader = new FileReader(file);
        //返回-1表示读取完毕,正常读取返回读取的个数
        while ((len=fileReader.read(br))!=-1){
            fileWriter.write(br,0,len);
        }
        fileReader.close();
        fileWriter.close();

    }

image-20220915154155314

四、I/O流的使用(处理流)

image-20220915200427808

通过装饰器模式(底层实现有点像多态),得到属性的子类对象,进而实现封装。

image-20220915202532190

1、Bufferreader

    @Test
    public void BR() throws IOException {
        File file = new File("D:\\java\\savegit\\javaIOTest\\theTestBag\\copyReaderAndWriter.txt");
        BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
        String Line;
//        按行读取
        while ((Line=bufferedReader.readLine())!=null){
            System.out.println(Line);
        }
        bufferedReader.close();
    }

2、Buffer进行复制文件

//处理流进行复制粘贴
    @Test
    public void BW() throws IOException{
        File file = new File("D:\\java\\savegit\\javaIOTest\\theTestBag\\copyReaderAndWriter.txt");
        BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\java\\savegit\\javaIOTest\\theTestBag\\cpBW.txt"));

        String Line;
//        读取一行数据时没有带换行符,所以,写入时要插入一个换行符
        while ((Line=bufferedReader.readLine())!=null){
            bufferedWriter.write(Line);
            bufferedWriter.newLine();
        }
        bufferedReader.close();
        bufferedWriter.close();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值