【JavaEE】一文学会如何使用:文件IO操作(详解)

news2024/11/14 20:20:29

目录

前言

什么是IO?

IO流原理

IO流分类

InputStream字节流输入

文件输入流--FileInputStream

理解InputStream中的read方法

关闭文件操作         

利用Scanner进行字符获取

OutputStream字节流输出

文件输出流--FileOutputStream

理解OutputStream中的write方法

利⽤PrintWriter找到我们熟悉的⽅法

Reader读操作

创建Reader对象

FileReader的构造方法

理解Reader类中的不同read方法

使用Reader中的read()方法

利用Scanner进行字符获取

Writer写操作

创建Writer对象

FileWriter构造方法

理解Writer中的write方法

使用Writer中的write方法

运用文件操作和IO实现一个删除指定文件的功能

复制指定文件

在指定目录中查找文件名或文件内容含有关键词的文件


前言

在上一篇中,我们学习了什么是文件,以及如何创建和关闭文件,但那些都是针对文件进行的操作,本篇我们就来讲解如何对文件内容进行操作。

什么是IO?

I/O是“Input/Output”的缩写,中文意思是“输入/输出”,指的是计算机系统与外部环境之间进行的数据交换的过程。我们从键盘输入数据、或者点击鼠标进行操作等这些都是属于输入操作;显示屏、声音这些都是属于输出操作。

当然,不仅局限于硬件设备的IO操作(磁盘IO),还有网络方面的I/O。

IO流原理

IO流是基于流(stream)的概念,将输入的数据和输出的数据看做一个连续的流。数据从一个地方流向另一个地方,流的方向可以是输入(读取数据)或者输出(写入数据)。

IO流的原理是通过流的管道将数据从源头传输到目标地。源头可以是文件、网络连接、内存等,而目标地可以是文件、数据库、网络等。IO流提供了一组丰富的类和方法来实现不同类型的输入和输出操作。

如何判断操作是输入还是输出?我们需要一个参照物,假设以CPU为参照物,那么我们可以认为靠近CPU的操作就是输入,而远离CPU的操作就是输出操作,即如果从CPU上向文件传输数据,那么对于CPU来说,就是在输出,而对于文件来说,这个操作是输入操作。

IO流分类

java中IO流可以按照数据的类型、流的方向和流的功能进行分类。

在Java中,IO流按照数据类型分为两类:

  • 字节流 :读取数据的基本单位是字节,适用于处理二进制数据,如图像、音频、视频等,InputStream/OutputStream
  • 字符流 :读取数据的基本单位是字符,适合处理文本数据。会自动进行字符编码和解码。Reader/Writer

一个字符有多少个字节?

这是不确定的,取决于编码方式,在UTF8中,一个字符是3个字节,而在Unicode中,一个字符是2个字节。

虽然分为两类,但是本质上都是字节流,不过字符流对字节进行了一些封装,根据字符集编码。

按照流的方向分类:

  • 输入流(InputStream):用于读取数据。输入流从数据源读取数据,如文件、网络连接等。常见的输入流类有:FileInputStream、ByteArrayInputStream、SocketInputStream等。
  • 输出流(OutputStream):用于写入数据。将数据写入到目标地,如文件、数据库、网络等。常见的输出流有:FileOutputStream、ByteArrayOnputStream、SocketOnputStream等。

按照流的功能分类

  • 节点流:直接和输入输出源交互。直接与数据源或目的地相连,没有额外的处理逻辑
  • 处理流:间接和输入输出源交互。包装在节点流或其他处理流之上,添加额外的功能。

适用场景

操作场景适用技术
*文件操作读写或写入文件中的数据字节流:使用 FileInputStream 和 FileOutputStream 来读写二进制文件。
字符流:使用 FileReader 和 FileWriter 来读写文本文件。
缓冲流:使用 BufferedInputStream 和 BufferedOutputStream 或 BufferedReader 和 BufferedWriter 来提高文件读写的效率。
转换流:使用 InputStreamReader 和 OutputStreamWriter 来处理不同编码的文本文件。
网络通信在网络上传输数据Socket 编程:使用 Socket 和 ServerSocket 类来实现客户端和服务端之间的通信。
字节流:使用 Socket 的 getInputStream() 和 getOutputStream() 方法来读写数据。
字符流:使用 Socket 的 getInputStream() 和 getOutputStream() 结合 InputStreamReader 和 OutputStreamWriter 来读写文本数据。
内存操作在内存中创建或操作数据流字节流:使用 ByteArrayInputStream 和 ByteArrayOutputStream 来处理内存中的字节数据。
字符流:使用 StringReader 和 StringWriter 来处理内存中的字符数据。
高效数据处理需要高效地处理大量数据缓冲流:使用 BufferedInputStream 和 BufferedOutputStream 或 BufferedReader 和 BufferedWriter 来提高读写速度。
NIO (New I/O):使用 FileChannel 和 ByteBuffer 来实现非阻塞的文件读写,以及内存映射文件等功能。
数据格式化需要以特定格式读写数据字节流:使用 DataInputStream 和 DataOutputStream 来读写基本数据类型。
对象序列化:使用 ObjectInputStream 和 ObjectOutputStream 来读写 Java 对象
异步 I/O需要非阻塞地处理 I/O 操作NIO.2 (Java 7+):使用 AsynchronousFileChannel 和 CompletionHandler 来实现真正的异步 I/O 操作
线程间通信线程之间需要传递数据管道流:使用 PipedInputStream 和 PipedOutputStream 来实现线程间的简单通信。
 
标准输入输出控制台输入输出字节流:使用 System.in 和 System.out 来处理标准输入输出。
字符流:使用 System.in 结合 InputStreamReader 和 System.out 结合 PrintWriter 来处理文本数据。
日志记录记录程序运行时的信息日志框架:使用如 Log4j、SLF4J 等日志框架来记录日志,通常使用字符流进行处理。
图形图像处理处理图像文件字节流:使用 ImageIO 类来读写图像文件,通常需要使用 FileInputStream 和 FileOutputStream。
音频视频处理处理多媒体文件字节流:使用 AudioSystem 和 javax.sound.sampled 包中的类来处理音频文件。
字节流:使用 javax.media 包中的类来处理视频文件。

本篇我们主要讲如何对文件内容进行操作。

InputStream和OutputStream都是抽象类所有字节输入流和输出流的父类

InputStream字节流输入

InputStream是抽象类所有字节输入流父类

在上面中,我们已经知道了InputStream是用来读入数据的。

InputStream常用方法

修饰符及返回值类型方法签名说明
intread()读取一个字节的数据,返回-1代表已经完全读完了
intread(byte[] b)最多读取b.length字节的数据到b中,返回实际读取到的数量;-1代表已经读完了
intread(byte[] b,nt off,int len)最多读取len-off字节的数据到b中,返回实际读取到的数量;-1代表已经读完了
voidclose()关闭字节流

InputStream 只是⼀个抽象类,要使⽤还需要具体的实现类。关于InputStream的实现类有很多,基 本可以认为不同的输⼊设备都可以对应⼀个InputStream类,我们现在只关心从文件中读取,所以使用FileInputStream。 

文件输入流--FileInputStream

构造方法

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

一般常用第二种构造方法,直接写入文本的绝对路径或者相对路径

我们可以来看下:

我们可以看到当在创建一个文件流对象时,是需要抛出一个FileNotFoundException异常的。这是为什么呢? 我们可以看相关注释:如果命名的文件不存在,或者是一个目录或者是由于其他原因无法打开的时候,就会抛出FileNotFoundException异常。

同理的,我们可以看到若是传入一个文件对象,抛出异常的原因和上面基本一样。 

 创建一个输入流对象

        InputStream inputStream=new FileInputStream("./text1.txt");

如果文件不存在就会抛出异常,创建的过程其实就是打开文件的过程。

理解InputStream中的read方法

当我们创建完一个输入流对象之后,那么此时我们就可以调用read方法来读取文件中的数据了。

 我们可以看到有三个重载的read方法。InputStream是字节流,如果想要用数组来接收的话,需要传入一个字节数组。

我们可以看下不带参数的read方法。

这段话的大概意思就是从输入流中读取下一个字节的数据。返回一个取值返回在0~255的int类型的数据。如果读到流的结尾就会返回-1.

示例:假设现在已经有一个文本text.txt,我们往其中输入”你好“

我们可以查看一下“你好”转为16进制是什么,可以看到为“0xe4 0xbd 0xa0 0xe5 0xa5 0xbd”

在线字符串/十六进制互相转换—LZL在线工具 (lzltool.cn)

那么我们期望在从文本读取到的结果也与我们在所查找的一致 

read():每次获取一个字节的数据

import java.io.IOException;
import java.io.InputStream;

public class Demo {
    public static void main(String[] args) throws IOException {
        InputStream inputStream=new FileInputStream("./text.txt");
        while(true) {
            int ch=inputStream.read();
            if(ch==-1) break;
            System.out.printf("0x%x ",ch);
        }
    }
}


read(byte[] b):将数据读取到字节数组中
但对于上述这种,每次从硬盘中读取一个字节,所带来的I/O次数太多,效率也不会很高。

所以我们传入一个字节数组去接受每次获取到的字节,尽可能的填满字节数组。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo1 {
   /**
    * 程序的入口点。读取并打印指定文件的二进制内容。
    *
    * @param args 命令行参数
    * @throws IOException 如果文件不存在或无法读取,将抛出此异常
    */
   public static void main(String[] args) throws IOException {
      // 创建一个文件输入流,用于读取指定文件的内容
      InputStream inputStream=new FileInputStream("./text.txt");

      // 创建一个字节数组,用于存储从文件中读取的数据
      byte[] bytes=new byte[1024];

      // 读取文件中的数据到字节数组中,并返回实际读取的字节数
      int ch=inputStream.read(bytes);

      // 遍历已读取的字节数组,并以十六进制格式打印每个字节
      for(int i=0;i<ch;i++){
         System.out.printf("0x%x ",bytes[i]);
      }
   }

}


read(byte[] b,int off,int len):字节数组从off位置开始写入,最大不能超过len。

我们也可以让read从off位置开始向后读入。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo1 {
   /**
    * 程序的入口点。读取并打印指定文件的二进制内容。
    *
    * @param args 命令行参数
    * @throws IOException 如果文件不存在或无法读取,将抛出此异常
    */
   public static void main(String[] args) throws IOException {
      // 创建一个文件输入流,用于读取指定文件的内容
      InputStream inputStream=new FileInputStream("./text.txt");

      // 创建一个字节数组,用于存储从文件中读取的数据
      byte[] bytes=new byte[1024];

      // 读取文件中的数据到字节数组中,并返回实际读取的字节数
      int ch=inputStream.read(bytes,5,100);

      // 遍历已读取的字节数组,并以十六进制格式打印每个字节
      for(int i=5;i<ch+5;i++){
         System.out.printf("0x%x ",bytes[i]);
      }
   }
}

关闭文件操作         

当我们把所有的文件操作都完成之后,那么我们就得关闭文件,使用inputStream.close()来防止文件资源泄露。但如果在执行这段代码之前就发生了异常,导致程序提前终止,那么这个文件就不会被关闭,所以我们需要使用异常处理机制来解决这个问题。

try {
	//...
} finally {
	inputstream.close();
}

通过try-finally语句,就可以保证finally中的代码无论如何都会被执行到。

但如果我们按照上面这种方法,可能就会写成:

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

public class Demo2 {
    /**
     * 程序的入口点。此方法用于读取指定文件并将其内容输出到控制台。
     * 
     * @param args 命令行参数,本程序未使用。
     */
    public static void main(String[] args) {
        // 尝试打开一个文件输入流以读取文件内容。
        try {
            InputStream inputStream=new FileInputStream("./text.txt");
            // 创建一个字节数组,用于存储从文件读取的数据。
            byte[] bytes=new byte[1024];
            // 读取文件内容到字节数组中。
            int len=inputStream.read(bytes);
            // 循环读取文件,直到文件末尾。
            while (len!=-1){
                // 将读取的字节数组转换为字符串并打印。
                System.out.println(new String(bytes,0,len));
                // 继续读取文件的下一部分内容。
                len=inputStream.read(bytes);
            }
        } 
        // 如果文件不存在,抛出运行时异常。
        catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } 
        // 如果发生IO错误,抛出运行时异常。
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

 这样看起来是不是不雅观?

那么有没有一种方法能够让我们的方法看起来更加简洁呢?

try (InputStream inputStream=new FileInputStream("./text.txt")) {
	//....
}

上面这种语句叫做 try-with-resources 语句,这个语句的特性:当括号里的对象实现了 closerable 的话,当{}内的代码执行完之后会自动调用该对象的 close() 方法。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo3 {
    /**
     * 程序的入口点
     * 该方法用于读取指定文件并打印出文件内容的十六进制表示
     * @param args 命令行参数,本程序未使用此参数
     */
    public static void main(String[] args) {
        // 尝试打开一个文件输入流以读取文件内容
        try(InputStream inputStream=new FileInputStream("./text.txt")){
            // 创建一个字节数组,用于存储从文件读取的数据
            byte[] bytes=new byte[1024];
            // 读取文件内容到字节数组中
            int len=inputStream.read(bytes);
            // 遍历已读取的字节数组,以十六进制格式打印每个字节
            for(int i=0;i<len;i++){
                System.out.printf("0x%x ",bytes[i]);
            }
        } catch (IOException e) {
            // 捕获可能发生的IO异常,并打印异常信息
            e.printStackTrace();
        }
    }
}

为什么不关闭文件,会造成文件资源泄露?

在前面,我们学习了进程和线程,当我们创建一个线程后,就有一个对应的PCB(进程控制块),而PCB中存在着多种属性,例如pid、、内存指针、文件描述表等。当在一个进程中打开一个文件,就需要在这个文件描述符表中分配一个元素,而这些元素存在一个长度固定的数组中,如果我们打开一个文件,并且不将文件关闭,这个数组中的元素就会越来越多,直到数组元素满了,就会出现文件资源泄露问题。 

所以我们在用完文件之后,一定要关闭文件!

利用Scanner进行字符获取

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

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

charset使用的是系统默认字符编码来读取。

我们可以使用下面的代码来查看系统的默认编码方式。

Charset defaultCharset = Charset.defaultCharset();
System.out.println("Default charset: " + defaultCharset.name());

 示例:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo4 {
    /**
     * 程序的入口点
     * 该方法用于读取指定文件中的内容,并将其打印到控制台
     * 
     * @param args 命令行参数,本例中未使用
     */
    public static void main(String[] args) {
        // 尝试打开文件 "./text.txt" 以读取其内容,在文本内写入:你好
        try(InputStream inputStream=new FileInputStream("./text.txt")){
            // 创建扫描器以读取输入流中的数据
            Scanner scanner=new Scanner(inputStream);
            // 循环读取并打印文件内容,直到文件末尾
            while(scanner.hasNext()){
                System.out.printf("%s",scanner.next());
            }
        }catch (IOException e){
            // 捕获并处理可能的IO异常
            e.printStackTrace();
        }
    }

}


OutputStream字节流输出

OutputStream也是一个抽象类,是所有字节输出流的父类。

方法

修饰符及返回值类型方法签名说明
voidwrite(int b)写入要给字节的数据
voidwrite(byte[] b)将b这个字符数组中的数据全部写入os中
intwrite(byte[] b,int off,int len)将b这个字符数组中从off开始的数据写入到os中,一共写len个
voidclose()关闭字节流
voidflush()重要:我们知道I/O的速度是很慢 的,所以,大多的OutputStream 为了减少设备操作的次数,在写数 据的时候都会将数据先暂时写⼊内 存的⼀个指定区域⾥,直到该区域 满了或者其他指定条件时才真正将 数据写⼊设备中,这个区域⼀般称 为缓冲区。但造成⼀个结果,就是 我们写的数据,很可能会遗留⼀部 分在缓冲区中。需要在最后或者合 适的位置,调⽤flush(刷新)操 作,将数据刷到设备中。

OutputStream同样只是⼀个抽象类,要使⽤还需要具体的实现类。我们现在还是只关⼼写⼊⽂件 中,所以使⽤FileOutputStream

文件输出流--FileOutputStream

既然我们知道了如何以字节为单位来读取文件数据,那么就来学习如何输出字节到文件中。

构造方法

签名说明
FileOutputStream(File file)利用文件构造文件输入流根据传入的文件,创建一个文件输出流对象
FileOutputStream(File file,boolean append)利用文件构造文件输入流创建一个文件输出流对象,若append为true,说明保留文件中的内容,不清除
FileOutputStream(String name)利用文件路径构造文件输入流
FileOutputStream(String name,boolean append)利用文件路径构造文件输入流创建一个文件输出流对象,若append为true,说明保留文件中的内容,不清除

首先需要构造一个OutputStream对象。同样的,这里我们使用FileOutputStream来实例化一个输出流对象。这里同样需要抛出IO异常。

  try(OutputStream outputStream=new FileOutputStream("./text.txt")){
            //.....
        }catch (IOException e){
            e.printStackTrace();
        }

理解OutputStream中的write方法

我们知道OutputStream其实是一个抽象类,所以其中的方法也是需要其子类来实现,这里我们将FileOutputStream类。

我们可以看到其实与InputStream中的read的方法相似,都是由三个重载的方法.

我们可以看到当参数为int的时候,其实b的值对于的是ASCII码中的字符,其余两个方法的参数,与InputStream类似。

示例:假设现在我们要在一个空文本text中写入“你好”,那么我们可以查看其对于的16进制值。

0xe4 0xbd 0xa0 0xe5 0xa5 0xbd 

write(int b):一次写入一个字节的数据

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo5 {
    /**
     * 程序的入口点 主函数
     * 将一组Unicode字符写入到文件中
     * 
     * @param args 命令行参数,本程序中未使用
     */
    public static void main(String[] args) {
        // 使用try-with-resources语句确保文件流正确关闭
        try(OutputStream outputStream=new FileOutputStream("./text.txt")){
            outputStream.write(0xe4);
            outputStream.write(0xbd);
            outputStream.write(0xa0);
            outputStream.write(0xe5);
            outputStream.write(0xa5);
            outputStream.write(0xbd);
        }catch (IOException e){
            // 捕获并处理可能的IO异常
            e.printStackTrace();
        }
    }

}

运行前:

运行后:


当然,这样一个字节一个字节写入效率是很低的,所有我们可以使用字节数组。

write(byte[] b):最多读取b.length字节的数据到b中,返回实际读取到的数量;-1代表已经读完了

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo6 {
    /**
     * 主函数,用于向指定文件写入字节数据
     * @param args 命令行参数,本例中未使用
     */
    public static void main(String[] args) {
        // 使用try-with-resources语句确保文件流正确关闭
        try(OutputStream outputStream=new FileOutputStream("./text.txt",true)){
            // 定义要写入文件的字节数组,此处为"你好"的UTF-8编码
            byte[] bytes={(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd};
            // 将字节数组写入文件
            outputStream.write(bytes);
        }catch (IOException e){
            // 捕获并处理可能的IO异常
            e.printStackTrace();
        }
    }

}

这里我们可以在构造的时候传入一个true,不删除文本原有内容。且这里需要注意的是:

由于数组是字节型,但是我们16进制表示的时候,其实是整型,所以我们这里存储的时候需要强转为字节类型。

运行前:

运行后:


write(byte[] b,int off,int len):将b这个字符数组中从off开始的数据写入到os中,一共写len个

接着上面的程序,我们想要拼接个“

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo7 {
    /**
     * 程序的入口点
     * 使用try-with-resources语句确保文件流正确关闭,以向文件追加写入字节
     * @param args 命令行参数,本程序不使用此参数
     */
    public static void main(String[] args) {
        // 使用try-with-resources语句确保文件流正确关闭
        try(OutputStream outputStream=new FileOutputStream("./text.txt",true)){
            // 定义要写入文件的字节数组,此处为"你好"的UTF-8编码
            byte[] bytes={(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd};
            // 将字节数组写入文件
            outputStream.write(bytes,3,3);
        }catch (IOException e){
            // 捕获并处理可能的IO异常
            e.printStackTrace();
        }

    }

}

 运行前:

运行后:

有时候我们会看见我们运行程序之后,但是我们查看的时候,并没有写入的数据,这是为什么?

其实在写入数据的时候,并不是直接将内存中的数据写入文件中的,而是在这个过程中还会经过一个特殊的内存空间--缓冲区。当数据传入缓冲区的时候,缓冲区并不会立即将数据传输到文件中,而是当缓冲区中的数据达到一定大小之后,才会将这些数据一起传输到文件中。

为什么要有缓冲区这个东西呢?

数据从内存到缓冲区中的速度是很欢的,但是数据从缓存区到文件中速度是比较慢的。如果缓冲区中接收到了数据之后就立即将这些数据传输到文件中,这样的I/O次数就会很多,效率自然也高不了。就类似于:你的领导给你分配了一个任务,那么你就去执行,但在这个过程中,由于领导又临时给你分配了一些任务,如果你在这个过程中,完成一个任务就上交给领导一次,这样不仅效率低,领导对你的好感也会降低,所以,可以选择把任务都完成之后再交给领导,这样效率就提高了很多。这就是缓冲区的作用。

我们既然知道了数据在写入的过程中,会经过缓冲区,那么为什么在写入数据的时候,是数据最终没有被写入?这就是由于缓冲区内的数据太少,没有达到数据传入文件的大小,那么缓冲区就不会将接收到的数据传入文件,而这时由于程序执行结束,那么缓冲区内的数据就会被释放掉,导致数据最终没有被写入文件中。

因此,为了解决这个问题,我们在把数据都写入后,可以刷新一下缓冲区。outputstream.flush().

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo7 {
    /**
     * 程序的入口点
     * 使用try-with-resources语句确保文件流正确关闭,以向文件追加写入字节
     * @param args 命令行参数,本程序不使用此参数
     */
    public static void main(String[] args) {
        // 使用try-with-resources语句确保文件流正确关闭
        try(OutputStream outputStream=new FileOutputStream("./text.txt",true)){
            // 定义要写入文件的字节数组,此处为"你好"的UTF-8编码
            byte[] bytes={(byte) 0xe4, (byte) 0xbd, (byte) 0xa0, (byte) 0xe5, (byte) 0xa5, (byte) 0xbd};
            // 将字节数组写入文件
            outputStream.write(bytes,3,3);
            outputStream.flush();
        }catch (IOException e){
            // 捕获并处理可能的IO异常
            e.printStackTrace();
        }

    }

}

利⽤PrintWriter找到我们熟悉的⽅法

上述,我们其实已经完成输出⼯作,但总是有所不⽅便,我们接来下将OutputStream处理下,使⽤ PrintWriter 类来完成输出,因为 PrintWriter 类中提供了我们熟悉的print/println/printf方法。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

public class Demo8 {
    /**
     * 程序的入口点 主要用于演示如何向文件写入文本内容
     * @param args 命令行参数,本例中未使用
     */
    public static void main(String[] args) {
        // 使用try-with-resources语句确保文件输出流在使用后能被正确关闭
        try(OutputStream outputStream=new FileOutputStream("./text.txt")){
            // 创建打印流,用于向文件输出流中写入可读的文本格式
            PrintStream printStream=new PrintStream(outputStream);
            // 向文件中写入并换行,以展示良好的文本格式
            printStream.println("你好");
            // 继续向文件中写入文本,不换行,以连续书写句子
            printStream.print("世界");
            // 使用格式化输出向文件中写入欢迎信息,增强文本的可读性
            printStream.printf("\n欢迎来到程序人生");
        }catch (IOException e){
            // 捕获并处理可能发生的IO异常,避免程序因未捕获异常而终止
            e.printStackTrace();
        }
    }

}

就此,我们关于如何以字节为单位对文件进行读取和写入操作就讲完了。

那么接下来,我们就来讲解如何以字符为单位对文件进行读取和写入操作,其实也就是对字节流的包装。


Reader读操作

Reader是一个抽象类,是所有字符输出流的父类

所以如果我们要使用,需要使用其子类。我们可以看到,直接继承Reader的类有:

这里我们讲的是文件操作,所以我们使用FileReader类 。

我们可以看到,其实看起来像是FileReader继承了inputStreamReader,但其实不是。根据 Java 文档,FileReader 是直接继承自 InputStreamReader 的父类 Reader,并且它内部使用了一个 InputStreamReader 实例来完成从文件到字符流的转换。

FileReader 类的目的是为了简化从文件中读取字符的过程。它使用平台默认的字符编码来创建一个 InputStreamReader,这样用户就不需要关心底层的编码细节。FileReader 提供了一个更简便的方式来读取文件中的文本数据。

创建Reader对象

那么我们就可以根据Fileder类来实例化一个Reader对象。

FileReader的构造方法

签名说明
FileReader(File  file)根据文件来构造FileReader对象。
FileReader(String name)根据文件路径来构造FileReader对象

这里我们选择第二种方法:

try(Reader reader=new FileReader("./text.txt")){
            //...
        }catch (IOException e){
            e.printStackTrace();
        }

 创建 Reader 的过程就是打开文件的过程,如果文件不存在就会创建失败。

理解Reader类中的不同read方法

当我们创建出Reader对象之后,那么我们就可以读取文件中的内容了。 

修饰符及返回值类型方法签名说明
intread()一次读取一个字符的数据,返回-1说明已经读完了
intread(char[] cbuf)一次读取若干个字符,直到把这个cbuf数组给填满
intread(char[] cbuf,int off,int len)一次读取若干个字符,并且将读取到的数据从 cbuf 的off位置开始,len长度的大小填充满
voidclose()关闭字符流

这里我们所需要的是字符,那么他为什么不用char作为返回值类型呢?

在java中,char类型是用来表示字符的,是一个16位的Unicode字符,可以有65536个不同的字节数。但是,我们read()需要能够返回一个特殊的值来表示文件结尾(EOF ,End and File)。所以,我们如果使用char类型,那么我们就无法表示这个特殊值。

为了解决这个问题,read()使用int作为返回类型。在java中,int是一个32位的整型,可以用来表示更大的范围。在Reader类中,read()返回废是一个int类型的值,其中0~65536表示实际的字符,而01表示文件结尾(EOF).

所以,在java中,Reader类的read()方法返回int类型是因为该方法需要能够表示所有可能的字符以及特殊值

既然知道了read()的返回值为什么是int后,那么为什么返回的int值是2个字节的长度而不是三个字节的长度?

在java标准库 ,对字符编码做了很多操作。

如果我们只使用char,此时的字符集就是Unicode,每个字符都是2个字节;

如果我们使用String,此时就会把字符的Unicode编码转为UTF-8,在utf-8中,一个英文字符是1个字节,一个中文字符是3个字节。

char[] c = {'a','b','c'};
String s = new String(c);

在上面代码中,c字符数组中一个字符占两个字节,但是如果将其转换为字符串的是,字符的编码方式就从Unicode转换为UTF-8编码了。

char cc=s.charAt(0);

当我们取其中一个字符,获取到的字符就会从UTF-8编码转化为Unicode编码。

使用Reader中的read()方法

1.read():一次读取一个字符,当到文件结尾会返回-1。

这个方法的返回值就是将读取到的字符转换为int类型。

Reader抽象类实现了Closeable接口,所以可以使用try-with-resources语句。 

示例:假设现在有文本文件text,在文本内存放:“你好”。

 

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo9 {
    /**
     * 程序的入口点
     * 该方法打开并读取本地文件系统中的文本文件,并将文件内容打印到控制台
     * 
     * @param args 命令行参数,本例中未使用
     */
    public static void main(String[] args) {
        // 尝试打开文件并创建一个Reader对象用于读取文件内容
        try(Reader reader=new FileReader("./text.txt")){
            // 循环读取文件中的每个字符,直到文件末尾
            while(true) {
                // 读取下一个字符,如果达到文件末尾,则返回-1
                int n = reader.read();
                // 如果达到文件末尾,则停止读取
                if(n==-1) break;
                // 打印读取的字符到控制台
                System.out.printf("%c",(char)n);
            }
        }catch (IOException e){
            // 捕获并处理可能的IO异常,例如文件不存在或无法读取
            e.printStackTrace();
        }
    }

}


2.read(char[] cbuf):一次读取多个字符,将字符读取到cbuf字符数组中。

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo10 {
    /**
     * 程序的入口点
     * 该方法用于读取指定文本文件的内容,并将其输出到控制台
     * 
     * @param args 命令行参数,本程序未使用
     */
    public static void main(String[] args) {
        // 尝试读取名为text.txt的文件内容
        try(Reader reader=new FileReader("./text.txt")) {
            // 创建一个能够一次读取1024个字符的字符数组
            char[] chars = new char[1024];
            // 读取文件内容到字符数组中
            int len = reader.read(chars);
            // 遍历已读取的字符数组,输出每个字符到控制台
            for(int i = 0; i < len; i++) {
                System.out.printf("%c", chars[i]);
            }
        } catch (IOException e) {
            // 如果在文件读取过程中发生错误,打印异常信息
            e.printStackTrace();
        }
    }

}

 

第三个方法与我们前面讲的InputStream中的read方法是一样的意思的,不过Reader中读取的是一个字符,而InputStream中read读取的是一个字节。

利用Scanner进行字符获取

对于上面这样读取字符,可能效率比较低,所以我们也可以使用Scanner来进行字符获取。

示例:现有一个文本内让内容如下,要求使用Scanner进行读取。

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;

public class Demo13 {
    /**
     * 程序的入口点
     * 该方法用于读取指定文本文件的内容,并将其输出到控制台
     *
     * @param args 命令行参数,本程序未使用
     */
    public static void main(String[] args) {
        // 尝试读取名为text.txt的文件内容
        try(Reader reader=new FileReader("./text.txt")) {
            Scanner scanner=new Scanner(reader);
            // 循环读取文件的每一行内容,并输出到控制台
            while(scanner.hasNext()){
                System.out.println(scanner.next());
            }
        } catch (IOException e) {
            // 如果在文件读取过程中发生错误,打印异常信息
            e.printStackTrace();
        }
    }

}

 


Writer写操作

Writer是一个抽象类,是所有字符输出流的父类。

 与Reader所讲的差不多,这里进行文件操作,我们需要使用FileWriter

创建Writer对象

FileWriter构造方法

签名说明
FileWriter(String name)根据文件路径来构造FileWriter对象
FileWriter(String name,boolean append)根据文件路径来构造FileWriter对象,若append为true,说明保留文件中的内容,不清除
FileWriter(File file)根据文件来构造FileWriter对象
FileWriter(File file,boolean append)根据文件来构造FileWriter对象,若append为true,说明保留文件中的内容,不清除

这里我们使用通过指定文件路径来创建Writer对象。

理解Writer中的write方法

此处的write方法其实与我们前面讲的OutputStream的write用法基本相同。不过此处比OutputStream的write多了两个额外的重载的方法,可以直接写入字符串,这样就方便多了。

当我们指定的文件不存在,那么Writer就会帮我们创建一个文件。

这里由于和OutputStream的write类似,所以我们只讲2个字节流中没有的。

使用Writer中的write方法

1.write(String str):给文件写入一个字符串。

示例:从控制台输入一段字符串并写入到text.txt文本文件中,(若文本不存在,会自动创建


import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Scanner;

public class Demo11 {
    /**
     * 程序的入口点
     * 从标准输入读取一行文本,并将其写入到文件中
     *
     * @param args 命令行参数,本程序中未使用
     */
    public static void main(String[] args) {
        // 创建Scanner对象,用于读取标准输入
        Scanner scanner=new Scanner(System.in);
        // 使用try-with-resources语句创建FileWriter对象,用于写入文件,自动管理资源
        try(Writer writer=new FileWriter("./text.txt")){
            // 读取标准输入中的一行文本
            String str=scanner.nextLine();
            // 将读取的文本写入文件
            writer.write(str);
        }catch (IOException e){
            // 捕获IOException,打印异常信息
            e.printStackTrace();
        }
    }

}

 运行中(等待输入):

 运行结束:

 


2.1.write(String str,int off ,int len):给文件写入一个从off位置开始,长度为len的字符串。

示例:这里我们接着上面的例子,拼接上一段字符串。

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Scanner;

public class Demo12 {
    public static void main(String[] args) {
        // 创建Scanner对象,用于读取标准输入
        Scanner scanner=new Scanner(System.in);
        // 使用try-with-resources语句创建FileWriter对象,用于写入文件,自动管理资源
        try(Writer writer=new FileWriter("./text.txt",true)){
            // 读取标准输入中的一行文本
            String str=scanner.nextLine();
            // 将读取的文本写入文件
            writer.write(str,2,2);
        }catch (IOException e){
            // 捕获IOException,打印异常信息
            e.printStackTrace();
        }
    }
}

运行中(待输入):

 

运行后:


既然我们已经学习了文件的基本操作+文件内容的读写操作,那么就下来,我们就可以来实现一些小工具来锻炼我们的能力。

运用文件操作和IO实现一个删除指定文件的功能

要求:实现一个扫描指定目录,并且找到名称中包含指定字符的所有普通文件(不包括目录),并且后序访问用户是否要删除该文件。

思路:根据要求,我们要在一个指定的目录下寻找包含指定字符的文件,并询问是否要删除。我们知道文件底层其实是使用树形结构,那么我们就需要使用递归,当遇到目录的时候,就继续递归;当递归遇到文件的时候,就判断这个文件是否包含用户指定的字符。如果包含那么进行询问是否要删除。

代码实现:

import java.io.File;
import java.util.Scanner;

/**
 * Demo类用于演示如何在指定目录中搜索并删除包含特定关键词的文件
 */
public class Demo {
    /**
     * 程序的入口点
     * @param args 命令行参数,未使用
     */
    public static void main(String[] args) {
        // 创建Scanner对象,用于读取用户输入
        Scanner sc = new Scanner(System.in);
        // 提示用户输入目录路径
        System.out.println("请输入你要扫描的目录路径:");
        // 读取用户输入的目录路径
        String path=sc.next();
        // 根据用户输入的路径创建File对象
        File root=new File(path);
        // 检查所给路径是否为目录
        if(!root.isDirectory()){
            System.out.println("输入的路径不是目录");
            return;
        }
        // 提示用户输入要删除的文件的关键词
        System.out.println("请输入你要删除的文件的关键词:");
        // 读取用户输入的关键词
        String dest=sc.next();

        // 调用fileSearch方法,在指定目录中搜索包含关键词的文件
        fileSearch(root,dest);
    }

    /**
     * 递归搜索指定目录下的文件,并对匹配关键词的文件进行删除确认
     * @param root 搜索的根目录
     * @param dest 要匹配并删除的文件的关键词
     */
    private static void fileSearch(File root, String dest) {
        // 列出目录下的所有文件和子目录
        File[] files=root.listFiles();
        // 如果该目录为空,则直接返回
        if(files==null) return;
        // 遍历目录中的每个文件或子目录
        for(File file:files){
            // 如果是文件,则检查是否匹配关键词并进行删除确认
            if(file.isFile()){
                doDelete(file,dest);
            }else{
                // 如果是目录,则递归搜索
                fileSearch(file,dest);
            }
        }
    }

    /**
     * 检查文件名是否包含关键词,如果包含则提示用户是否删除
     * @param file 待检查的文件
     * @param dest 关键词
     */
    private static void doDelete(File file, String dest) {
        // 检查文件名是否包含关键词
        if(file.getName().contains(dest)){
            // 提示用户找到匹配文件,并询问是否删除
            System.out.println("当前文件为:"+file.getAbsoluteFile()+", 是否要删除(Y/N)?");
            // 创建Scanner对象,用于读取用户输入
            Scanner sc=new Scanner(System.in);
            // 读取用户的删除选择
            String choice=sc.next();
            if(choice.equals("Y")){
                // 如果用户选择删除,则执行删除操作
                file.delete();
                System.out.println("删除成功");
            }else{
                System.out.println("取消删除");
            }
        }
    }
}

 

复制指定文件

进⾏普通⽂件的复制,通过传入一个要被复制的文件的路径,判断是否是文件,如果是文件,则进行下一步操作;反之,则直接返回。输入要复制到的文件路径,判断该文件是否存在,如果存在就需要判断是否要进行覆盖;反之则直接返回。通过字节流来复制。

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

/**
 * Demo1类用于演示文件复制的功能
 */
public class Demo1 {
    /**
     * 主函数,用于处理文件复制的流程
     * @param args 命令行参数,未使用
     */
    public static void main(String[] args) {
        // 创建Scanner对象,用于从命令行读取用户输入
        Scanner sc = new Scanner(System.in);
        
        // 提示用户输入源文件路径,并读取
        System.out.println("请输入你要复制的文件的路径:");
        String src = sc.next();
        // 根据输入的路径创建File对象
        File scFile = new File(src);
        
        // 检查源文件是否存在
        if (!scFile.exists()) {
            System.out.println("您输入的文件不存在,请确认文件路径的正确性");
            return;
        }
        
        // 检查源文件是否为文件夹
        if (scFile.isDirectory()) {
            System.out.println("您输入的是一个文件夹,请输入一个文件路径");
            return;
        }
        
        // 提示用户输入目标文件路径,并读取
        System.out.println("请输入你要复制到的目标路径:");
        String dest = sc.next();
        // 根据输入的目标路径创建File对象
        File destFile = new File(dest);
        
        // 检查目标路径是否已存在文件
        if (destFile.exists()) {
            // 如果目标路径是一个目录而不是文件,则提示错误
            if (destFile.isDirectory()) {
                System.out.println("您输入的路径是一个目录,不是文件,请确认文件路径是否正确");
                return;
            }
        }
        
        // 如果源文件是文件,则进行以下操作
        if (scFile.isFile()) {
            // 如果目标文件已存在,则询问用户是否覆盖
            if (destFile.exists()) {
                System.out.println("您要复制到的文件已经存在,是否需要覆盖:Y/N");
                String choice = sc.next();
                // 如果用户选择不覆盖,则退出程序
                if (!choice.equalsIgnoreCase("Y")) return;
            }
        }

        // 使用try-with-resources语句复制文件内容
        try (InputStream inputStream = new FileInputStream(scFile);
             OutputStream outputStream = new FileOutputStream(destFile)) {
            // 创建字节数组用于读取文件内容
            byte[] bytes = new byte[1024];
            // 循环读取源文件内容直到末尾
            while (true) {
                // 读取字节数组的下一个块
                int n = inputStream.read(bytes);
                // 如果读取到文件末尾则退出循环
                if (n == -1) break;
                // 将读取的内容写入目标文件
                outputStream.write(bytes, 0, n);
            }
        } catch (IOException e) {
            // 处理异常
            e.printStackTrace();
        }
    }
}

示例:复制图片

现有一张图片,我们想要给他复制一张那么我们就得找到其相应的文件路径

输入对应的文件路径,并指定一个目标路径

 

在指定目录中查找文件名或文件内容含有关键词的文件

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

public class Demo2 {
    /**
     * 主函数,用于交互式输入目录和关键词,并打印出包含关键词的文件
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你要扫描的目录:");
        String root = sc.next();
        File rootFile = new File(root);
        if (!rootFile.exists()) {
            System.out.println("目录不存在");
            return;
        }
        if (!rootFile.isDirectory()) {
            System.out.println("您输入的路径不是正确的目录路径,请检查你输入的路径");
            return;
        }
        System.out.println("请输入你要查找的文件名或者文件内容中包含中包含的关键词:");
        String word = sc.next();

        List<File> list = new ArrayList<>();
        Serach(rootFile, word, list);
        for (File file : list) {
            System.out.println(file.getAbsoluteFile());
        }
    }

    /**
     * 递归搜索目录下包含特定关键词的文件
     * @param rootFile 根目录文件对象
     * @param word 关键词
     * @param list 保存匹配文件的列表
     */
    private static void Serach(File rootFile, String word, List<File> list) {
        File[] files = rootFile.listFiles();
        if (files == null) return;
        for (File file : files) {
            if (file.isDirectory()) {
                Serach(file, word, list);
            } else {
                if (isContainsContent(file, word)) {
                    list.add(file);
                }
            }
        }
    }

    /**
     * 检查文件是否包含特定内容
     * @param file 待检查的文件
     * @param word 关键词
     * @return 如果文件名或文件内容包含关键词,返回true;否则返回false
     */
    private static boolean isContainsContent(File file, String word) {
        if (file.getName().contains(word)) return true;
        StringBuilder sb = new StringBuilder();
        try (InputStream inputStream = new FileInputStream(file)) {
            Scanner scanner = new Scanner(file);
            while (scanner.hasNextLine()) {
                sb.append(scanner.nextLine());
                if (sb.indexOf(word) != -1) return true;
            }
            return false;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
}


以上就是本篇IO操作的所有内容啦,若有不足欢迎指正~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2048171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机毕业设计选题推荐-在线学习平台-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Linux - 权限

文章目录 一、用户二、文件 一、用户 1、Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以再linux系统下做任何事情&#xff0c;不受限制 。 普通用户&#xff1a;在linux下做有限的事情。 超级用户的命令提示符是“…

Python数据分析实战:从零开始构建销售预测模型

随着大数据时代的到来&#xff0c;数据分析已经成为企业决策的重要依据之一。Python作为一种强大的编程语言&#xff0c;在数据分析领域有着广泛的应用。本文将通过一个具体的案例——销售预测——来演示如何使用Python进行数据预处理、特征工程、模型构建与评估。无论你是Pyth…

【持续更新】Adoobe Afteer Effeects 2024 v24.5.0.052最新免费修改版

利用Adoobe Afteer Effeects CC&#xff0c;您可以轻松打造生动的动画标题、字幕以及下三分屏元素。无论是从零开始还是借助应用内丰富的预设动画&#xff0c;都能让您的文字以各种方式动起来&#xff0c;无论是旋转、滑动或是平移&#xff0c;创意无限。 将视频和图像融合在一…

负载均衡、高可用

负载均衡 负载均衡&#xff08;Load Balance&#xff09;&#xff1a;可以利用多个计算机和组合进行海量请求处理&#xff0c;从而获得很高的处理效率&#xff0c;也可以用多个计算机做备份&#xff08;高可用&#xff09;&#xff0c;使得任何一个机器坏了整个系统还是能正常…

华为HCIP证书好考吗?详解HCIP证书考试难易程度及备考策略!

华为认证体系主要分为三个层次&#xff1a;HCIA(Huawei Certified ICT Associate)&#xff0c;HCIP(Huawei Certified ICT Professional)和HCIE(Huawei Certified Internetwork Expert)。作为中级认证&#xff0c;HCIP证书主要面向具备一定技术基础和项目实践能力的专业人士。在…

SQL 数据库设计、事务、视图 <13>

一、数据库设计 1.多表之间的关系 1&#xff09; 一对一&#xff08;了解&#xff09; 如&#xff1a;人和身份证 分析&#xff1a;一个人只有一个身份证&#xff0c;一个身份证只能对应一个人 2&#xff09;一对多&#xff08;多对一&#xff09; 如&#xff1a;部门和员…

【C语言】深入讲解指针(上)

文章目录 前言字符指针指针数组数组指针数组指针的定义&数组名和数组名数组指针的使用 指针和数组传参一维数组传参二维数组传参一级指针传参二级指针传参 结束 前言 之前我们初步了解了指针的概念&#xff0c;没有看过的大家可以移步到【C语言】初阶指针详解&#xff0c;…

手撕C++入门基础

1.C介绍 C课程包括&#xff1a;C语法、STL、高阶数据结构 C参考文档&#xff1a;Reference - C Reference C 参考手册 - cppreference.com cppreference.com C兼容之前学习的C语言 2.C的第一个程序 打印hello world #define _CRT_SECURE_NO_WARNINGS 1 // test.cpp // …

day02--HTML CSS

一、HTML表单 表单的作用是用于采集用户再页面上填入的数据&#xff0c;并发送给后端服务器&#xff0c;经常用于用户注册、登录、xx信息添加、xx信息修改 1.1表单 1、input表示文本框 type属性&#xff1a;负责配置不同的输入框类型 text&#xff1a;普通文本框 password&…

服务器数据恢复—raid5阵列离线硬盘强制上线失败如何恢复数据?

服务器数据恢复环境&#xff1a; 某品牌2850服务器上有一组由6块SCSI硬盘组建的raid5磁盘阵列&#xff0c;上层操作系统为Redhat linuxext3文件系统。 服务器故障&初检&#xff1a; 服务器在运行过程中突然瘫痪&#xff0c;管理员对服务器中的raid进行检查后发现有两块硬盘…

P3572 [POI2014] PTA-Little Bird

[POI2014] PTA-Little Bird - 洛谷 核心思路 注意力惊人。 注意到&#xff0c;只有两种决策 选高过自己的树中代价最小的 或者 选低于自己的树种代价的最小的1。 取最小值 显然 不等式&#xff1a; 恒成立。 由此&#xff0c;维护一个优先队列即可。 AC 代码 #includ…

宠物空气净化器推荐买吗?清除浮毛的效果好吗

家里养了两只哈基米&#xff0c;它们每天的日常就是早上跑酷、中午跑酷、晚上还在跑酷&#xff0c;有时候看着很好玩&#xff0c;每天都活蹦乱跳的&#xff0c;这在说明它们很健康&#xff0c;我把它们养得很好&#xff0c;所以原谅它们经常跑跑跳跳得行为&#xff0c;虽然会把…

库室联管联控系统DW-S306|是一套智能化系统

装备库室联管联控系统&#xff08;DW-S306&#xff09;是依托互3D技术、RFID技术、数据库技术、AI、视频分析技术对库室装备进行统一管理、分析的信息化、智能化、规范化的系统。 本解决方案利用现有内部网络&#xff0c;部署部队装备库室联管联控系统&#xff0c;形成一套上下…

ai大模型之争-落地场景分析20240817

大模型之争&#xff1a; 目标&#xff1a;吸引客户的注意力&#xff0c;养成客户习惯&#xff0c;占领市场 结语 对于研发人员而言&#xff1a;浏览器插件&#xff0c;开发工具的大模型&#xff1a;通义灵码真是效率倍增的神器 对非研发普通人而言&#xff1a;增加很多便捷工…

DolphinScheduler集群部署问题(趟坑)总结

目录 官方文档 官方项目地址 问题解决 官方文档 DolphinScheduler | 文档中心 (apache.org) 官方项目地址 部署及使用过程中的问题可以参见项目Issue:Issues apache/dolphinscheduler GitHub GitHub - apache/dolphinscheduler at 3.2.2-release 问题解决 1、JVM在运…

ThreejsWebGPU运动残影demo

功能点 实例化SkinnedMesh 修改NodeMaterial着色器 节点材质系统 shader 语言 使用uniform和attribute 中合其他几篇博客中的内容 代码仓库 克隆后需要放到three源码同级别目录下 运行 three源码部分不在git仓库中(太大了) 使用vscode的live-server启动后访问 http://127.0.0.…

HarmonyOS NEXT - Navigation组件封装BaseNavigation

demo 地址: https://github.com/iotjin/JhHarmonyDemo 代码不定时更新&#xff0c;请前往github查看最新代码 在demo中这些组件和工具类都通过module实现了&#xff0c;具体可以参考HarmonyOS NEXT - 通过 module 模块化引用公共组件和utils 官方介绍 组件导航 (Navigation)(推…

​【迅为电子】RK3568驱动指南|第十七篇 串口-第197章 串口通信协议

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

vue3 ts 集成 supermap

文章目录 1. 依赖安装2. 模块声明3. css 全局引入4. 地图加载 1. 依赖安装 npm install supermapgis/iclient-leafletnpm install leaflet2. 模块声明 env.d.ts declare module leaflet {const L: any;export default L; }declare module supermapgis/iclient-leaflet3. css …