全栈开发那些事

全栈开发那些事

IO流

72
2024-06-25

1、File类

java.io包下有一个File类,File就是文件或文件夹。API中File的解释是文件和目录路径名的抽象表示形式,即通过指定路径名称来表示磁盘或网络中的某个文件或目录。也就是说,程序中的文件和目录都可以通过File类的对象来完成,如新建、删除、重命名文件和目录等。

另外,程序不能直接通过File对象读取内容或写入数据,如果要操作数据,则必须通过IO流。

1.1 获取文件或目录信息

File类常用方法一如下:

序号方法名描述
1String getName()返回此File对象所表示的文件或目录名(返回最后一级)
2String getPath()返回此File对象所对应的路径名
3String getAbsolutePath()返回此File对象所对应的绝对路径名
4String getCanonicalPath()返回此File对象对应的规范路径名
5String getParent()返回此File对象的父目录名
6File getParentFile()返回此File对象的父目录名所对应的File对象

用File对象表示文件,并获取文件的相关路径信息:

import java.io.File;
import java.io.IOException;

public class FileTest1 {
    public static void main(String[] args) throws IOException {
        File file = new File("../chap12ConnectionAndMap.ListAddTest");
        System.out.println("user.dir="+System.getProperty("user.dir"));
        System.out.println("文件/目录的名称:"+file.getName());
        System.out.println("文件/目录的路径名:"+file.getPath());
        System.out.println("文件/目录的绝对路径名:"+file.getAbsolutePath());
        System.out.println("文件/目录的规范路径名:"+file.getCanonicalPath());
        System.out.println("文件/目录的父目录名:"+file.getParent());
        System.out.println("文件/目录的父目录对象:"+file.getParentFile());
    }
}

image-20220926205221367

File类的常用方法二:

序号方法名描述
1boolean exists()判断File对象对应的文件或目录是否存在
2boolean canRead()判断File对象对应的文件或目录是否可读
3boolean canWrite()判断File对象对应的文件或目录是否可写
4boolean isHidden()判断File对象对应的文件或目录是否隐藏
5boolean isFile()判断File对象对应的是否是文件
6boolean isDirectory()判断File对象对应的是否是目录
7long lastModified()返回File对象对应的文件或目录的最后修改时间(毫秒值)
8long length()返回File对象对应的文件的内容的长度(字节数),如果File对象对应的是目录,则结果是不确定的

如果new的File对象所表示的文件或目录并不存在,那么并不会应位new了一个File对象,操作系统就在对应的路径下创建所对应的文件和目录,它仅仅是在JVM的堆中new了一个File对象而已。此时通过File对象获取的所有属性都是对象属性的默认值,如length()返回为0,isFile()和isDirectory()返回为false等。

示例代码:

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileTest2 {
    public static void main(String[] args) {
        File file = new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\test.java");
        long time = file.lastModified();
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String format = sf.format(new Date(time));
        System.out.println("最后修改时间:"+format);
        System.out.println("文件大小:"+file.length());
    }
}

image-20220926205857590

1.2 操作文件

我们在java程序中new了一个File对象,仅仅是在JVM的堆中创建了一个实例对象,并不会导致操作系统在对应路径下创建一个文件。如果希望在对应路径下创建或删除一个文件,那么需要使用如下表格中的方法。

File类的常用方法三:

序号方法名描述
1boolean createNewFile()如果指定的文件不存在并创建成功,返回true;如果指定的文件已存在,返回false;
2boolean createTempFile(String prefix,String suffix)在默认临时文件的目录中创建一个空文件,给定前缀和后缀生成其名称,调用此方法等同于调用createTempFile(prefix,suffix,null)
3boolean delete()当且仅当成功删除文件时,返回true;否则返回false
4void deleteOnExit()当退出JVM时,删除文件,一般用于删除临时文件
5boolean renameTo(File dest)重命名

创建新文件

import java.io.File;
import java.io.IOException;

public class TestCreateFile {
    public static void main(String[] args) throws IOException {
        File file = new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\test.java");
        //创建新文件
        file.createNewFile();

    }
}

创建临时新文件:

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

        //创建临时新文件
        File tempFile = File.createTempFile("hello", ".tmp");
        System.out.println(tempFile.getAbsolutePath());
    }
}

删除文件

public class TestDeleteFile {
    public static void main(String[] args) {
        File file = new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\test.java");
        //删除文件
        file.delete();
    }
}

重命名一个文件或目录

import java.io.File;

public class TestRenameFile {
    public static void main(String[] args) {
        File src = new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\test.txt");
        File dest = new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\testFile.txt");
        src.renameTo(dest);

    }
}

1.3 操作目录

以下是操作目录的常见方法,

序号方法名描述
1boolean mkdir()必须确保父目录存在,否则创建失败
2boolean mkdirs()如果父目录链不存在,则会一同创建父目录链
3String[] list()列出当前目录的下级目录或文件的名称
4File[] listFiles()列出当前目录的下级目录或文件对应的File对象
5File[] listFiles(FileFilter filter)返回抽象路径名数组,返回所有满足指定过滤器的文件和目录
6File[] listFiles(FilenameFilter filter)返回抽象路径名数组,返回所有满足指定过滤器的文件和目录
7static File[] listRoots()列出可用的文件系统根
8boolean delete()只能删除空目录。否则需要先将目录下的所有内容删除才能将该目录删除
9boolean renameTO(File dest)如果是Windos目录,则只能在同一个盘下,不能从D盘移动到E盘

创建一级目录

public class TestCreateDirectory {
    public static void main(String[] args) {
        File dir = new File("F:\\codeleader\\javase\\io");
        dir.mkdir();
    }
}

创建多级目录

public class TestCreateDirectorys {
    public static void main(String[] args) {
        File dir = new File("F:\\codeleader\\javase\\io");
        dir.mkdirs();
    }
}

如果父目录不存在,那么会一并创建,当然F盘分区是必须存在的,否则无法创建成功。

列出目录内容

public class TestListFiles {
    public static void main(String[] args) {
        File dir = new File("f:\\codeleader");
        File[] listFiles = dir.listFiles();
        if(listFiles!=null){
            for (int i = 0; i < listFiles.length; i++) {
                System.out.println(listFiles[i]);
            }
        }
    }
}

image-20220926211314563

列出目录内容,并加入过滤条件:

列出目录下的所有.java文件

public class TestFileFilter {
    public static void main(String[] args) {
        File dir = new File("F:\\IDEA_test\\剑指Java\\src\\chap13File");
        File[] listFiles = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {

                return name.endsWith(".java");
            }
        });

        if(listFiles!=null){
            for (File sub : listFiles) {
                System.out.println(sub);
            }
        }
    }
}

image-20220926211404083

删除目录

public class TestDeleteDirectory {
    public static void main(String[] args) {
        File dir = new File("F:\\codeleader\\javase\\io");
        dir.delete();
    }
}

目录重命名

public class TestRenameDirectory {
    public static void main(String[] args) {
        File dir = new File("F:\\codeleader\\javase");
        File dest = new File("F:\\codeleader\\javacode");
        dir.renameTo(dest);
    }
}

上述代码将F:/codeleader目录下的javase重命名为javacode

1.4 案例:递归列出目录的下一级

案例需求:列出某个目录(文件夹)下所有的下一级,如果下一级仍然是一个目录(文件夹),那么就继续列出它的下一级知道最后一级。

方案一:直接打印输出,不返回。

public class TestListAllSubs {
    public static void main(String[] args) {
        File dir = new File("F:\\codeleader");
        listSubFiles(dir);
    }
    public static void listSubFiles(File dir){
        if(dir!=null&&dir.isDirectory()){
            File[] listFiles = dir.listFiles();
            if(listFiles!=null){
                for (File sub : listFiles) {
                    listSubFiles(sub);      //递归调用
                }
            }
        }
        System.out.println(dir);
    }
}

image-20220926211752676

方案二:使用集合返回所有下一级,并考虑异常处理。

public class TestListAllSubs2 {
    public static void main(String[] args) {
        File dir = new File("F:\\codeleader");
        try {
            ArrayList<File> all = listSubFiles(dir);
            for (File file : all) {
                System.out.println(file);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    //列出某个目录下的所有下一级子目录或子文件
    public static ArrayList<File> listSubFiles(File dir) throws FileNotFoundException,IllegalArgumentException {
        if(dir==null||!dir.exists()){
            throw new FileNotFoundException(dir+"不存在");
        }
        if(dir.isFile()){
            throw new IllegalArgumentException(dir+"不是一个目录");
        }
        ArrayList<File> all = new ArrayList<>();
        if(dir!=null&& dir.isDirectory()){
            File[] listFiles = dir.listFiles();
            if(listFiles!=null){
                for (File sub : listFiles) {
                    all.add(sub);
                    if(sub.isDirectory()){
                        all.addAll(listSubFiles(sub));//递归调用
                    }
                }
            }
        }
        return all;
    }
}

image-20220926211836963

1.5 案例:递归列出目录下的所有Java源文件

public class TestListAllJavaFiles {
    public static void main(String[] args) {
        File dir = new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\dirs");
        listByFileFilter(dir);
    }

    public static void listByFileFilter(File file){
        if(file!=null && file.isDirectory()){
            File[] listFiles = file.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return pathname.isDirectory() || pathname.getName().endsWith(".java");
                }
            });
            if(listFiles!=null){
                for (File sub : listFiles) {
                    if(sub.isFile()){//如果是文件就直接输出
                        System.out.println(sub);
                    }else{//如果是文件夹目录,就递归
                        listByFileFilter(sub);
                    }
                }
            }
        }
    }
}

image-20220926212037429

2、IO流的分类和设计

IO流的四个超级父类、抽象基类:

  • InputStream:字节输入流,以字节方式读取数据。
  • OutputStream:字节输出流,以字节的方式输出数据。
  • Reader:字符输入流,以字符的方式读取数据。
  • Writer:字符输出流,以字符的方式输出数据。

2.1 输出纯文本数据

案例需求:用键盘输入文本留言信息,并保存到message.txt文件中。

public class FileWriterTest {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        FileWriter fw=null;

        try {
            //true表示追加模式,默认是覆盖模式
            fw=new FileWriter("F:\\IDEA_test\\剑指Java\\src\\chap13File\\IO\\message.txt",true);

            while(true){
                System.out.println("请输入留言(stop结束):");
                String message = input.nextLine();//输入

                if("stop".equals(message)){
                    break;
                }
                fw.write(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(input!=null){
                input.close();
            }
            try {
                if (fw!=null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

image-20220926212452813

image-20220926212501659

2.2 读取纯文本数据

案例需求:从message.txt中读取用户留言信息。

public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fr=null;
        try {
            fr = new FileReader("F:\\IDEA_test\\剑指Java\\src\\chap13File\\IO\\message.txt");

            char[] data=new char[1024];
            int len;
            //每次read,将数据读入到data数组中,并返回读入data中字符的个数
            //如果是行尾,则返回-1
            while((len=fr.read(data))!=-1){
                System.out.println(new String(data,0,len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            if(fr!=null){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

image-20220926212543395

2.3 按行读取

案例需求:从message.txt文件中读取用户留言信息,要求按行读取留言消息。

分析:message.txt是纯文本文件,首先考虑的肯定是Reader系列的FileReader类。但是FileReader类没有办法实现按行读取,所以需要其他的IO流来协助,如BufferedReader类的readLine方法,Scanner类的nextLine方法。

public class BufferedReaderTest {
    public static void main(String[] args) {
        BufferedReader br=null;
        try {
            br=new BufferedReader(new FileReader("F:\\IDEA_test\\剑指Java\\src\\chap13File\\IO\\message.txt"));
            String str;
            //按行读取,每次读取一行数据。如果是行尾,则返回null
            while((str=br.readLine())!=null){
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

image-20220926212931038

==注意:只有纯文本的数据才支持按行读取。==

2.4 复制文件基本版

和文件独写相关的IO流一共有以下四个:

  • 文件字节输入流:FileInputStream
  • 文件字节输出流:FileOutputStram
  • 文件字符输入流:FileReader
  • 文件字符输出流:FileWriter
public class CopyTest {
    public static void main(String[] args) {
        try {
            copyFile(new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\IO\\1.png"),new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\IO\\test\\1.png"));
            System.out.println("文件复制成功");
        } catch (IOException e) {
            System.out.println("文件复制失败");
            e.printStackTrace();
        }
    }

    //复制文件
    public static void copyFile(File src,File dest) throws IOException{
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(dest);

        try {
            byte[] data = new byte[1024];
            int len;
            while((len=fis.read(data))!=-1){//读取
                fos.write(data,0,len);//写入
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(fis!=null){
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(fos!=null){
                    fos.close();
                }
            }
        }
    }
}

上述代码实现了图片的复制

2.5 复制文件提升效率版

java.io包提供了Buffered系列的缓冲流,可以在独写数据时提升效率;Buffered系列的IO流只能给对应类型的IO流增加缓冲功能。例如,BufferedInputStream可以给InputStream系列的IO流增加缓冲功能,BufferedReader可以给Reader系列的IO流增加缓冲功能。

public class BufferedCopyTest {
    public static void main(String[] args) {
        try {
            copyFileInHighSpeed(new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\IO\\ig.jpg"),new File("F:\\IDEA_test\\剑指Java\\src\\chap13File\\IO\\test\\ig.jpg"));
            System.out.println("文件复制成功");
        } catch (IOException e) {
            System.out.println("文件复制失败");
            e.printStackTrace();
        }
    }

    //复制文件-带缓冲功能
    public static void copyFileInHighSpeed(File src,File dest) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest));
        try {
            byte[] data = new byte[1024];
            int len;
            while((len=bis.read(data))!=-1){//读取
                bos.write(data,0,len);//写入
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(bis!=null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    if(bos!=null){
                        bos.close();
                    }
                }
            }
        }
    }
}

==缓冲流的工作原理是先将要读取或写出的数据缓存到缓冲流的缓冲区,而缓冲区在JVM内存中,这样就减少了JVM内存与外接设备的交互次数,从而可以提高读写效率。==

2.6 操作Java各种数据类型的数据

可以使用DataOutputStream把数据写入文件,随后用DataInputStream进行读取,而且读取和写入的顺序要完全一致。

public class DataTest {
    public static void main(String[] args) throws IOException {
        save();
        reload();
    }
    //写入数据
    public static void save() throws IOException {
        String name="巫师";
        int age=300;
        char gender='男';
        int energy=5000;
        double price=75.5;
        boolean relive=true;
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("F:\\IDEA_test\\剑指Java\\src\\chap13File\\dataExer\\game.dat"));
        dos.writeUTF(name);
        dos.writeInt(age);
        dos.writeChar(gender);
        dos.writeInt(energy);
        dos.writeDouble(price);
        dos.writeBoolean(relive);
        dos.close();
    }
    //读取数据
    public static void reload() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("F:\\IDEA_test\\剑指Java\\src\\chap13File\\dataExer\\game.dat"));
        try {
            String name = dis.readUTF();
            int age = dis.readInt();
            char gender = dis.readChar();
            int energy = dis.readInt();
            double price = dis.readDouble();
            boolean relive = dis.readBoolean();

            System.out.println(name+","+age+","+gender+","+energy+","+price+","+relive);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(dis!=null){
                dis.close();
            }
        }
    }
}

2.7 保存对象

2.7.1 序列化和反序列化

Java中输出对象的过程称为序列化,读取对象的过程称为反序列化

==序列化的过程需要使用ObjectOutputStream,它有一个writeObject(obj)方法可以输出对象,即将对象的完整信息转换为字节流数据。==

==反序列化的过程需要使用ObjectInoutStream,它有一个readObject()方法可以读取对象,即从字节流数据中读取信息并重构一个java对象。==

import java.io.Serializable;

public class Account implements Serializable {
    private static double interestRate;//利率
    private String number;  //账号
    private String name;//户主
    private String password;//密码
    private double balance;//余额

    public Account(String number, String name, String password, double balance) {
        this.number = number;
        this.name = name;
        this.password = password;
        this.balance = balance;
    }

    public static double getInterestRate() {
        return interestRate;
    }

    public static void setInterestRate(double interestRate) {
        Account.interestRate = interestRate;
    }

    @Override
    public String toString() {
        return "Account{" +
                "number='" + number + '\'' +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", balance=" + balance +
                '}';
    }
}
public class ObjectOutputStreamTest {
    public static void main(String[] args) throws IOException {
        Account.setInterestRate(0.0024);
        Account account = new Account("111000111", "codeleader", "123456", 1000.0);
        ObjectOutputStream oos=null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("F:\\IDEA_test\\剑指Java\\src\\chap13File\\serializable\\account.dat"));
            oos.writeObject(account);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(oos!=null){
                oos.close();
            }
        }

    }
}

打开account.dat文件发现,序列化后的文件很难让人阅读,没关系,只有使用java程序才能读取序列化后的数据。

示例代码:

public class ObjectInputStreamTest {
    public static void main(String[] args) throws IOException {
        ObjectInputStream ois=null;
        try {
            ois = new ObjectInputStream(new FileInputStream("F:\\IDEA_test\\剑指Java\\src\\chap13File\\serializable\\account.dat"));
            Object readObject = ois.readObject();//读取对象
            System.out.println(readObject);
            System.out.println(Account.getInterestRate());

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(ois!=null){
                ois.close();
            }
        }
    }
}

image-20220926214252133

可以看到,正确地将account.dat中的数据反序列化回来了。但是发现利率值是不对的,因为它被static修饰,它不会被序列化。

2.7.2 不序列化的属性

类中static修饰的静态变量值是不会序列化的。

如果有些变量不需要被序列化,可以标记为transient(瞬时)。被标记为transient的对象在反序列化时,会被恢复为默认值。

2.7.3 序列化版本ID

序列化时的类与反序列化时的类不一致,会报异常。java为了避免这种类型安全性问题的发生,使序列化接口类在每次编译时,自动生成一个序列化版本ID,用以区别不同的版本,当序列化和反序列化的版本不一致时,就会失败,抛出异常java.io.InvalidClassException

然而,有些修改并不影响对象的反序列化,如类中加入了新的实例变量,而序列化的数据中并没有新实例变量的值,那么它在反序列化的过程中可以使默认值。为了适应这种情况,我么你在实现java.io.Serializable接口时,给类增加一个long类型的静态常量serialVersionUID,这样在对类进行修改后重新编译时系统并不会自动生成序列化版本ID,只要serialVersionUID没有修改,那么原来序列化的数据也可以顺利反序列化。

Account类增加序列化版本ID.

public class Account implements Serializable {
    private static final long serialVersionUID=1L;//序列化版本ID[唯一标识]

    private static double interestRate;//利率
    private String number;  //账号
    private String name;//户主
    private String password;//密码
    private double balance;//余额

    public Account(String number, String name, String password, double balance) {
        this.number = number;
        this.name = name;
        this.password = password;
        this.balance = balance;
    }

    public static double getInterestRate() {
        return interestRate;
    }

    public static void setInterestRate(double interestRate) {
        Account.interestRate = interestRate;
    }

    @Override
    public String toString() {
        return "Account{" +
                "number='" + number + '\'' +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", balance=" + balance +
                '}';
    }
}

2.8 按行输出文本内容

PrintStream和PringWriter是两个打印流,可以实现将java基本数据类型的数据格式转化为字符串输出,引用类型的数据自动调用toString()方法。

案例需求:从键盘中输入消息,按行写入文件。

import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Scanner;

public class PrintWriterTest {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        PrintWriter pw=null;
        try {
            pw = new PrintWriter("F:\\IDEA_test\\剑指Java\\src\\chap13File\\demo\\message.txt");
            while(true){
                System.out.println("请输入留言:");
                String message = input.nextLine();//按行输入

                if("stop".equals(message)){
                    break;
                }
                pw.println(message);
                System.out.println("结束留言,请输入stop");
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(input!=null){
                input.close();
            }
            if(pw!=null){
                pw.close();
            }
        }
    }
}

image-20220926215243849

image-20220926215252506

2.9 Scanner类与IO流

java.util.Scanner是一个可以使用正则表达式来解析基本数据类型和字符串的简单文本扫描器。

案例需求:使用Scanner在控制台接收用键盘输入的各种类型数据。

public class ScannerSystemInTest {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("姓名:");
        String name = input.nextLine();
        System.out.println("性别:");
        char gender = input.next().charAt(0);
        System.out.println("年龄:");
        int age = input.nextInt();
        System.out.println("电话:");
        String phone = input.next();
        System.out.println("邮箱:");
        String email = input.next();
        System.out.println("姓名:"+name);
        System.out.println("性别:"+gender);
        System.out.println("年龄:"+age);
        System.out.println("电话:"+phone);
        System.out.println("邮箱:"+email);

    }
}

image-20220926215502676

案例需求:使用Scanner从文件中扫描数据。

public class ScannerFromFileTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream=null;
        Scanner input=null;
        try {
            fileInputStream = new FileInputStream("F:\\IDEA_test\\剑指Java\\src\\chap13File\\System\\info.txt");
            input = new Scanner(fileInputStream);
            while(input.hasNext()){
                String str = input.nextLine();
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(input!=null){
                input.close();
            }
            if(fileInputStream!=null){
                fileInputStream.close();
            }
        }
    }
}

image-20220926215551616

image-20220926215622673

这块的API实在是太多了,上面也只是接触到了点皮毛,更多的细节操作只有在生产中发掘了。