zookeeper基础学习之六: zookeeper java客户端curator

简介

Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等等。Patrixck Hunt(Zookeeper)以一句“Guava is to Java that Curator to Zookeeper”给Curator予高度评价。
Curator的maven依赖

Apache Curator 是 Apache 基金会提供的一款 ZooKeeper 客户端,它提供了一套易用性和可读性非常强的 Fluent 风格的客户端 API ,可以帮助我们快速搭建稳定可靠的 ZooKeeper 客户端程序。

为便于你更全面了解 Curator 的功能,我整理出了如下表格,展示了 Curator 提供的 jar 包:

在这里插入图片描述

添加依赖

<!-- 对zookeeper的底层api的一些封装 --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>5.0.0</version></dependency><!-- 封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式Barrier --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>5.0.0</version></dependency>

创建连接

public static void main(String[] args) throws Exception {//fluent风格
//        CuratorFramework curatorFramework1=CuratorFrameworkFactory.builder().connectString(ZKSERVERS).sessionTimeoutMs(5000).
//                retryPolicy(new ExponentialBackoffRetry(1000,3)).
//                namespace("/curator").build();
//        curatorFramework1.start();RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);CuratorFramework client = CuratorFrameworkFactory.newClient(ZKSERVERS, retryPolicy);client.start();String result=client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/curator/curator1/curator11","123".getBytes());System.out.println(result);}

curator连接的重试策略

ExponentialBackoffRetry() 衰减重试
RetryNTimes 指定最大重试次数
RetryOneTime 仅重试一次
RetryUnitilElapsed 一直重试直到规定的时间

在这里插入图片描述

基本操作

public static void main(String[] args) throws Exception {// Zookeeper集群地址,多个节点地址可以用逗号分隔String zkAddress = "127.0.0.1:2181";// 重试策略,如果连接不上ZooKeeper集群,会重试三次,重试间隔会递增RetryPolicy retryPolicy =new ExponentialBackoffRetry(1000, 3);// 创建Curator Client并启动,启动成功之后,就可以与Zookeeper进行交互了CuratorFramework client =CuratorFrameworkFactory.newClient(zkAddress, retryPolicy);client.start();// 下面简单说明Curator中常用的API// create()方法创建ZNode,可以调用额外方法来设置节点类型、添加Watcher// 下面是创建一个名为"user"的持久节点,其中会存储一个test字符串String path = client.create().withMode(CreateMode.PERSISTENT).forPath("/user", "test".getBytes());System.out.println(path);// 输出:/user// checkExists()方法可以检查一个节点是否存在Stat stat = client.checkExists().forPath("/user");System.out.println(stat!=null);// 输出:true,返回的Stat不为null,即表示节点存在// getData()方法可以获取一个节点中的数据byte[] data = client.getData().forPath("/user");System.out.println(new String(data));// 输出:test// setData()方法可以设置一个节点中的数据stat = client.setData().forPath("/user","data".getBytes());data = client.getData().forPath("/user");System.out.println(new String(data));// 输出:data// 在/user节点下,创建多个临时顺序节点for (int i = 0; i < 3; i++) {client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/user/child-");}// 获取所有子节点List<String> children = client.getChildren().forPath("/user");System.out.println(children);// 输出:[child-0000000002, child-0000000001, child-0000000000]// delete()方法可以删除指定节点,deletingChildrenIfNeeded()方法// 会级联删除子节点client.delete().deletingChildrenIfNeeded().forPath("/user");}

Curator 异步接口,引入了BackgroundCallback

上面介绍的创建、删除、更新、读取等方法都是同步的,Curator 提供异步接口,引入了BackgroundCallback 这个回调接口以及 CuratorListener 这个监听器,用于处理 Background 调用之后服务端返回的结果信息。BackgroundCallback 接口和 CuratorListener 监听器中接收一个 CuratorEvent 的参数,里面包含事件类型、响应码、节点路径等详细信息。

下面我们通过一个示例说明 BackgroundCallback 接口以及 CuratorListener 监听器的基本使用:

public class CuratorAsynApi {public static void main(String[] args) throws Exception {// Zookeeper集群地址,多个节点地址可以用逗号分隔String zkAddress = "127.0.0.1:2181";// 重试策略,如果连接不上ZooKeeper集群,会重试三次,重试间隔会递增RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);// 创建Curator Client并启动,启动成功之后,就可以与Zookeeper进行交互了CuratorFramework client = CuratorFrameworkFactory.newClient(zkAddress, retryPolicy);client.start();// 添加CuratorListener监听器,针对不同的事件进行处理client.getCuratorListenable().addListener(new CuratorListener() {public void eventReceived(CuratorFramework client,CuratorEvent event) throws Exception {switch (event.getType()) {case CREATE:System.out.println("CREATE:" +event.getPath());break;case DELETE:System.out.println("DELETE:" +event.getPath());break;case EXISTS:System.out.println("EXISTS:" +event.getPath());break;case GET_DATA:System.out.println("GET_DATA:" +event.getPath() + ","+ new String(event.getData()));break;case SET_DATA:System.out.println("SET_DATA:" +new String(event.getData()));break;case CHILDREN:System.out.println("CHILDREN:" +event.getPath());break;default:}}});// 注意:下面所有的操作都添加了inBackground()方法,转换为后台操作client.create().withMode(CreateMode.PERSISTENT).inBackground().forPath("/user", "test".getBytes());client.checkExists().inBackground().forPath("/user");client.setData().inBackground().forPath("/user","setData-Test".getBytes());client.getData().inBackground().forPath("/user");for (int i = 0; i < 3; i++) {client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).inBackground().forPath("/user/child-");}client.getChildren().inBackground().forPath("/user");// 添加BackgroundCallbackclient.getChildren().inBackground(new BackgroundCallback() {public void processResult(CuratorFramework client,CuratorEvent event) throws Exception {System.out.println("in background:"+ event.getType() + "," + event.getPath());}}).forPath("/user");client.delete().deletingChildrenIfNeeded().inBackground().forPath("/user");System.in.read();}}

连接状态监听

除了基础的数据操作,Curator 还提供了监听连接状态的监听器——ConnectionStateListener,它主要是处理 Curator 客户端和 ZooKeeper 服务器间连接的异常情况,例如, 短暂或者长时间断开连接。

短暂断开连接时,ZooKeeper 客户端会检测到与服务端的连接已经断开,但是服务端维护的客户端 Session 尚未过期,之后客户端和服务端重新建立了连接;当客户端重新连接后,由于 Session 没有过期,ZooKeeper 能够保证连接恢复后保持正常服务。

而长时间断开连接时,Session 已过期,与先前 Session 相关的 Watcher 和临时节点都会丢失。当 Curator 重新创建了与 ZooKeeper 的连接时,会获取到 Session 过期的相关异常,Curator 会销毁老 Session,并且创建一个新的 Session。由于老 Session 关联的数据不存在了,在 ConnectionStateListener 监听到 LOST 事件时,就可以依靠本地存储的数据恢复 Session 了。

这里 Session 指的是 ZooKeeper 服务器与客户端的会话。客户端启动的时候会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。客户端能够通过心跳检测与服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watch 事件通知。

我们可以设置客户端会话的超时时间(sessionTimeout),当服务器压力太大、网络故障或是客户端主动断开连接等原因导致连接断开时,只要客户端在 sessionTimeout 规定的时间内能够重新连接到 ZooKeeper 集群中任意一个实例,那么之前创建的会话仍然有效。ZooKeeper 通过 sessionID 唯一标识 Session,所以在 ZooKeeper 集群中,sessionID 需要保证全局唯一。 由于 ZooKeeper 会将 Session 信息存放到硬盘中,即使节点重启,之前未过期的 Session 仍然会存在。

public class CuratorSessionApi {public static void main(String[] args) throws Exception {// Zookeeper集群地址,多个节点地址可以用逗号分隔String zkAddress = "127.0.0.1:2181";// 重试策略,如果连接不上ZooKeeper集群,会重试三次,重试间隔会递增RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);// 创建Curator Client并启动,启动成功之后,就可以与Zookeeper进行交互了CuratorFramework client = CuratorFrameworkFactory.newClient(zkAddress, retryPolicy);client.start();// 添加ConnectionStateListener监听器client.getConnectionStateListenable().addListener(new ConnectionStateListener() {public void stateChanged(CuratorFramework client,ConnectionState newState) {// 这里我们可以针对不同的连接状态进行特殊的处理switch (newState) {case CONNECTED:// 第一次成功连接到ZooKeeper之后会进入该状态。// 对于每个CuratorFramework对象,此状态仅出现一次System.out.println("第一次成功连接到ZooKeeper之后会进入该状态");break;case SUSPENDED: //   ZooKeeper的连接丢失System.out.println("ZooKeeper的连接丢失");break;case RECONNECTED: // 丢失的连接被重新建立System.out.println("丢失的连接被重新建立");break;case LOST:System.out.println("当Curator认为会话已经过期时,则进入此状态");// 当Curator认为会话已经过期时,则进入此状态break;case READ_ONLY: // 连接进入只读模式System.out.println("连接进入只读模式");break;}}});client.close();System.in.read();}
}

Watcher

Watcher 监听机制是 ZooKeeper 中非常重要的特性,可以监听某个节点上发生的特定事件,例如,监听节点数据变更、节点删除、子节点状态变更等事件。当相应事件发生时,ZooKeeper 会产生一个 Watcher 事件,并且发送到客户端。通过 Watcher 机制,就可以使用 ZooKeeper 实现分布式锁、集群管理等功能。

在 Curator 客户端中,我们可以使用 usingWatcher() 方法添加 Watcher,前面示例中,能够添加 Watcher 的有 checkExists()、getData()以及 getChildren() 三个方法,下面我们来看一个具体的示例:

public class CuratorWatcherApi {public static void main(String[] args) throws Exception {// Zookeeper集群地址,多个节点地址可以用逗号分隔String zkAddress = "127.0.0.1:2181";// 重试策略,如果连接不上ZooKeeper集群,会重试三次,重试间隔会递增RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);// 创建Curator Client并启动,启动成功之后,就可以与Zookeeper进行交互了CuratorFramework client = CuratorFrameworkFactory.newClient(zkAddress, retryPolicy);client.start();try {client.create().withMode(CreateMode.PERSISTENT).forPath("/user", "test".getBytes());} catch (Exception e) {}// 这里通过usingWatcher()方法添加一个WatcherList<String> children = client.getChildren().usingWatcher(new CuratorWatcher() {public void process(WatchedEvent event) throws Exception {System.out.println(event.getType() + "," +event.getPath());}}).forPath("/user");System.out.println(children);System.in.read();}
}

接下来,我们打开 ZooKeeper 的命令行客户端,在 /user 节点下先后添加两个子节点,此时我们只得到一行输出:

NodeChildrenChanged,/user

之所以这样,是因为通过 usingWatcher() 方法添加的 CuratorWatcher 只会触发一次,触发完毕后就会销毁。checkExists() 方法、getData() 方法通过 usingWatcher() 方法添加的 Watcher 也是一样的原理,只不过监听的事件不同,你若感兴趣的话,可以自行尝试一下。

相信你已经感受到,直接通过注册 Watcher 进行事件监听不是特别方便,需要我们自己反复注册 Watcher。Apache Curator 引入了 Cache 来实现对 ZooKeeper 服务端事件的监听。Cache 是 Curator 中对事件监听的包装,其对事件的监听其实可以近似看作是一个本地缓存视图和远程ZooKeeper 视图的对比过程。同时,Curator 能够自动为开发人员处理反复注册监听,从而大大简化了代码的复杂程度。

实践中常用的 Cache 有三大类:

  • NodeCache。 对一个节点进行监听,监听事件包括指定节点的增删改操作。注意哦,NodeCache 不仅可以监听数据节点的内容变更,也能监听指定节点是否存在,如果原本节点不存在,那么 Cache 就会在节点被创建后触发 NodeCacheListener,删除操作亦然。
  • PathChildrenCache。 对指定节点的一级子节点进行监听,监听事件包括子节点的增删改操作,但是不对该节点的操作监听。
  • TreeCache。 综合 NodeCache 和 PathChildrenCache 的功能,是对指定节点以及其子节点进行监听,同时还可以设置监听的深度。
    下面通过示例介绍上述三种 Cache 的基本使用:
public class CuratorWatcherCacheApi {public static void main(String[] args) throws Exception {// Zookeeper集群地址,多个节点地址可以用逗号分隔String zkAddress = "127.0.0.1:2181";// 重试策略,如果连接不上ZooKeeper集群,会重试三次,重试间隔会递增RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);// 创建Curator Client并启动,启动成功之后,就可以与Zookeeper进行交互了CuratorFramework client = CuratorFrameworkFactory.newClient(zkAddress, retryPolicy);client.start();// 创建NodeCache,监听的是"/user"这个节点NodeCache nodeCache = new NodeCache(client, "/user");// start()方法有个boolean类型的参数,默认是false。如果设置为true,// 那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的// 数据内容,并保存在Cache中。nodeCache.start(true);if (nodeCache.getCurrentData() != null) {System.out.println("NodeCache节点初始化数据为:"+ new String(nodeCache.getCurrentData().getData()));} else {System.out.println("NodeCache节点数据为空");}// 添加监听器nodeCache.getListenable().addListener(() -> {String data = new String(nodeCache.getCurrentData().getData());System.out.println("NodeCache节点路径:" + nodeCache.getCurrentData().getPath()+ ",节点数据为:" + data);});// 创建PathChildrenCache实例,监听的是"user"这个节点PathChildrenCache childrenCache = new PathChildrenCache(client, "/user", true);// StartMode指定的初始化的模式// NORMAL:普通异步初始化// BUILD_INITIAL_CACHE:同步初始化// POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);// childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);// childrenCache.start(PathChildrenCache.StartMode.NORMAL);List<ChildData> children = childrenCache.getCurrentData();System.out.println("获取子节点列表:");// 如果是BUILD_INITIAL_CACHE可以获取这个数据,如果不是就不行children.forEach(childData -> {System.out.println(new String(childData.getData()));});childrenCache.getListenable().addListener(((client1, event) -> {System.out.println(LocalDateTime.now() + "  " + event.getType());if (event.getType().equals(PathChildrenCacheEvent.Type.INITIALIZED)) {System.out.println("PathChildrenCache:子节点初始化成功...");} else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)) {String path = event.getData().getPath();System.out.println("PathChildrenCache添加子节点:" + event.getData().getPath());System.out.println("PathChildrenCache子节点数据:" + new String(event.getData().getData()));} else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {System.out.println("PathChildrenCache删除子节点:" + event.getData().getPath());} else if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {System.out.println("PathChildrenCache修改子节点路径:" + event.getData().getPath());System.out.println("PathChildrenCache修改子节点数据:" + new String(event.getData().getData()));}}));// 创建TreeCache实例监听"user"节点TreeCache cache = TreeCache.newBuilder(client, "/user").setCacheData(false).build();cache.getListenable().addListener((c, event) -> {if (event.getData() != null) {System.out.println("TreeCache,type=" + event.getType() + " path=" + event.getData().getPath());} else {System.out.println("TreeCache,type=" + event.getType());}});cache.start();System.in.read();}
}

启动程序后自己在客户端进行增加节点,修改数据等操作,观察输出这里不进行截图了

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

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

相关文章

【算法刷题 | 数组】3.12(二分查找、移除元素、有序数组的平方、长度最小的子数组、螺旋矩阵2)

文章目录 1.二分查找1.1题目1.2思路&#xff08;核心&#xff1a;区间的定义&#xff09;1.3左闭右闭1.4左闭右开1.5总结 2.移除元素2.1题目2.1思路2.2.1暴力解法2.2.2双指针法 23总结 3.有序数组的平方3.1题目3.2思路3.2.1暴力解法3.2.2双指针法 4.长度最小的子数组4.1题目4.2…

Linux中文件和目录管理(创建删除移动复制)

目录 1——一次建立一个或多个目录&#xff1a;mkdir ​2——创建一个空文件&#xff1a;touch 3——移动和重命名&#xff1a;mv 4——复制文件和目录&#xff1a;cp 5—— 删除目录和文件&#xff1a;rmdir和rm 在学习文件与目录的管理的一些命令之前&#xff0c;我们先…

深度学习-面经(part2、CNN)

2 CNN 对图像&#xff08;不同的数据窗口数据&#xff09;和滤波矩阵做内积&#xff08;逐个元素相乘再求和&#xff09;的操作就是所谓的『卷积』操作。 卷积神经网络由输入层、卷积层、激励层、池化层、全连接层组成。 ① 最左边: 数据输入层&#xff0c;对数据做一些处理…

数字后端 EDA 软件分享

数字后端 EDA 软件分享 推荐这几家的EDA工具吧&#xff0c;虽说我也支持国产工具&#xff0c;但是我还是选择了这几家的工具 apache cadence mentor synopsys 下图我现在用的eda环境&#xff0c;利用网上的资源&#xff0c;自己独立在vmware上搭建好的EDA环境 除去pdk&#…

从政府工作报告探究计算机行业发展

从政府工作报告探计算机行业发展 政府工作报告作为政府工作的全面总结和未来规划&#xff0c;不仅反映了国家整体的发展态势&#xff0c;也为各行各业提供了发展的指引和参考。随着信息技术的快速发展&#xff0c;计算机行业已经成为推动经济社会发展的重要引擎之一。因此&…

计算机考研|怎么备考「科软」?

学好408和考研数学就可以了 大家对于科软已经回到了理性的区间&#xff0c;很难再出现刚开始的300分上科软的现象&#xff0c;也不会再出现388分炸穿地心的现象。 如果大家想报考科软&#xff0c;我觉得一定要认真对待复习&#xff0c;不要抱有抄底的心态去复习。 众所周知&am…

7.JavaWebHTML:构建数字世界的语言和结构

目录 导语&#xff1a; 第一部分&#xff1a;Web概念与作用 1.1 Web的定义 1.2 Web的作用 1.3 JavaWeb 第二部分&#xff1a;HTML概念与内容 2.1 HTML的定义 2.2 HTML的内容 第三部分&#xff1a;HTML的作用 3.1 HTML的作用 3.2 HTML在现代Web开发中的角色 …

[Redis]——主从同步原理(全量同步、增量同步)

目录 Redis集群&#xff1a; 主从同步原理&#xff1a; replid和offset: 全量同步和增量同步&#xff1a; repl_baklog文件&#xff1a; 主从集群的优化&#xff1a; Redis集群&#xff1a; 部署多台Redis我们称之为Redis集群&#xff0c;他有一个主节点(负责写操作)&…

爱普生晶振发布RTC模块晶振(压电侠)

爱普生晶振一直以”省&#xff0c;小&#xff0c;精”技术作为资深核心&#xff0c;并且已经建立了一个原始的垂直整合制造模型&#xff0c;可以自己创建独特的核心技术和设备&#xff0c;使用这些作为基地的规划和设计提供独特价值的产品. 世界领先的石英晶体技术精工爱普生公…

中国联通智慧矿山行业解决方案

中国联通国际公司以其全球化服务能力&#xff0c;针对矿山行业的特殊挑战提供了定制化的解决方案&#xff0c;尤其是在网络通信基础设施搭建和智能应用部署方面&#xff0c;助力企业克服远程作业环境下的通信难题&#xff0c;并有效拓展海外市场。 对于矿山类企业而言&#xf…

【遍历方法】浅析Java中字符串、数组、集合的遍历

目录 前言 字符串篇 1.1 使用 for 循环和 charAt 方法 1.2 使用增强 for 循环&#xff08;forEach 循环&#xff09; 1.3 使用 Java 8 的 Stream API 最终效果 数组篇 2.1 使用普通 for 循环 2.2 使用增强型 for 循环( forEach 循环) 2.3 使用 Arrays.asList 和 forE…

解决:springboot项目访问hdfs文件提示guava版本不兼容

1、问题描述 版本说明&#xff1a;我用的hadoop版本&#xff1a;3.1.3 项目可以正常启动&#xff0c;但是调用访问hdfs的服务时候报错,报错消息如下&#xff1a;com.google.common.base.preconditions.checkArgument(ZL java/lang/String;Ljava/lang/Object:)V 原因分析&#x…

Flutter开发进阶之使用工具效率开发

Flutter开发进阶之使用工具效率开发 软件开发团队使用Flutter开发的原因通常是因为Flutter开发性能高、效率高、兼容性好、可拓展性高&#xff0c;作为软件PM来说主要考虑的是范围管理、进度管理、成本管理、资源管理、质量管理、风险管理和沟通管理等&#xff0c;可以看到Flu…

企业内部培训考试系统培训计划功能说明

培训计划是预设好的一套课程系列&#xff0c;包含课程和考试&#xff0c;分多个阶段&#xff0c;每完成一个阶段就会在学习地图上留下标记&#xff0c;让用户看到自己的努力成果&#xff0c;增强成就感&#xff0c;从而坚持完成课程。 企业内部培训考试系统中如何设置培训计划…

基于springboot的购物商城管理系统

1.项目简介 1.1 用户简介 用户主要分为管理员和用户端&#xff1a; 管理员&#xff1a; 管理员可以对后台数据进行管理、拥有最高权限、具体权限有登录后进行首页轮播图的配置管理、商品的配置、新品家具商城的配置管理、、家具商城分类管理配置、家具商城详情商品管理、用户…

react-面试题

一、组件基础 1. React 事件机制 <div onClick{this.handleClick.bind(this)}>点我</div> React并不是将click事件绑定到了div的真实DOM上&#xff0c;而是在document处监听了所有的事件&#xff0c;当事件发生并且冒泡到document处的时候&#xff0c;React将事…

网络安全JavaSE第二天(持续更新)

3. 基本数据与运算 3.6 运算符 3.6.1 算术运算符 在 Java 中&#xff0c;算术运算符包含&#xff1a;、-、*、/、% public class ArithmeticOperator { public static void main(String[] args) { int a 10; // 定义了一个整型类型的变量 a&#xff0c;它的值是 10 int b …

区块链推广海外市场怎么做,CloudNEO服务商免费为您定制个性化营销方案

随着区块链技术的不断发展和应用场景的扩大&#xff0c;区块链项目希望能够进入海外市场并取得成功已成为越来越多公司的目标之一。然而&#xff0c;要在海外市场推广区块链项目&#xff0c;需要采取有效的营销策略和措施。作为您的区块链项目营销服务商&#xff0c;CloudNEO将…

后端程序员入门react笔记(八)-redux的使用和项目搭建

一个更好用的文档 添加链接描述 箭头函数的简化 //简化前 function countIncreAction(data) {return {type:"INCREMENT",data} } //简化后 const countIncreAction data>({type:"INCREMENT",data })react UI组件库相关资料 组件库连接和推荐 antd组…

Python 多线程大批量处理文件小程序

说明 平时偶尔需要进行重复性的对文件进行重命名、格式转化等。假设以文件复制功能作为目标&#xff0c;设计一个小程序使用多线程对文件进行批量复制。&#xff08;其实以后主要目标是针对Realsense的raw文件进行批量的转化&#xff0c;并借助多线程加速&#xff09; 代码 i…