【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解

news2024/11/30 2:40:44

目录

1、FileChannel

(1)获取 FileChannel 

(2)读取文件

(3)写入文件

(4)关闭通道

(5)当前位置与文件大小

(6)强制写入磁盘

2、两个 FileChannel 之间的数据传输

(1)使用 transferTo() 进行文件传输

(2)处理大于 2GB 的文件

(3)为什么说 transferTo() 高效?

3、文件的操作

3.1、Path类

(1)创建 Path 实例

(2)目录中的特殊符号:. 和 ..

3.2、Files

(1)检查文件是否存在

(2)创建一级目录

(3)创建多级目录

(4)拷贝文件

(5)移动文件

(6)删除文件或目录

(7)遍历目录文件

(8)统计 .jar 文件数量

(9)删除多级目录

(10)拷贝多级目录

1、FileChannel

        在 Java 的 NIO(New I/O)框架中,FileChannel 是一个强大的工具,它能让我们更高效地操作文件,但它和传统的 I/O 操作方式有很多不同。下面,让我们一起了解如何通过 FileChannel 来高效的进行文件的读写操作。

        注意:一个需要注意的地方是,FileChannel 只能在阻塞模式下工作。虽然 NIO 中的大部分组件都支持非阻塞模式(比如 SocketChannel),但 FileChannel 并不支持。换句话说,它会像传统 I/O 一样在读写时等待操作完成。

(1)获取 FileChannel 

FileChannel 不能被直接创建 ,而是要通过以下几种方式间接获取:

  1. FileInputStream 通过它获取的 FileChannel 只能用于读操作。
  2. FileOutputStream 通过它获取的 FileChannel 只能用于写操作。
  3. RandomAccessFile 这个类比较特殊,它可以同时支持读写,具体取决于你在创建 RandomAccessFile 时的模式。

示例:

FileInputStream fis = new FileInputStream("data.txt");
FileChannel readChannel = fis.getChannel(); // 只读

FileOutputStream fos = new FileOutputStream("data.txt");
FileChannel writeChannel = fos.getChannel(); // 只写

RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
FileChannel readWriteChannel = raf.getChannel(); // 读写

(2)读取文件

        从 FileChannel 读取数据需要借助 ByteBuffer。当我们调用 read() 方法时,它会把数据读入 ByteBuffer,并返回读到的字节数。如果返回值是 -1,则表示文件已经读取到末尾。

示例:

ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);

if (bytesRead == -1) {
    System.out.println("文件已读完");
}

(3)写入文件

        写入文件时,我们也需要通过 ByteBuffer。先把数据写入 ByteBuffer,然后再通过 write() 方法把 ByteBuffer 中的数据写入 FileChannel。但有个特别需要注意的地方是,write() 方法不能保证会一次性把整个 ByteBuffer 的内容全部写完如果 ByteBuffer 中的数据量大于操作系统或通道能够处理的最大写入量,write() 方法将只能写入部分数据。,因此需要循环调用 write()

示例:

ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello, FileChannel".getBytes());
buffer.flip();  // 切换成读模式

while (buffer.hasRemaining()) {
    channel.write(buffer);
}

        这里的 flip() 很重要(上一篇博客有讲到),因为它把 ByteBuffer 从写模式切换到了读模式,确保我们可以把数据从 ByteBuffer 中读出来并写入文件。

(4)关闭通道

        使用完 FileChannel 后,务必要记得关闭它。但好消息是,如果我们关闭了对应的 FileInputStreamFileOutputStream 或者 RandomAccessFile,它们会自动关闭 FileChannel,我们不用额外再关闭一次。

(5)当前位置与文件大小

        可以通过 position() 方法获取当前的文件指针位置,也可以通过 position(long newPos) 方法来设置它的位置。如果把位置设置到文件末尾,再进行写操作时,新的内容会直接追加在文件末尾。如果位置超过了文件末尾,会在新内容和末尾之间填充空白数据。

long pos = channel.position();  // 获取当前位置
channel.position(pos + 1024);   // 移动到当前位置之后的 1024 字节处

        通过 size() 方法获取文件的大小。

long fileSize = channel.size();
System.out.println("文件大小: " + fileSize);

(6)强制写入磁盘

在写文件时,操作系统通常会先将数据缓存起来,而不是立即写入磁盘。如果你需要确保数据即时写入磁盘,可以调用 force(true) 方法。该方法会将文件内容和元数据(如权限信息)立即写入磁盘,防止数据丢失。

2、两个 FileChannel 之间的数据传输

        当我们需要在文件之间进行拷贝时,NIO 提供了一种高效的方式,利用操作系统底层的零拷贝技术,大大提升了传输速度。就是使用两个 FileChannel 进行文件的拷贝操作。

(1)使用 transferTo() 进行文件传输

        FileChannel 提供了两个非常实用的方法来进行文件传输:transferTo()transferFrom()。这两个方法让我们可以高效地从一个通道复制数据到另一个通道。下面是一个简单的例子,展示了如何通过 transferTo() 方法把一个文件的内容传输到另一个文件。

String FROM = "helloword/data.txt";  // 源文件路径
String TO = "helloword/to.txt";      // 目标文件路径
long start = System.nanoTime();      // 记录开始时间

try (FileChannel from = new FileInputStream(FROM).getChannel();  // 获取源文件的通道
     FileChannel to = new FileOutputStream(TO).getChannel();     // 获取目标文件的通道
    ) {
    from.transferTo(0, from.size(), to);  // 通过 transferTo 方法传输数据
} catch (IOException e) {
    e.printStackTrace();
}

long end = System.nanoTime();  // 记录结束时间
System.out.println("transferTo 用时:" + (end - start) / 1000_000.0);  // 输出耗时

输出结果会显示传输的时间

transferTo 用时:8.2011

        transferTo() 方法可以让我们在两个 FileChannel 之间直接传输数据。参数 0 表示从文件开头开始传输,from.size() 表示传输的字节数等于源文件的大小。它利用了底层的操作系统机制(如 Linux 中的 sendfile()),因此在大文件传输时具有非常高的效率。

(2)处理大于 2GB 的文件

        对于小文件,上面那样的传输是没有问题的。但是,如果我们要处理超过 2GB 的大文件,transferTo() 可能会遇到一些限制,特别是在 32 位的系统中或者由于操作系统的内部限制。因此,我们需要进行分段传输。

以下代码展示了如何处理超过 2GB 的大文件:

public class TestFileChannelTransferTo {
    public static void main(String[] args) {
        try (
                FileChannel from = new FileInputStream("data.txt").getChannel();
                FileChannel to = new FileOutputStream("to.txt").getChannel();
        ) {
            long size = from.size();  // 获取文件大小
            // left 变量表示剩余未传输的字节数
            for (long left = size; left > 0; ) {
                System.out.println("position:" + (size - left) + " left:" + left);
                // transferTo 支持的最大传输量是 2GB,因此我们用循环分段传输
                left -= from.transferTo((size - left), left, to);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  1. 由于 transferTo() 的一次传输量限制为 2GB(2147483647 字节),我们通过一个循环进行多次传输,直到传输完所有数据。每次调用 transferTo() 后,会更新剩余的字节数,直到 left 变为 0 表示传输完成。
  2. 这个方法非常高效,因为它利用了操作系统的零拷贝机制,避免了不必要的上下文切换和数据拷贝。

(3)为什么说 transferTo() 高效?

        transferTo() 之所以高效,是因为它通过操作系统的低层 API 直接进行文件的传输,避免了从用户空间到内核空间的多次数据拷贝(零拷贝)。

(一)传统的 I/O 读写操作

        在传统的 I/O 读写操作中,数据从一个文件传输到另一个文件通常需要经过如下步骤:

  1. 从磁盘读取数据到内核缓冲区: 操作系统首先从磁盘中读取数据,存放在操作系统的内核缓冲区(Kernel Buffer)中。

  2. 从内核缓冲区拷贝到用户空间: 应用程序从内核缓冲区中读取数据,并将其拷贝到用户空间的缓冲区(User Buffer),这是一个从操作系统到应用程序层的数据传递。

  3. 从用户空间写回内核缓冲区: 应用程序在获取到数据后,再将数据传回给另一个文件的内核缓冲区,即从用户空间的缓冲区拷贝回内核缓冲区,这里再次发生一次拷贝。

  4. 从内核缓冲区写入目标磁盘: 最后,操作系统将数据从目标文件的内核缓冲区写入到目标磁盘。

在这种情况下,数据从磁盘到磁盘的传输经历了 4 次拷贝2 次上下文切换(应用程序和内核之间的切换),这导致了性能损耗。

(二)使用 transferTo() 或 transferFrom()

        transferTo() 或 transferFrom() 的优化过程主要是依赖于零拷贝技术,省略了不必要的用户空间数据拷贝。使用这两个方法时,操作系统直接将数据从一个文件的内核缓冲区传输到另一个文件的内核缓冲区,过程如下:

  1. 从源文件读取数据到内核缓冲区: 操作系统从源文件中读取数据,直接存放在源文件的内核缓冲区中。

  2. 通过 DMA 将数据从内核缓冲区传输到目标缓冲区: 操作系统使用 DMA(Direct Memory Access) 机制,将数据从源文件的内核缓冲区直接传输到目标文件的内核缓冲区,而无需经过用户空间。这一步通过操作系统内核中的数据传输通道完成。

  3. 将数据从目标缓冲区写入磁盘: 最后,操作系统将目标缓冲区中的数据写入到目标磁盘。

这样,整个过程中只发生了 2 次拷贝1 次上下文切换,并且跳过了用户空间的数据传输,从而大幅减少了 CPU 的参与,降低了 I/O 操作的成本,特别是在处理大文件时,这种优化表现非常显著。

(3)零拷贝的操作系统支持

  1. Linux:在 Linux 中,transferTo()transferFrom() 底层通过 sendfile() 系统调用实现,它允许文件描述符之间直接传输数据,避免了数据拷贝到用户空间的过程。

  2. Windows:在 Windows 操作系统中,类似的功能是通过 TransmitFile() 系统调用来实现的,提供了类似的零拷贝优化。

总结比较:

  • 传统 I/O:4 次拷贝2 次上下文切换
  • transferTo() / transferFrom()2 次拷贝1 次上下文切换

3、文件的操作

3.1、Path类

        Path类是在 JDK 7 中引入的一个新类,它用于替代之前繁琐的 File 类来处理文件路径。相较于 File 类,Path 提供了更灵活的方式来表示和操作文件路径。与之配套的还有 Paths 工具类,它帮助我们更便捷地创建 Path 实例。下面,是一些简单的 Path 用法的例子。

(1)创建 Path 实例

使用 Paths.get() 来获取 Path 实例,可以通过传入不同形式的路径来表示文件的位置:

Path source = Paths.get("1.txt"); 

这个表示相对路径,它使用的是当前项目目录作为起点(可以通过 System.getProperty("user.dir") 查看当前项目所在目录)。

如果你想指定一个绝对路径,直接写绝对路径即可:

Path source = Paths.get("d:\\1.txt"); // 代表 d:\1.txt
Path source = Paths.get("d:/1.txt");  // 斜杠也可以正常使用

另外,Paths.get() 也可以通过多个参数来拼接路径:

Path projects = Paths.get("d:\\data", "projects");  // 这里的代码相当于 d:\data\projects。

(2)目录中的特殊符号:. 和 ..

  • . 表示当前目录。
  • .. 表示上一级目录。

假设你有以下目录结构:

d:
	|- data
		|- projects
			|- a
			|- b

如果你想表示 b 目录,但从 a 目录“退回”一步再进入 b,你可以写成:

Path path = Paths.get("d:\\data\\projects\\a\\..\\b");
System.out.println(path);

这段代码输出的是 d:\data\projects\a\..\b,看起来还不是很直观。不过,Path 提供了一个 normalize() 方法,它可以将路径中的 .. 等符号“正常化”:

System.out.println(path.normalize());

经过 normalize() 处理后,输出就变成了 d:\data\projects\b,清晰地展示了最终的路径。

3.2、Files

        Files 类是 NIO 中一套强大的文件操作工具,无论是检查文件是否存在、创建目录、拷贝文件,还是删除文件,Files 类都可以轻松搞定。

(1)检查文件是否存在

        使用 Files.exists() 可以轻松判断某个文件或目录是否存在。

Path path = Paths.get("helloword/data.txt");
System.out.println(Files.exists(path)); // true 表示文件存在,false 表示文件不存在

(2)创建一级目录

        如果目录已经存在,会抛出 FileAlreadyExistsException,同时 createDirectory() 方法不能一次创建多级目录。如果父目录不存在,将抛出 NoSuchFileException

Path path = Paths.get("helloword/d1");
Files.createDirectory(path); // 创建单一的一级目录

(3)创建多级目录

        与 createDirectory() 不同,createDirectories() 方法会自动创建不存在的父目录,非常方便。

Path path = Paths.get("helloword/d1/d2");
Files.createDirectories(path); // 创建多级目录

(4)拷贝文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target); // 拷贝文件,如果目标文件已存在,会抛 FileAlreadyExistsException

        如果希望在目标文件已存在时进行覆盖,可以使用 StandardCopyOption.REPLACE_EXISTING

Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); // 覆盖已有文件

(5)移动文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data_moved.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE); // 原子性文件移动

        StandardCopyOption.ATOMIC_MOVE 选项确保文件移动的操作是原子的,也就是说,要么移动成功,要么不做任何更改。

(6)删除文件或目录

删除文件

Path target = Paths.get("helloword/target.txt");
Files.delete(target); // 删除文件,如果文件不存在,会抛 NoSuchFileException

        注意:如果文件不存在,Files.delete() 会抛出 NoSuchFileException。如果不确定文件是否存在,可以使用 Files.deleteIfExists() 来避免异常抛出。

删除目录

Path target = Paths.get("helloword/d1");
Files.delete(target); // 删除空目录

        注意:如果目录不为空,会抛出 DirectoryNotEmptyException,需要先删除目录中的文件。

(7)遍历目录文件

        使用 Files.walkFileTree() 可以递归遍历目录中的所有文件。以下代码将遍历指定目录,并统计文件和目录的数量:

public static void main(String[] args) throws IOException {
    Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"); // 要遍历的根目录
    AtomicInteger dirCount = new AtomicInteger(); // 记录目录数量
    AtomicInteger fileCount = new AtomicInteger(); // 记录文件数量

    // 遍历目录和文件
    Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            System.out.println(dir); // 打印目录
            dirCount.incrementAndGet(); // 统计目录数量
            return super.preVisitDirectory(dir, attrs);
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            System.out.println(file); // 打印文件
            fileCount.incrementAndGet(); // 统计文件数量
            return super.visitFile(file, attrs);
        }
    });

    System.out.println("目录数:" + dirCount); // 输出目录总数
    System.out.println("文件数:" + fileCount); // 输出文件总数
}

(8)统计 .jar 文件数量

        假设我们想要统计某个目录中 .jar 文件的数量,可以在 visitFile 方法中添加对文件后缀的判断:

Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");
AtomicInteger fileCount = new AtomicInteger();

Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        if (file.toFile().getName().endsWith(".jar")) { // 判断文件是否为 .jar 结尾
            fileCount.incrementAndGet();
        }
        return super.visitFile(file, attrs);
    }
});

System.out.println("JAR 文件数:" + fileCount); // 输出 .jar 文件总数

(9)删除多级目录

        我们可以使用 Files.walkFileTree() 来递归删除目录及其内容。以下代码演示了如何递归删除指定路径下的所有文件和目录:

Path path = Paths.get("d:\\a");
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        Files.delete(file); // 删除文件
        return super.visitFile(file, attrs);
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        Files.delete(dir); // 删除空目录
        return super.postVisitDirectory(dir, exc);
    }
});

        注意删除操作的风险:删除是一个非常危险的操作,特别是递归删除目录时,务必要确保该目录中的文件没有重要数据。

(10)拷贝多级目录

想要递归地拷贝整个多级目录,以下代码演示了如何遍历目录并进行拷贝操作:

long start = System.currentTimeMillis();
String source = "D:\\Snipaste-1.16.2-x64"; // 源目录
String target = "D:\\Snipaste-1.16.2-x64_copy"; // 目标目录

// 遍历源目录
Files.walk(Paths.get(source)).forEach(path -> {
    try {
        String targetName = path.toString().replace(source, target); // 将源路径转换为目标路径

        if (Files.isDirectory(path)) { // 如果是目录
            Files.createDirectory(Paths.get(targetName)); // 创建目录
        } else if (Files.isRegularFile(path)) { // 如果是文件
            Files.copy(path, Paths.get(targetName)); // 拷贝文件
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});

推荐: 

【NIO基础】NIO(非阻塞 I/O)和 IO(传统 I/O)的区别,以及 NIO 的三大组件详解-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/m0_65277261/article/details/142662308?spm=1001.2014.3001.5501【Redis】Redis中的 AOF(Append Only File)持久化机制-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/m0_65277261/article/details/142661193?spm=1001.2014.3001.5501

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

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

相关文章

HTML的修饰(CSS) -- 第三课

文章目录 前言一、CSS是什么&#xff1f;二、使用方式1. 基本语法2. 引入方式1.行内式2.内嵌式3. 链入式 3. 选择器1. 标签选择器2.类选择器3. id选择器4. 通配符选择器 4. css属性1. 文本样式属性2. 文本外观属性 5. 元素类型及其转换1. 元素的类型2. 元素的转换 6.css高级特性…

isinstance()学习

aa {} if isinstance(aa,dict):print("是")aa 2 if isinstance(aa,dict):print("是")aa 2 if isinstance(aa,int):print("是")aa [] if isinstance(aa,list):print("list")aa [1,2,3] if isinstance(aa,list):print("list"…

模拟算法(4)_外观数列

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 模拟算法(4)_外观数列 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 题目链…

选择排序:直接选择排序、堆排序

目录 直接选择排序 1.选择排序的基本思想 2.直接选择排序的基本思想 3.直接插入排序的代码思路步骤 4.直接选择排序代码 5.直接选择排序的特性总结 堆排序 一、排升序&#xff0c;建大堆 1.利用向上调整函数建大堆 1.1.建立大堆的思路 1.2.以下是具体步骤&#xff1a…

Android Framework AMS(01)AMS启动及相关初始化1-4

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要涉及systemserver启动AMS及初始化AMS相关操作。同时由于该部分内容分析过多&#xff0c;因此拆成2个章节&#xff0c;本章节是第一章节&…

Solidity 存储和内存管理:深入理解与高效优化

在 Solidity 中&#xff0c;存储和内存管理是编写高效智能合约的关键组成部分。合约执行的每一步操作都可能涉及到数据的存储和读取&#xff0c;而这些操作对 gas 的消耗有很大影响。因此&#xff0c;理解 Solidity 的存储模型以及如何优化数据的管理对于合约的安全性、性能和成…

pytorch之梯度累加

1.什么是梯度&#xff1f; 梯度可以理解为一个多变量函数的变化率&#xff0c;它告诉我们在某一点上&#xff0c;函数的输出如何随输入的变化而变化。更直观地说&#xff0c;梯度指示了最优化方向。 在机器学习中的作用&#xff1a;在训练模型时&#xff0c;我们的目标是最小…

day2网络编程项目的框架

基于终端的 UDP云聊天系统 开发环境 Linux 系统GCCUDPmakefilesqlite3 功能描述 通过 UDP 网络使服务器与客户端进行通信吗&#xff0c;从而实现云聊天。 Sqlite数据库 用户在加入聊天室前&#xff0c;需要先进行用户登录或注册操作&#xff0c;并将注册的用户信息&#xf…

P4、P4D、HelixSwarm 各种技术问题咨询

多年大型项目P4仓库运维经验&#xff0c;为你解决各种部署以及标准工业化流程问题。 Perforce 官网SDPHelixCore GuideHelixSwarm GuideHelixSwarm Download

SpringBoot基础(三):Logback日志

SpringBoot基础系列文章 SpringBoot基础(一)&#xff1a;快速入门 SpringBoot基础(二)&#xff1a;配置文件详解 SpringBoot基础(三)&#xff1a;Logback日志 目录 一、日志依赖二、日志格式1、记录日志2、默认输出格式3、springboot默认日志配置 三、日志级别1、基础设置2、…

家长们,你们认为孩子沉迷游戏严重还是沉迷Linux严重呢

matrix禁食 ​ 计算机技术与软件专业技术资格证持证人 ​ 关注 谢邀 Hieronymus no-sh 218 人赞同了该回答 十年前&#xff0c;你还能得到一个自己能控制的计算机系统&#xff0c;现在&#xff0c;窗口期早走过了。普通人不懂软件&#xff0c;但因该懂人心啊&#xff0c;人心一…

使用Apifox创建接口文档,部署第一个简单的基于Vue+Axios的前端项目

前言 在当今软件开发的过程中&#xff0c;接口文档的创建至关重要&#xff0c;它不仅能够帮助开发人员更好地理解系统架构&#xff0c;还能确保前后端开发的有效协同。Apifox作为一款集API文档管理、接口调试、Mock数据模拟为一体的工具&#xff0c;能够大幅度提高开发效率。在…

武汉自闭症儿童寄宿学校:开启学习与成长的新篇章

武汉与广州的自闭症教育之光&#xff1a;星贝育园开启学习与成长新篇章 在自闭症儿童教育的广阔领域&#xff0c;寄宿学校以其独特的教育模式和全方位的关怀&#xff0c;为这些特殊孩子提供了学习、成长与融入社会的宝贵机会。虽然本文标题提及了武汉自闭症儿童寄宿学校&#…

【HTML+CSS】仿电子美学打造响应式留言板

创建一个响应式的留言板 在这篇文章中&#xff0c;我们将学习如何创建一个简单而美观的留言板&#xff0c;它将包括基本的样式和动画效果&#xff0c;以及响应式设计&#xff0c;确保在不同设备上都能良好显示。 HTML 结构 首先&#xff0c;我们创建基本的HTML结构。留言板由…

8646 基数排序

### 思路 基数排序是一种非比较型排序算法&#xff0c;通过逐位&#xff08;从最低位到最高位&#xff09;对数字进行排序。每次分配和收集后输出当前排序结果。 ### 伪代码 1. 读取输入的待排序关键字个数n。 2. 读取n个待排序关键字并存储在数组中。 3. 对数组进行基数排序&…

MinIO 在windows环境下载和安装

目录 1.MinIO&#xff08;windows&#xff09;下载链接&#xff1a; 2. 启动MinIO &#xff08;1&#xff09;直接启动MinIo &#xff08;2&#xff09;指定端口号启动MinIo 3.通过创建.bat文件帮助启动MinIO 1.MinIO&#xff08;windows&#xff09;下载链接&#xff1a;…

国外电商系统开发-运维系统批量添加服务器

您可以把您准备的txt文件&#xff0c;安装要求的格式&#xff0c;复制粘贴到里面就可以了。注意格式&#xff01; 如果是“#” 开头的&#xff0c;则表示注释&#xff01;

Python数据可视化--Matplotlib--入门

我生性自由散漫&#xff0c;不喜欢拘束。我谁也不爱&#xff0c;谁也不恨。我没有欺骗这个&#xff0c;追求那个&#xff1b;没有把这个取笑&#xff0c;那个玩弄。我有自己的消遣。 -- 塞万提斯 《堂吉诃德》 Matplotlib介绍 1. Matplotlib 是 Python 中常用的 2D 绘图库&a…

ArkTS语法

一、声明 格式:关键字 变量/常量名 : 类型注释 = 值 变量声明 let count : number = 0; count = 40; 常量声明 const MAX_COUNT : number = 100; 二、数据类型 基本数据类型:string、number、boolean等 引用数据类型:Object、Array、自定义类等 …