IO笔记

一、文件

1.1 概念

文件是保存数据的地方

文件流:文件在程序中是以流的形式来操作的

image-20220722170802785

1.2 常用操作

创建文件对象相关构造器和方法:

new File(String pathName); // 根据路径创建一个File对象
new File(File parent, String child); // 根据父目录文件(文件的路径)+子路径创建
new File(String parent, String child); // 根据父目录+子路径创建

file.createNewFile();  // 创建新文件
// 方式一
public void create01() {
    String filePath = "e:\\new1.txt";
    File file = new File(filePath);

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

// 方式二
// e:\new1.txt
public void create02() {
    File parentFile = new File("e:\\");
    String fileName = "new2.txt";
    File file = new File(parentFile, fileName);
    try {
        file.createNewFile();
        System.out.println("文件创建成功!");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 方式三
public void create03() {
    String parentPath = "e:\\";
    String fileName = "new3.txt";
    File file = new File(parentPath, fileName);
    try {
        file.createNewFile();
        System.out.println("文件创建成功!");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

获取文件的相关信息

getName, getAbsolutePath, getParent, length, exists, isFile, isDirectory
// 获取文件的信息
public void info() {
    // 先创建文件对象
    File file = new File("e:\\new1.txt");

    // 调用响应的方法,得到信息
    String fileName = file.getName(); // 文件名
    String absolutePath = file.getAbsolutePath(); // 绝对路径
    String parent = file.getParent(); // 文件父级目录
    long length = file.length(); // 文件大小(字节)
    boolean exists = file.exists(); // 文件是否存在
    boolean file1 = file.isFile(); // 是不是一个文件
    boolean directory = file.isDirectory(); // 是不是一个目录
}

目录操作和文件删除

mkdir() // 创建一级目录
mkdirs // 创建多级目录
delete() // 删除空目录或文件
// 判断 e:\\new1.txt 文件是否存在,如果存在就删除
public void m1() {
    String filePath = "e:\\new1.txt";
    File file = new File(filePath);
    if (file.exists()) {
        if (file.delete()) {
            System.out.println("删除成功!");
        } else {
            System.out.println("删除失败!");
        }

    } else {
        System.out.println("该文件不存在!");
    }
}

// 判断 e:\\demo 目录是否存在,如果存在就删除
public void m2() {
    String filePath = "e:\\demo";
    File file = new File(filePath);
    if (file.exists()) {
        if (file.delete()) {
            System.out.println("删除成功!");
        } else {
            System.out.println("删除失败!");
        }

    } else {
        System.out.println("该目录不存在!");
    }
}

// 判断 e:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
public void m3() {
    String directoryPath = "e:\\demo\\a\\b\\c";
    File file = new File(directoryPath);
    if (file.exists()) {
        System.out.println("目录存在!");
    } else {
        if (file.mkdirs()) {
            System.out.println("创建成功!");
        } else {
            System.out.println("创建失败!");
        }
    }
}

二、IO流原理及流的分类

image-20220722182430155

image-20220722204844117

image-20220722205146906

image-20220722211212878

三、节点流和处理流

处理流就是对节点流的包装!!(装饰器模式)

image-20220723134839486

image-20220723135020437

image-20220723143522636

image-20220723223755161

// System 类的 public final static InputStream in = null
// System.in 编译类型:InputStream
// System.in 运行类型:BufferedInputStream
System.out.println(System.in.getClass());

// System 类的 public final static PrintStream out = null
// System.out 编译类型:PrintStream
// System.out 运行类型:PrintStream
System.out.println(System.out.getClass());

打印流只有输出流,没有输入流

image-20220724164611849

四、输入流

4.1 InputStream

image-20220722211548120

image-20220722211500606

4.1.1 FileInputStream

image-20220724222714038

/**
* 单个字节的读取,效率低
* -> 使用 read(byte[] b) 优化
*/
public void readFile01() {
    String filePath = "e:\\hello.txt";
    int readData = 0;
    FileInputStream fileInputStream = null;
    try {
        // 创建 FileInputStream 对象,用于读取文件
        fileInputStream = new FileInputStream(filePath);
        // 从该输入流读取一个字节数据
        // 如果返回 -1,表示读取完毕
        while ((readData = fileInputStream.read()) != -1) {
            System.out.print((char) readData); // 转成char显示
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭文件流,释放资源
        try {
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/**
* 使用 read(byte[] b) 优化
*/
public void readFile02() {
    String filePath = "e:\\hello.txt";
    // 字节数组
    byte[] buf = new byte[8]; // 一次读取8个字节
    int readLen = 0;
    FileInputStream fileInputStream = null;
    try {
        // 创建 FileInputStream 对象,用于读取文件
        fileInputStream = new FileInputStream(filePath);
        // 从该输入流读取一个字节数据
        // 如果返回 -1,表示读取完毕
        // 如果读取正常,返回实际读取的字节数
        while ((readLen = fileInputStream.read(buf)) != -1) {
            System.out.print(new String(buf, 0, readLen)); // 转成char显示
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭文件流,释放资源
        try {
            fileInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.1.2 BufferedInputStream

处理流

image-20220723171405414

处理流属性中,封装了Reader对象,即封装了一个节点流,可实现多种数据类型。该节点流可以是任意的,只要是Reader的子类就行。

image-20220723171651116

4.1.3 ObjectInputStream

序列化

image-20220723174947656

image-20220723191145136

public static void main(String[] args) throws IOException, ClassNotFoundException {
    // 指定反序列化的文件
    String filePath = "e:\\data.txt";

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));

    // 读取
    // 1.读取(反序列化)的顺序,需要和保存数据(序列化)的顺序一致
    System.out.println(ois.readInt());
    System.out.println(ois.readBoolean());
    System.out.println(ois.readChar());
    System.out.println(ois.readDouble());
    System.out.println(ois.readUTF());
    Object dog = ois.readObject();
    System.out.println(dog);

    // 关闭流
    ois.close();
}

image-20220723222819871

4.2 Reader

4.2.1 FileReader

image-20220722222656948

image-20220724222826414

4.2.2 BufferedReader

处理流

image-20220723141003492

关闭流时,只需关闭包装流即可,会自动关闭节点流,源码如下:

image-20220723165248501

public static void main(String[] args) throws Exception {

    String filePath = "e:\\a.java";
    // 创建bufferedReader
    BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
    // 读取
    String line;
    // 说明:
    // 1.bufferedReader.readLine(); 按行读取
    // 2.当返回空时,表示读取完毕
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }

    // 关闭流, 只需要关闭 bufferedReader就行了,底层会自动关闭节点流 
    bufferedReader.close();
}

4.2.3 InputStreamReader

转换流:将字节流 包装为 字符流

image-20220723224714443

字节流可以指定编码方式,因此可以先用字节流,然后转成字符流。

image-20220723225634973

image-20220723225827950

public static void main(String[] args) throws IOException {
    String filePath = "e:\\a.txt";
    // 1.FileInputStream 转成 InputStreamReader
    InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
    // 2.把 InputStreamReader 传入 BufferedReader
    BufferedReader br = new BufferedReader(isr);
    // 3.读取
    String s = br.readLine();
    System.out.println("读取内容:" + s);

    // 4.关闭外层流
    br.close();
}

五、输出流

5.1 OutputStream

5.1.1 FileOutputStream

image-20220724222636444

/**
* 使用 FileOutputStream 将数据写入文件中
* 如果文件不存在,则创建文件
*/
public void writeFile() {
    // 创建 FileOutputStream 对象
    String filePath = "e:\\a.txt";
    FileOutputStream fileOutputStream = null;

    try {
        // 得到输出流对象
        // 说明:
        // 1.new FileOutputStream(filePath) 当写入内容时,会覆盖原先内容
        // 2.new FileOutputStream(filePath, true) 追加文件内容
        fileOutputStream = new FileOutputStream(filePath);
        // 写入一个字节
        fileOutputStream.write('a');
        // 写入字符串
        String str = "hello,world";
        // str.getBytes() 可以把字符串===> 字节数组
        fileOutputStream.write(str.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.1.2 BufferedOutputStream

处理流

image-20220723171423265

image-20220723171755670

5.1.3 ObjectOutputStream

反序列化

image-20220723191240242

public static void main(String[] args) throws IOException {
    // 序列化后,保存的文件格式,不是指定的后缀格式,而是按照他的格式保存
    String filePath = "e:\\data.txt";

    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

    // 序列化数据到 e:\data.txt
    oos.write(100); // int -> Integer(实现了 Serializable)
    oos.writeBoolean(true); // boolean -> Boolean
    oos.writeChar('a'); // char -> Character
    oos.writeDouble(9.5); // double -> Double
    oos.writeUTF("孔祥宇"); // String
    // 保存一个dog对象
    oos.writeObject(new Dog("旺财", 10));

    oos.close();
    System.out.println("数据保存完毕(序列化形式)");
}

5.1.4 PrintStream

image-20220724165029340

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

    PrintStream out = System.out;
    // 在默认情况下,PrintStream 输出位置是 标准输出,即显示器
    /*
        public void print(String s) {
            if (s == null) {
                s = "null";
            }
            write(s);
        }
         */
    out.print("hello, john");
    // 因为 print底层使用的是 write(),所以我们可以直接调用 write进行输出
    out.write("孔祥宇, 你好".getBytes());

    out.close();

    // 我们可以去修改打印流的输出位置
    // 1.修改为到 e:\\a.txt
    // 2."hello, 孔祥宇" 就会输出到 e:\a.txt
    // 3.public static void setOut(PrintStream out) {
    //        checkIO();
    //        setOut0(out); // native方法,修改了out
    //    }
    System.setOut(new PrintStream("e:\\a.txt"));
    System.out.println("hello, 孔祥宇");
}

5.2 Writer

5.2.1 FileWriter

image-20220724222904903

image-20220722222812314

注意:如果最终没有关闭(close)或者刷新(flush),写入不到指定文件!

close() 和 flush() 方法中关键的写操作

image-20220722231556710

5.2.2 BufferedWriter

处理流

image-20220723142158630

public static void main(String[] args) throws IOException {
    String filePath = "e:\\ok.txt";
    // 创建BufferedWriter对象
    // new FileWriter(filePath, true)  追加方式写入
    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
    bufferedWriter.write("hello, 孔祥宇");
    // 插入一个换行
    bufferedWriter.newLine();

    // 关闭流
    bufferedWriter.close();
}

5.2.3 OutputStreamWriter

实现将OutputStream(字节流) 包装成 Writer(字符流)

image-20220723230538568

public static void main(String[] args) throws IOException {
    String filePath = "e:\\a.txt";
    String charset = "gbk";
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charset);
    osw.write("hi, 孔祥宇");

    // 关闭流
    osw.close();
    System.out.println("按照 " + charset + " 保存文件成功!");
}

5.2.4 PrintWriter

image-20220724165122145

PrintWriter 需要close() 或者 flush() 才能写入

public static void main(String[] args) throws IOException {
    // PrintWriter writer = new PrintWriter(System.out);
    PrintWriter writer = new PrintWriter(new FileWriter("e:\\a.txt"));
    writer.print("hello, world");

    // PrintWriter 需要close() 或者 flush() 才能写入
    writer.close();
}

六、Properties类

传统方法读取 xxx.properties 文件

public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
    String line = "";
    while ((line = br.readLine()) != null) {
        String[] split = line.split("=");
        System.out.println(split[0] + " 值是:" + split[1]);
    }
    br.close();
}

使用 Properties 类

image-20220724215358189

image-20220724215559928

public static void main(String[] args) throws IOException {
    // 使用Properties类读取mysql.properties文件

    // 1.创建Properties对象
    Properties properties = new Properties();
    // 2.加载指定配置文件
    properties.load(new FileReader("src\\mysql.properties"));
    // 3.把k-v显示到控制台
    properties.list(System.out);
    // 4.根据key 获取对应的值
    String user = properties.getProperty("user");
    String pwd = properties.getProperty("pwd");
    System.out.println("用户名=" + user);
    System.out.println("密码=" + pwd);
}

使用 Properties类添加key-value到新文件 mysql2.properties

public static void main(String[] args) throws IOException {
    // 使用Properties类 创建配置文件,修改配置文件
    Properties properties = new Properties();
    // 创建
    // 如果不存在key,就是创建,否则就是修改
    properties.setProperty("charset", "utf8");
    properties.setProperty("user", "汤姆"); // 中文保存的 unicode编码
    properties.setProperty("pwd", "abc111");

    // 将k-v存储到文件中
    properties.store(new FileOutputStream("src\\mysql2.properties"), null);
    System.out.println("保存配置文件成功!");
}

使用 Properties类完成对 mysql2.properties 的读取,并修改某个key-value

// 如果不存在key,就是创建,否则就是修改
properties.setProperty("charset", "utf8");

7、IO模型

I/O描述了计算机系统与外部设备之间的通信过程。

当应用程序发起I/O调用后,会经历两个步骤:

  1. 内核等待I/O设备准备数据
  2. 内核将数据从内核空间拷贝到用户空间

常见的I/O模型:

  • BIO:同步阻塞
  • NIO:同步非阻塞
  • AIO:异步非阻塞

BIO:应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

NIO:应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间,线程依然是阻塞的。一直进行 read 调用轮询数据十分耗费资源。出现IO多路复用模型。线程会首先发起 select 调用,查询数据是否准备好,等内核准备好数据,用户再发起 read 调用。

所以,non blocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有:

  • 等待数据准备就绪 (Waiting for the data to be ready) 「非阻塞」
  • 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process) 「阻塞」

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

因此对异步IO模型来说:

  • 等待数据准备就绪 (Waiting for the data to be ready) 「非阻塞」
  • 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process) 「非阻塞」

select、poll 和 epoll 区别

select:

  • 时间复杂度O(n),当有I/O事件时,无差别轮询所有流,确定具体哪个流

poll:

  • 时间复杂度O(n),依次查询每个文件对象的设备状态
  • 基于链表存储,因此没有最大连接数限制

epoll:

  • 时间复杂度O(1),不用轮询,基于事件,会通知我们哪个流发生了怎样的I/O事件通知我们
posted @ 2022-11-28 17:36  柯文先生  阅读(23)  评论(0)    收藏  举报