IO操作——IO字节流相关类、字节流读写、缓冲区的使用和区别、文件资源泄露

概念

File类中虽然能进行一些常规的文件操作,但是少了两个非常核心的操作,读文件、写文件。
流 是Java中针对文件操作,又进行了进一步的抽象
流是一组类/一组API,不同的类会存在差异

流对象的核心操作

1.打开文件(构造方法)
2. read:从文件中把数据读到内存中.
3. write:把数据从内存中写入文件中.
4. close:关闭文件.(和打开相对应的),不关闭会造成文件资源泄露

流相关类

文本文件和二进制文件区分方式很简单:
拿一 个记事本打开这个文件,
里面的内容能看懂,就是文本文件.
在这里插入图片描述
里面的内容看不懂,就是二进制文件.
在这里插入图片描述
在这里插入图片描述
1、字节(byte)流
读写数据以字节为单位,处理二进制文件/数据使用字节流
在这里插入图片描述
InputStream:输入,从输入设备读取数据到内存中
OutputStream:输出,把内存中的数据写入到输出设备中
如果发现某个类的名字中带有InputStream / OutputStream字样,说明这就是字节流

2、字符(char)流
读写数据以字符为单位,处理文本文件/数据使用字符流
在这里插入图片描述
Reader:输入
Writer:输出
如果发现某个类的名字中带有Reader或Writer,说明这个类就是字符流

特例
InputStreamReader
OutputStreamWriter
名字里把两个部分都占用了,是字符流~(看一个单词后面的部分,最后一个部分是主体,前面的部分都是修饰的)

字节流读写

一个二进制文件,实现文件的拷贝(读取原来文件的内容,然后依次写入到目标文件中)
在这里插入图片描述
图片也是二进制文件
在这里插入图片描述
拷贝
在这里插入图片描述
在这里插入图片描述
一次读一个字节.
一次读若干个字节, 尝试把b这个数组填满
一次读若干个字节, 尝试填充b这个数组,从off下标开始填充,最多填充len个元素
在这里插入图片描述
一次写一个字节.
一次写若干个字节, 尝试把b这个数组填满
一次写若干个字节, 尝试填充b这个数组,从off下标开始填充,最多填充len个元素

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

public class IODemo {
    public static void main(String[] args) throws IOException {
        copyFile("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径

    }
    private static void copyFile(String srcPath,String destPath) throws IOException {//抛出异常,传入文件路径不存在
        //先打开文件,再进行读写(创建InputStream/OutputStream对象)
        FileInputStream fileInputStream = new FileInputStream(srcPath);//打开要拷贝的文件,,可以传入字符地址,或者File对象
        FileOutputStream fileOutputStream = new FileOutputStream(destPath);//要写入的文件

        byte[] bytes = new byte[1024];//缓冲区
        //单词读取的内容是有上限的(缓冲区的长度)
        //返回值表示本次读取实际读取的字节,如果文件读完返回-1
        int len = -1;
        while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
            //2、将读取到的内容写入destPath对应的文件中
            //读取,然后把读到的内容写入到另外一个文件中
            //因为len的值不一定和缓冲区长度相同
            fileOutputStream.write(bytes,0,len);//避免脏读
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}

在这里插入图片描述
在某些情况下可能close方法无法调用,因为中间存在IOException异常,一旦触发,此时方法就会被立刻终止,从而导致下面的close无法被调用

对代码进行优化
优化一

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

public class IODemo {
    public static void main(String[] args) throws IOException {
        copyFile("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径
        copyFile1("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径


    }
    private static void copyFile(String srcPath,String destPath) throws IOException {//抛出异常,传入文件路径不存在
        FileInputStream fileInputStream = null;//打开要拷贝的文件,,可以传入字符地址,或者File对象
        FileOutputStream fileOutputStream = null;//要写入的文件
        try {
            //先打开文件,再进行读写(创建InputStream/OutputStream对象)
            fileInputStream = new FileInputStream(srcPath);
            fileOutputStream = new FileOutputStream(destPath);

            byte[] bytes = new byte[1024];//缓冲区
            //单词读取的内容是有上限的(缓冲区的长度)
            //返回值表示本次读取实际读取的字节,如果文件读完返回-1
            int len = -1;
            while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
                //2、将读取到的内容写入destPath对应的文件中
                //读取,然后把读到的内容写入到另外一个文件中
                //因为len的值不一定和缓冲区长度相同
                fileOutputStream.write(bytes,0,len);//避免脏读
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileInputStream.close();
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
   public static void copyFile1(String srcPath,String destPath) throws IOException {
        try( FileInputStream fileInputStream = new FileInputStream(srcPath);//打开要拷贝的文件,,可以传入字符地址,或者File对象
             FileOutputStream fileOutputStream = new FileOutputStream(destPath)){//要写入的文件)
            byte[] bytes = new byte[1024];
            int len = -1;
            while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
                //2、将读取到的内容写入destPath对应的文件中
                //读取,然后把读到的内容写入到另外一个文件中
                //因为len的值不一定和缓冲区长度相同
                fileOutputStream.write(bytes,0,len);//避免脏读
            }

        }
        catch (IOException e) {
            e.printStackTrace();
        }
}
}

优化二

   public static void copyFile1(String srcPath,String destPath) throws IOException {
        //不需要显式调用close方法
       //try语句会在代码执行完毕后,自动调用close方法(前提是这个类必须要实现Closeable接口)
       //标准库中的流的方法都已经自动实现了Closeable接口
        try( FileInputStream fileInputStream = new FileInputStream(srcPath);//打开要拷贝的文件,,可以传入字符地址,或者File对象
             FileOutputStream fileOutputStream = new FileOutputStream(destPath)){//要写入的文件)
            byte[] bytes = new byte[1024];
            int len = -1;
            while ((len = fileInputStream.read(bytes)) != -1) {//1、读取srcPath文件内容
                //2、将读取到的内容写入destPath对应的文件中
                //读取,然后把读到的内容写入到另外一个文件中
                //因为len的值不一定和缓冲区长度相同
                fileOutputStream.write(bytes,0,len);//避免脏读
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
}
}

BufferedInputStream和BufferedOutputStream

这两个类内置缓冲区,提高程序的效率
程序访问内存比访问磁盘要快3-4个数量级
IO操作肯定涉及到磁盘访问,IO次数越多整体的程序效率就越低。

读写磁盘的时候每次读写一个字节,分100次读写,效率远远低于一次读写完100个字节。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import java.io.*;

public class IODemo2 {
    public static void main(String[] args) throws IOException {
        copyFile("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");//拷贝路径和目标路径

    }
    private static void copyFile(String srcPath,String destPath) throws IOException {
        //先创建File的实例
        //需要创建实例BufferedInputStream和 BufferedOutputStream方法内部特有了缓冲区对象
        FileInputStream fileInputStream = new FileInputStream(srcPath);
        FileOutputStream fileOutputStream = new FileOutputStream(destPath);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        byte[] buffer = new byte[1024];
        int length = -1;
        while ((length = bufferedInputStream.read(buffer))!= -1 ) {
            bufferedOutputStream.write(buffer,0,length);
        }
        //四个流对象关闭
        //调用bufferedInputStream.close时,会自动关闭内部的,FileInputStream和FileOutputStream
        bufferedInputStream.close();
        bufferedOutputStream.close();
    }

将close代码简化
代码中可以不使用close方法

 private static void copyFile2(String srcPath,String destPath) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(srcPath);
        FileOutputStream fileOutputStream = new FileOutputStream(destPath);
        try ( BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
              BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)){
                  int len = -1;
                  byte[] buffer = new byte[1024];
                  while(true){
                      len = bufferedInputStream.read(buffer);
                      if(len == -1){
                          break;
                      }else {
                          bufferedOutputStream.write(buffer,0,len);

                      }
                  }

              }catch (IOException e) {
            e.printStackTrace();
        }

    }
}

有无缓冲区的区别

不使用和使用缓冲区的区别,在比较大的文件会体现得比较明显
在这里插入图片描述
不设定缓冲区也不使用内置缓冲区


import java.io.*;

public class IODemo3 {
    public static void main(String[] args) throws IOException {

        //不使用和使用缓冲区的区别,在比较大的文件会体现得比较明显
        teatNoBUffer("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");
        teatBuffer("D:\\test_dir\\p1.JPEG","D:\\test_dir\\1\\p2.JPEG");
    }
    private static void teatNoBUffer(String srcPath,String destPath){
        //读的时候就是一个字节,一个字节读,不使用缓存区
        long beg = System.currentTimeMillis();
        try(FileInputStream fileInputStream = new FileInputStream(srcPath);
        FileOutputStream fileOutputStream = new FileOutputStream(destPath)){
            int ch = -1;
            while ((ch = fileInputStream.read())!= -1) {
            //啥都不干
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("no buffer"+(end - beg)+"ms");
    }
    private static void teatBuffer(String srcPath,String destPath) throws IOException {
        long beg = System.currentTimeMillis();
        try(BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(srcPath));
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destPath))){
            int ch = -1;
            while ((ch = bufferedInputStream.read())!= -1){


            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("have buffer"+(end - beg)+"ms");


    }
}

no buffer 1594ms
have buffer 15ms

所以有缓冲区的运行速度要快
BufferedOutputStream这里还有一个特殊操作
flush操作——手动刷新缓冲区(把数据从缓冲区写入磁盘/IO设备).

缓冲区刷新

缓冲区刷新一次,缓冲区中的东西就会写入到IO,之后缓冲区就会空白了。
1、缓冲区如果满了,就会自动刷新
2、调用close方法的时候,也会触发刷新
3、主动调用flush也会刷新

文件资源泄露

在这里插入图片描述
文件资源泄露关键在于,文件描述符表是有上限的,如果不关闭,就会反复打开,文件描述符表就会被一直打开,直到上限,后面再想打开新的就会打开失败。

一个进程的文件描述符表的上限数目(打开的最多的文件数目),是可配置的,在Linux中65535为上限
在这里插入图片描述
如果打开这么多就会上限,除非将前面不用的文件关闭。

此处的文件是广义的文件,包含普通文件,目录文件,也包含网卡这样的设备
对应的socket文件,每次有一个客户端和服务器建立连接的时候,系统中就会分配-一个 socket文件

关于扩容
好处能够更好的适应更多的场景
坏处:考虑到效率,考虑到线程安全
链式结构扩容方便,但是基本操作的效率不如顺序表(随机访问能力)
内核是一切应用程序的基础.对于效率要求是非常高的. (内核效率低,应用程序效率不可能高)顺序结构,扩容就很不方便

猜你喜欢

转载自blog.csdn.net/char_m/article/details/106835662