JavaEE初阶教程之Java IO流读写与文件操作实战

 更新时间:2025年12月16日 08:29:52   作者:学无止尽5  
在Java编程中IO流是进行文件读写操作的重要组成部分,IO流提供了一种灵活、可靠的方式来处理文件和数据流,这篇文章主要介绍了JavaEE初阶教程之Java IO流读写与文件操作实战的相关资料,需要的朋友可以参考下

前言:

对于刚接触 Java“文件操作”和“IO” 的小伙伴来说,“文件操作”和“IO 流”总像两座小山峰——听着有点难,实则只要找对路径,一步一步就能轻松登顶。今天这篇文章,就带着大家从基础概念到实战代码,把 Java 文件操作和 IO 流彻底搞明白。

一、先搞懂:文件和文件系统的基础认知

在写代码之前,我们得先明白“文件”到底是什么。狭义上的文件,是硬盘这种持久化存储设备中独立的数据单位,就像办公桌上一份份单独的文档,不仅有文字内容,还有文件名、类型、大小这些“附加信息”——我们把这些附加信息叫做“文件的元信息”。

比如你在电脑上看到的“PSGet.Format.ps1xml”文件,它的元信息就包括“修改日期 2019/3/19”“类型 Windows PowerShell 数据文件”“大小 9KB”,这些信息和文件内容是分开保存的。

而随着文件越来越多,系统就用“树形结构”来管理它们——这就是我们熟悉的“文件夹(folder)或者目录(directory)”。比如 Windows 里的“此电脑→Windows(C:)→Program Files(X86)”,Linux 里的“/usr/bin”,都是通过层级目录把文件组织起来,既方便查找,逻辑上也更清晰。

另外,定位文件必须用到“路径”,这里分了两种:

  • 绝对路径:从根目录开始的完整路径,比如 C:\Program Files (x86)\WindowsPowerShell,不管当前在哪里,都能通过它找到文件;

  • 相对路径:从当前目录出发的路径,比如从“WindowsPowerShell”目录去“Windows NT”,用 ..\Windows NT 就行(.. 代表父目录,. 代表当前目录)。

拓展:即使是普通文件,根据其保存数据的不同,也经常被分为不同的类型,我们一般简单的划分为:

文本文件:保存被字符集编码的文本。

二进制文件:按照标准格式保存的非被字符集编码过的文件。

在Windows操作系统上,还有一类文件比较特殊,就是平时我们看到的快捷方式(shortcut),这种文件只是对真实文件的一种引用而已。其他操作系统上也有类似的概念,例如软链接(soft link)等。

最后,很多操作系统为了实现接口的统一性,将所有的 I/O 设备都抽象成了文件的概念,使用这一理念最为知名的就是 Unix、Linux 操作系统——万物皆文件。这种抽象设计能让操作系统对不同I/O设备(如硬盘、键盘、打印机等)的操作,都统一到文件操作的接口上,简化了开发和使用逻辑,无需为不同设备单独设计一套操作方式。

二、Java 中操作文件的“核心工具”:File 类

Java 用 java.io.File 类来抽象描述一个文件(包括目录),但要注意:创建了 File 对象,不代表真实存在这个文件,它只是对文件的“描述”而已。

1. File 类的关键属性、构造和方法

属性

修饰符及类型属性说明
static StringpathSeparator依赖于系统的路径分隔符,String类型的表示
static charpathSeparator依赖于系统的路径分隔符,char类型的表示

构造方法

签名说明
File(File parent, String child)根据父目录 + 孩子文件路径,创建一个新的 File 实例
File(String pathname)根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径
File(String parent, String child)根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示

File类常用方法

修饰符及返回值类型方法签名说明
StringgetParent()返回 File 对象的父目录文件路径
StringgetName()返回 File 对象的纯文件名称
StringgetPath()返回 File 对象的文件路径
StringgetAbsolutePath()返回 File 对象的绝对路径
StringgetCanonicalPath()返回 File 对象的修饰过的绝对路径
booleanexists()判断 File 对象描述的文件是否真实存在
booleanisDirectory()判断 File 对象代表的文件是否是一个目录
booleanisFile()判断 File 对象代表的文件是否是一个普通文件
booleancreateNewFile()根据 File 对象,自动创建一个空文件。成功创建后返回 true
booleandelete()根据 File 对象,删除该文件。成功删除后返回 true
voiddeleteOnExit()根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行
String[]list()返回 File 对象代表的目录下的所有文件名
File[]listFiles()返回 File 对象代表的目录下的所有文件,以 File 对象表示
booleanmkdir()创建 File 对象代表的目录
booleanmkdirs()创建 File 对象代表的目录,如果必要,会创建中间目录
booleanrenameTo(File dest)进行文件改名,也可以视为我们平时的剪切、粘贴操作
booleancanRead()判断用户是否对文件有可读权限
booleancanWrite()判断用户是否对文件有可写权限

2. File 类实操:从获取信息到创建删除

(1)搞懂 get 系列方法:获取文件信息

比如想知道文件的父目录、名称、路径,用这几个方法就行,:

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

public class Main {
    public static void main(String[] args) throws IOException {
        // 这里的文件不一定真实存在
        File file = new File("..\\hello-world.txt");
        System.out.println(file.getParent()); // 输出父目录:..
        System.out.println(file.getName());   // 输出文件名:hello-world.txt
        System.out.println(file.getPath());   // 输出路径:..\hello-world.txt
        System.out.println(file.getAbsolutePath()); // 输出绝对路径:D:\代码练习\文件示例1\..\hello-world.txt
        System.out.println(file.getCanonicalPath()); // 输出简化绝对路径:D:\代码练习\hello-world.txt
    }
}

(2)创建与删除文件:createNewFile() 和 delete()

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

public class Main {
    public static void main(String[] args) throws IOException {
        File file = new File("hello-world.txt"); // 确保文件初始不存在
        System.out.println(file.exists());      // 初始不存在:false
        System.out.println(file.createNewFile());// 创建成功:true
        System.out.println(file.exists());      // 创建后存在:true
        System.out.println(file.isFile());      // 是普通文件:true
        System.out.println(file.delete());      // 删除成功:true
        System.out.println(file.exists());      // 删除后不存在:false
    }
}

这里要注意:createNewFile() 只能创建普通文件,不能创建目录;delete() 直接删除文件,不会进回收站,操作要谨慎。

(3)创建目录:mkdir() 和 mkdirs() 的区别

新手最容易踩的坑就是这两个方法的区别!

  • mkdir():只能创建单层目录,如果父目录不存在,创建失败;
  • mkdirs():能创建多层目录(包括不存在的父目录)。

比如要创建“some-parent\some-dir”这个多层目录,用 mkdir() 会失败,用 mkdirs() 才能成功:

package IO;
import java.io.File;
public class demo3 {
    public static void main(String[] args) {
        File dir=new File("some-parent\\some-dir");
        System.out.println(dir.mkdir());
        System.out.println(dir.isDirectory());
        System.out.println(dir.mkdirs());
        System.out.println(dir.isDirectory());
    }
}

这个区别一定要记牢,不然创建多层目录时会卡很久。

(4)文件重命名:renameTo()

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

public class Main {
    public static void main(String[] args) throws IOException {
        File oldFile = new File("some-file.txt"); // 确保该文件存在
        File newFile = new File("dest.txt");      // 确保该文件不存在
        System.out.println(oldFile.exists());
        System.out.println(newFile.exists());
        System.out.println(oldFile.renameTo(newFile)); // 重命名成功:true
        System.out.println(oldFile.exists());     // 原文件不存在:false
        System.out.println(newFile.exists());     // 新文件存在:true
    }
}

注意:如果目标文件(dest.txt)已经存在,renameTo() 会返回 false,所以先判断目标文件是否存在很重要。

三、Java IO 流:文件内容读写的核心

搞懂了文件操作,接下来就是“读写文件内容”——这就需要 IO 流了。可以把 IO 流比喻得很形象:读文件像“接水”(输入流 InputStream),写文件像“灌水”(输出流 OutputStream),我们就顺着这个逻辑来学。

1. 字节流:InputStream 和 OutputStream

字节流是最基础的 IO 流,以“字节”为单位读写数据,适合所有文件(比如文本、图片、视频)。

(1)InputStream:读文件内容

InputStream 是抽象类,我们常用它的子类 FileInputStream 来读文件。它的核心方法是 read(),有三种用法:

  • read():读1个字节,返回字节值(-1 表示读完);
  • read(byte[] b):读多个字节到数组 b 中,返回实际读的字节数;
  • read(byte[] b, int off, int len):从 off 位置开始,读 len 个字节到数组 b 中。
  • close():关闭字节流。

FileInputStream类构造方法

签名说明
FileInputStream(File file)利用 File 构造文件输入流
FileInputStream(String name)利用文件路径构造文件输入流

强调:用数组读比单个字节读效率高,因为减少了 IO 次数。比如读“hello.txt”里的“Hello”:

单个字节读:

package IO;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo5 {
    //需要在项目目录下创建hello.txt文件
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            while(true){
                int b=is.read();
                if(b==-1){
                    break;
                }
                System.out.printf("%c",b);
            }
        }
    }
}

数组读:

package IO;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo6 {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            byte[] buf=new byte[1024];
            int len;
            while(true){
                len=is.read(buf);
                if(len==-1){
                    break;
                }
                for(int i=0;i<len;i++){
                    System.out.printf("%c",buf[i]);
                }
            }
        }
    }
}

运行后会输出“Hello”,这里用了 try-with-resources 语法,能自动关闭流,避免资源泄漏,新手一定要养成这个习惯。

如果读中文文件(比如“你好中国”),要注意编码,用 UTF-8 解码,因为 UTF-8 中一个中文字符占 3 个字节:

package IO;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo7 {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            byte[] buf=new byte[1024];
            int len;
            while (true){
                len=is.read(buf);
                if(len==-1){
                    break;
                }
                for(int i=0;i<len;i+=3){
                    String s=new String(buf,i,3,"UTF-8");
                    System.out.printf("%s",s);
                }
            }
        }
    }
}

我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是Scanner 类。

构造方法说明
Scanner(InputStream is, String charset)使用 charset 字符集进行 is 的扫描读取
package IO;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class demo8 {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            try(Scanner sc=new Scanner(System.in)){
                while(sc.hasNext()){
                    String s=sc.next();
                    System.out.print(s);
                }
            }
        }
    }
}

(2)OutputStream:写文件内容

OutputStream 也是抽象类,常用子类 FileOutputStream 写文件。核心方法是 write(),同样有三种用法,还有一个关键方法 flush()——因为 OutputStream 有缓冲区,数据会先存在内存,必须调用 flush() 才能把数据刷到硬盘

比如写字符串“你好中国”到“output.txt”:

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try (OutputStream os = new FileOutputStream("output.txt")) {
            String s = "你好中国";
            byte[] b = s.getBytes("UTF-8"); // 转成 UTF-8 字节数组
            os.write(b); // 写入字节数组
            os.flush();  // 必须刷新,否则数据可能留在缓冲区
        }
    }
}

运行后打开“output.txt”,就能看到“你好中国”。如果想追加内容,把 FileOutputStream 构造方法改成 new FileOutputStream("output.txt", true) 即可(第二个参数 true 表示追加)。

2. 字符流:更方便的文本读写

字节流读中文需要处理编码,很麻烦,这时候就需要“字符流”——按“字符”为单位读写,自动处理编码问题。用 Scanner 读字符,用 PrintWriter 写字符。

(1)用 Scanner 读文本文件

Scanner 能按行读文本,还能指定编码(比如 UTF-8),避免乱码。比如读“hello.txt”里的“你好中国”:

import java.io.*;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws IOException {
        try (InputStream is = new FileInputStream("hello.txt")) {
            // 指定 UTF-8 编码,避免中文乱码
            try (Scanner scanner = new Scanner(is, "UTF-8")) {
                while (scanner.hasNextLine()) { // 按行读
                    String line = scanner.nextLine();
                    System.out.println(line); // 输出:你好中国
                }
            }
        }
    }
}

这种方式比字节流简单多了,新手读文本文件优先用这个。

(2)用 PrintWriter 写文本文件

PrintWriter 有我们熟悉的 print()println()printf() 方法,写文本很方便,还能指定编码。

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try (OutputStream os = new FileOutputStream("output.txt")) {
            // 先转成 OutputStreamWriter,指定 UTF-8 编码
            try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
                // 用 PrintWriter 写内容
                try (PrintWriter writer = new PrintWriter(osWriter)) {
                    writer.println("我是第一行"); // 换行
                    writer.print("我是第二行");   // 不换行
                    writer.printf("%d: 我是第三行\n", 3); // 格式化输出
                    writer.flush(); // 刷新到硬盘
                }
            }
        }
    }
}

运行后“output.txt”里会有三行内容,格式清晰,比直接用 OutputStream 方便太多。

四、实战案例:把知识点串起来用

学完基础,必须通过实战巩固。通过一些经典案例,我们逐个拆解,新手跟着写一遍就能掌握。

案例 1:扫描目录,找到指定文件并删除

需求:输入根目录和关键词,找到文件名包含关键词的普通文件,询问用户是否删除。

核心思路:用“递归”遍历树形目录(因为文件系统是树形结构),找到符合条件的文件后处理。

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 输入根目录
        System.out.print("请输入要扫描的根目录(绝对路径/相对路径):");
        String rootDirPath = scanner.next();
        File rootDir = new File(rootDirPath);
        if (!rootDir.isDirectory()) {
            System.out.println("根目录不存在或不是目录,退出!");
            return;
        }
        // 输入关键词
        System.out.print("请输入文件名包含的字符:");
        String token = scanner.next();

        List<File> result = new ArrayList<>();
        // 递归扫描目录
        scanDir(rootDir, token, result);

        // 处理结果
        System.out.println("共找到 " + result.size() + " 个符合条件的文件:");
        for (File file : result) {
            System.out.print(file.getCanonicalPath() + ",是否删除?(y/n)");
            String choice = scanner.next();
            if (choice.toLowerCase().equals("y")) {
                file.delete();
                System.out.println("已删除!");
            }
        }
    }

    // 递归扫描目录的方法
    private static void scanDir(File rootDir, String token, List<File> result) {
        File[] files = rootDir.listFiles();
        if (files == null || files.length == 0) {
            return; // 目录为空,返回
        }
        for (File file : files) {
            if (file.isDirectory()) {
                scanDir(file, token, result); // 是目录,递归扫描
            } else {
                // 是普通文件,判断文件名是否包含关键词
                if (file.getName().contains(token)) {
                    result.add(file.getAbsoluteFile());
                }
            }
        }
    }
}

这个案例用到了 File 类的 isDirectory()listFiles(),还有递归遍历,新手要理解递归的逻辑——“自己调用自己,处理子目录”。

案例 2:文件复制工具

需求:输入源文件路径和目标路径,实现文件复制(支持所有文件类型,比如文本、图片)。

核心思路:用 InputStream 读源文件,用 OutputStream 写目标文件,用字节数组做缓冲区提高效率。

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 输入源文件
        System.out.print("请输入要复制的文件路径:");
        String sourcePath = scanner.next();
        File sourceFile = new File(sourcePath);
        if (!sourceFile.exists()) {
            System.out.println("源文件不存在!");
            return;
        }
        if (!sourceFile.isFile()) {
            System.out.println("不是普通文件,无法复制!");
            return;
        }
        // 输入目标路径
        System.out.print("请输入目标路径:");
        String destPath = scanner.next();
        File destFile = new File(destPath);

        // 处理目标文件已存在的情况
        if (destFile.exists()) {
            if (destFile.isDirectory()) {
                System.out.println("目标是目录,无法覆盖!");
                return;
            }
            System.out.print("目标文件已存在,是否覆盖?(y/n)");
            String choice = scanner.next();
            if (!choice.toLowerCase().equals("y")) {
                System.out.println("停止复制!");
                return;
            }
        }

        // 开始复制:读源文件,写目标文件
        try (InputStream is = new FileInputStream(sourceFile);
             OutputStream os = new FileOutputStream(destFile)) {
            byte[] buf = new byte[1024]; // 1KB 缓冲区
            int len;
            while ((len = is.read(buf)) != -1) {
                os.write(buf, 0, len); // 写读到的字节
            }
            os.flush(); // 刷新缓冲区
        }
        System.out.println("复制完成!");
    }
}

执行完成后ouput文件复制了hello文件里面的内容。

这个案例是 IO 流的经典应用,不管复制什么文件(文本、图片、视频)都能用,因为字节流不区分文件类型。

案例 3:扫描目录,找到内容包含关键词的文件

需求:输入根目录和关键词,找到文件名或内容包含关键词的普通文件。

核心思路:在案例 1 的基础上,增加“读文件内容判断”的逻辑,用 Scanner 读文件内容,判断是否包含关键词。

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入要扫描的根目录:");
        String rootDirPath = scanner.next();
        File rootDir = new File(rootDirPath);
        if (!rootDir.isDirectory()) {
            System.out.println("根目录无效,退出!");
            return;
        }
        System.out.print("请输入要查找的关键词:");
        String token = scanner.next();

        List<File> result = new ArrayList<>();
        scanDirWithContent(rootDir, token, result);

        System.out.println("共找到 " + result.size() + " 个文件:");
        for (File file : result) {
            System.out.println(file.getCanonicalPath());
        }
    }

    // 递归扫描目录,判断文件名或内容
    private static void scanDirWithContent(File rootDir, String token, List<File> result) throws IOException {
        File[] files = rootDir.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                scanDirWithContent(file, token, result);
            } else {
                // 文件名包含,或内容包含,都加入结果
                if (file.getName().contains(token) || isContentContains(file, token)) {
                    result.add(file);
                }
            }
        }
    }

    // 判断文件内容是否包含关键词(按 UTF-8 处理)
    private static boolean isContentContains(File file, String token) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (InputStream is = new FileInputStream(file);
             Scanner scanner = new Scanner(is, "UTF-8")) {
            while (scanner.hasNextLine()) {
                sb.append(scanner.nextLine()).append("\n"); // 读所有行
            }
        }
        return sb.indexOf(token) != -1; // 判断是否包含关键词
    }
}

这个案例增加了 isContentContains() 方法,读文件内容并判断,适合查找文本文件中的关键词(注意:大文件会影响性能,文档里也提到了这一点)。

五、新手避坑总结

看到这里,你已经掌握了 Java 文件操作和 IO 流的核心内容,最后再总结几个新手常踩的坑,帮你少走弯路:

  1. File 类不代表真实文件:创建 File 对象只是“描述”文件,不代表文件存在,必须调用 createNewFile()mkdirs() 才会真实创建;
  2. mkdir() 和 mkdirs() 别用混:创建多层目录一定要用 mkdirs()
  3. IO 流必须关闭:用 try-with-resources 语法,自动关闭流,避免资源泄漏;
  4. OutputStream 要 flush():写完数据必须调用 flush(),否则数据可能留在缓冲区,没写到硬盘;
  5. 读中文要指定编码:用 Scanner 或 OutputStreamWriter 时,明确指定 UTF-8,避免乱码。

至此,Java 文件操作和 IO 流的核心知识和实战就讲完了。其实这些内容并不难,关键是多写代码、多跑案例——把文中的代码逐个复制到 IDE 里运行,改改参数(比如换个文件路径、关键词),很快就能熟练掌握。告别小白,从搞定文件操作和 IO 流开始吧!

到此这篇关于JavaEE初阶教程之Java IO流读写与文件操作实战的文章就介绍到这了,更多相关Java IO流读写与文件操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring中的@RestController注解详细解析

    Spring中的@RestController注解详细解析

    这篇文章主要介绍了Spring中的@RestController注解详细解析,@RestController 是 Spring Framework 中的一个注解,用于标识一个类为 RESTful Web 服务的控制器(Controller),处理 HTTP 请求并返回相应的数据,
    2024-01-01
  • JVM的垃圾回收机制详解和调优

    JVM的垃圾回收机制详解和调优

    JVM的垃圾回收机制详解和调优...
    2006-12-12
  • SpringBoot整合SSE接口实现实时数据推送

    SpringBoot整合SSE接口实现实时数据推送

    SSEServer-Sent Events)是一种基于HTTP的服务器向客户端单向实时推送数据的技术,本文主要介绍了SpringBoot整合SSE接口实现实时数据推送的方法,需要的可以参考下
    2025-05-05
  • 详解Spring Boot 项目部署到heroku爬坑

    详解Spring Boot 项目部署到heroku爬坑

    这篇文章主要介绍了详解Spring Boot 项目部署到heroku爬坑,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • Java检测线程中断状态的方法示例

    Java检测线程中断状态的方法示例

    这篇文章主要介绍了Java检测线程中断状态的方法,结合实例形式分析了java针对线程中断状态检测的相关实现技巧,需要的朋友可以参考下
    2019-10-10
  • SpringBoot启动多数据源找不到合适的驱动类问题

    SpringBoot启动多数据源找不到合适的驱动类问题

    这篇文章主要介绍了SpringBoot启动多数据源找不到合适的驱动类问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • springboot使用@Slf4j进行日志的记录步骤详解

    springboot使用@Slf4j进行日志的记录步骤详解

    这篇文章主要介绍了springboot使用@Slf4j进行日志的记录,使用@Slf4j的注解进行日志记录非常方便,本文给大家分享操作步骤,需要的朋友可以参考下
    2023-08-08
  • MybatisPlus自动填充创建(更新)时间问题

    MybatisPlus自动填充创建(更新)时间问题

    在开发数据库相关应用时,手动设置创建和更新时间会导致代码冗余,MybatisPlus提供了自动填充功能,通过实现MetaObjectHandler接口并重写insertFill、updateFill方法,可以自动维护创建时间、更新时间等字段,极大简化了代码,这不仅提高了开发效率,也保证了数据的可追溯性
    2024-09-09
  • SpringMvc框架的简介与执行流程详解

    SpringMvc框架的简介与执行流程详解

    MVC是一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个组件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,MVC分层有助于管理和架构复杂的应用程序
    2021-06-06
  • java非官方常用类MessageInfo消息接口示例

    java非官方常用类MessageInfo消息接口示例

    这篇文章主要为大家介绍了java非官方常用类MessageInfo消息接口使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09

最新评论