手写实现简单Redis命令客户端功能

RESP协议

Redis 的客户端和服务端之间采取了一种名为 Redis序列化的协议(REdis Serialization Protocol,简称RESP),是基于 TCP 的应用层协议 ,RESP 底层采用的是 TCP 的连接方式,通过 TCP 进行数据传输,然后根据解析规则解析相应信息。

在RESP协议中,数据的类型取决于第一个字节:

  • +开始表示单行字符串
  • -开始表示错误类型
  • :开始表示整数
  • $开始表示多行字符串
  • *开始表示数组

在RESP协议中,构成协议的每一部分必须使用\r\n作为结束符

命令格式示例:

SET key value*3\r\n    #3表示这个命令由3部分组成
$3\r\n    # 第一部分的长度是3
SET\r\n   # 第一部分的内容
$3\r\n    # 第二部分的长度是3
key\r\n   # 第二部分的内容
$5\r\n    # 第三部分的长度是5
value\r\n # 第三部分的内容

几种响应格式示例:

简单字符串:+OK\r\n错误(Errors):-ERR Error message\r\n整数(Integers)::1000\r\n批量字符串(Bulk Strings):$6\r\n
foobar\r\n数组(Arrays)(示例包含两个元素,分别为 "foo" 和 "bar"):*2\r\n
$3\r\n
foo\r\n
$3\r\n
bar\r\n
手写通过RESP协议实现客户端
package com.qf.redis.client;import java.io.*;
import java.net.Socket;public class RedisClient {public static void main(String[] args) {String ip = "localhost";int port = 6379;try {Socket socket = new Socket(ip,port);OutputStream os = socket.getOutputStream();//发送一个redis命令: set class javaString[] commends = new String[]{"set","class","java"};String s = String.format("*%d\r\n", commends.length);os.write(s.getBytes());//os.write("*3\r\n".getBytes());for (int i = 0; i < commends.length; i++) {String s1 = String.format("$%d\r\n", commends[i].length());os.write(s1.getBytes());String s2 = String.format("%s\r\n", commends[i]);os.write(s2.getBytes());}
//            os.write("$3\r\n".getBytes());
//            os.write("set\r\n".getBytes());
//            os.write("$5\r\n".getBytes());
//            os.write("class\r\n".getBytes());
//            os.write("$4\r\n".getBytes());
//            os.write("java\\r\n".getBytes());os.flush();socket.shutdownOutput();//告诉服务器发送已完毕InputStream in = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));String response;while ((response=reader.readLine())!=null){System.out.println(response);}reader.close();socket.close();} catch (IOException e) {throw new RuntimeException(e);}}
}

注:Redis采用的是RSEP协议进行通信,这种协议只支持传输字符串或者字节数组,传输字符串的时候,容器出现中文乱码问题,具体来说,是因为Redis默认的编码格式和java不同,所以在获取长度的时候,上面那种获取的是中文的长度,而不同编码格式对于中文汉字的长度可能不一样,从而导致Redis拿到长度后发现长度和对象不匹配,导致乱码。因此,推荐使用字节数组进行传输,这样计算的长度都是字节数组的长度,就不会发生乱码问题。

package com.qf.redis.client;import java.io.*;
import java.net.Socket;public class RedisClient {public static void main(String[] args) {String ip = "localhost";int port = 6379;try {Socket socket = new Socket(ip,port);OutputStream os = socket.getOutputStream();//发送一个redis命令: set class java
//            String[] commends = new String[]{"set","stu","zhangsan"};byte[][] commends = new byte[][]{"set".getBytes(),"stu".getBytes(),"张三".getBytes()};String s = String.format("*%d\r\n", commends.length);os.write(s.getBytes());//os.write("*3\r\n".getBytes());for (byte[] commend : commends) {os.write("$".getBytes());os.write(Integer.toString(commend.length).getBytes());os.write("\r\n".getBytes());os.write(commend);os.write("\r\n".getBytes());}
//            for (int i = 0; i < commends.length; i++) {
//                String s1 = String.format("$%d\r\n", commends[i].length());
//                os.write(s1.getBytes());
//                String s2 = String.format("%s\r\n", commends[i]);
//                os.write(s2.getBytes());
//            }
//            os.write("$3\r\n".getBytes());
//            os.write("set\r\n".getBytes());
//            os.write("$5\r\n".getBytes());
//            os.write("class\r\n".getBytes());
//            os.write("$4\r\n".getBytes());
//            os.write("java\\r\n".getBytes());os.flush();socket.shutdownOutput();//告诉服务器发送已完毕InputStream in = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));String response;while ((response=reader.readLine())!=null){System.out.println(response);}reader.close();socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}

对于获得的结果,目前的处理是将结果直接进行打印,这样的处理并不合理,我们应该将获取结果封装为一个方法,根据不同的情况得到不同的返回值

package com.qf.redis.client;import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;public class RedisClient {public static void main(String[] args) {String ip = "localhost";int port = 6379;try {Socket socket = new Socket(ip,port);//发送一个redis命令: set class java
//            String[] commends = new String[]{"set","stu","zhangsan"};byte[][] commends = new byte[][]{"get".getBytes(),"stu".getBytes()
//                    "张三".getBytes()};sendCommend(socket, os, commends);Object result = getResult(socket);System.out.println(result);} catch (IOException e) {throw new RuntimeException(e);}}private static Object getResult(Socket socket) throws IOException {InputStream in = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));char read = (char) in.read();switch (read){case '+'://单行字符串return reader.readLine();case '*'://多行字符串,以数组的形式出现String str = reader.readLine();int rows = Integer.parseInt(str);List<String> results = new ArrayList<>();while (rows-- > 0){reader.readLine();results.add(reader.readLine());}return results;case ':'://单行字符串,表示整数str = reader.readLine();return Integer.parseInt(str);case '$'://多行字符串reader.readLine();return reader.readLine();case '-'://错误StringBuilder sb = new StringBuilder();String line;while((line = reader.readLine())!=null){sb.append(line);}throw new RuntimeException(sb.toString());default:return "";}
//        String response;
//        while ((response=reader.readLine())!=null){
//            System.out.println(response);
//        }
//        reader.close();
//        socket.close();}private static void sendCommend(Socket socket, byte[][] commends) throws IOException {String s = String.format("*%d\r\n", commends.length);OutputStream os = socket.getOutputStream();os.write(s.getBytes());//os.write("*3\r\n".getBytes());for (byte[] commend : commends) {os.write("$".getBytes());os.write(Integer.toString(commend.length).getBytes());os.write("\r\n".getBytes());os.write(commend);os.write("\r\n".getBytes());}os.flush();socket.shutdownOutput();//告诉服务器发送已完毕}}

以上是通过指定的命令进行编写,我们可以将get、set命令单独编写方法实现,但是在方法中如果想要实现设置和获取的对象不是简单的字符串,而是一个对象(比如集合),该怎么实现呢?

Redis本身主要就是用来做高速缓存的,因此其中可能会缓存一些查询数据,但这些数据在Java应用中都是以集合形式出现,因此需要提供一种方式能够将集合转换成字节数组,这样就能实现将集合数据存储在Redis中。

这个过程称为序列化,而将数据转换回集合并读取的过程叫做反序列化,序列化接口:

RedisSerializer

public interface RedisSerializer<T> {//这个接口方法就是将给定的对象转换成字节数组byte[] serialize(T t) throws IOException;//这个接口方法就是将给定的byte数组还原成T对象T deserialize(byte[] data) throws IOException, ClassNotFoundException;}

实现类,包含两种,一种是输入格式为字符串,一种是字节数组:

字符串类型很好转换,因为String类型本身就提供了和字节数组之间的转换

public class StringRedisSerializer implements RedisSerializer<String>{@Overridepublic byte[] serialize(String s) throws IOException {return s.getBytes();}@Overridepublic String deserialize(byte[] data) throws IOException, ClassNotFoundException {return new String(data);}
}

字节数组相对来说比较麻烦,需要实现字节数据和集合对象之间的转换,有两种思路,第一种是通过反射获取对象的属性,然后转换为json格式(即map)的数据,再转换为字节数组,反序列化则是将字节数组转换为字符串,然后反射获取要转换对象的属性信息,就需要考虑不同属性类型进而进行不同的转化方式(对象属性要求装箱),十分复杂;

第二种则是通过对象流的形式直接进行读写,因此推荐这种方式,这里也只以这种方式进行实现

public class GenericObjectSerializer<T> implements RedisSerializer<T>{private Class<T> clazz;public GenericObjectSerializer(Class<T> clazz) {this.clazz = clazz;}@Overridepublic byte[] serialize(T t) throws IOException {ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(t);oos.close();return baos.toByteArray();}@Overridepublic T deserialize(byte[] data) throws IOException, ClassNotFoundException {ByteArrayInputStream bais = new ByteArrayInputStream(data);ObjectInputStream ois = new ObjectInputStream(bais);T t = (T) ois.readObject();ois.close();return t;}
}

实现了字符串和字节数组的序列化和反序列化后,可以进行对get和set方法的编写,注意:这里的get和set已经不仅仅可以实现Redis中的原生的设置字符串和获取字符串,而是可以设置对象和获取对象。

package com.qf.redis.client;import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class RedisClient {public static void main(String[] args) {
//        String ip = "localhost";
//        int port = 6379;
//        try {
//            Socket socket = new Socket(ip,port);
//            OutputStream os = socket.getOutputStream();
//            //发送一个redis命令: set class java
            String[] commends = new String[]{"set","stu","zhangsan"};
//            byte[][] commends = new byte[][]{
//                    "get".getBytes(),
//                    "stu".getBytes()
                    "张三".getBytes()
//            };
//            sendCommend(socket, os, commends);
//
//            Object result = getResult(socket);
//            System.out.println(result);
//
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }//        set("hobbies", Arrays.asList("吃饭","睡觉","撸狗","撸猫","撸水豚"));get("hobbies");}private static Object getResult(Socket socket) throws IOException {InputStream in = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));char read = (char) in.read();switch (read){case '+'://单行字符串return reader.readLine();case '*'://多行字符串,以数组的形式出现String str = reader.readLine();int rows = Integer.parseInt(str);List<String> results = new ArrayList<>();while (rows-- > 0){reader.readLine();results.add(reader.readLine());}return results;case ':'://单行字符串,表示整数str = reader.readLine();return Integer.parseInt(str);case '$'://多行字符串reader.readLine();return reader.readLine();case '-'://错误StringBuilder sb = new StringBuilder();String line;while((line = reader.readLine())!=null){sb.append(line);}throw new RuntimeException(sb.toString());default:return "";}
//        String response;
//        while ((response=reader.readLine())!=null){
//            System.out.println(response);
//        }
//        reader.close();
//        socket.close();}private static void sendCommend(Socket socket, byte[][] commends) throws IOException {String s = String.format("*%d\r\n", commends.length);OutputStream os = socket.getOutputStream();os.write(s.getBytes());//os.write("*3\r\n".getBytes());for (byte[] commend : commends) {os.write("$".getBytes());os.write(Integer.toString(commend.length).getBytes());os.write("\r\n".getBytes());os.write(commend);os.write("\r\n".getBytes());}os.flush();socket.shutdownOutput();//告诉服务器发送已完毕}public static void get(String key){StringRedisSerializer keySerializer = new StringRedisSerializer();RedisSerializer<List> valueSerializer = new GenericObjectSerializer<>(List.class);try {byte[] keyData = keySerializer.serialize(key);byte[][] commands = {"get".getBytes(),keyData};//{"get".getBytes(),"name".getBytes()}Socket socket = new Socket("localhost",6379);sendCommend(socket,commands);byte[] result = receiveMsg(socket);System.out.println(Arrays.toString(result));//没进行反序列化的结果,是字节数组强行转换为字符串的样子List list = valueSerializer.deserialize(result);System.out.println(list);//进行了反序列化后的结果} catch (IOException | ClassNotFoundException e) {throw new RuntimeException(e);}}public static void set(String key,Object value){StringRedisSerializer keySerializer = new StringRedisSerializer();GenericObjectSerializer<Object> valueSerializer = new GenericObjectSerializer<>(Object.class);try {byte[] keyData = keySerializer.serialize(key);byte[] valueData = valueSerializer.serialize(value);byte[][] commands = {"set".getBytes(),keyData,valueData};//{"set".getBytes(),"name".getBytes(),"张三".getBytes()}Socket socket = new Socket("localhost",6379);sendCommend(socket,commands);Object result = getResult(socket);System.out.println(result);} catch (IOException e) {throw new RuntimeException(e);}}/*** 这个方法是将序列化结果的字节数组读取出来* @param socket* @return*/public static byte[] receiveMsg(Socket socket){try {InputStream in = socket.getInputStream();while (true){char c = (char) in.read();if(c == '\r'){c = (char) in.read();if(c == '\n')break;}}ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[2048];int len;while ((len = in.read(buffer))!=-1){baos.write(buffer,0,len);}byte[] result = baos.toByteArray();baos.close();return result;} catch (IOException e) {throw new RuntimeException(e);}}
}

注:这里的get方法中不能用getResult()方法直接获取结果,因为这个方法只针对于没有进行序列化就存储的数据,而这里的数据需要反序列化,所以添加了receiveMsg()方法以将获取的数据视为字节数组并进行反序列化处理。

拓展:

但是假如我需要同时利用get和set方法存储序列化和非序列化的数据,该如何实现呢?

可以在set和get方法的参数中加一个布尔值参数,以区分是否需要进行序列化,如果需要则进行序列化

package com.qf.redis.client;import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class RedisClient {public static void main(String[] args) {set("hobbies", Arrays.asList("吃饭","睡觉","撸狗","撸猫","撸水豚"),true);get("hobbies",true);
//        get("stu",false);
//        set("tea","boduolaoshi",false);
//        get("tea",false);}private static Object getResult(Socket socket) throws IOException {InputStream in = socket.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));char read = (char) in.read();switch (read){case '+'://单行字符串return reader.readLine();case '*'://多行字符串,以数组的形式出现String str = reader.readLine();int rows = Integer.parseInt(str);List<String> results = new ArrayList<>();while (rows-- > 0){reader.readLine();results.add(reader.readLine());}return results;case ':'://单行字符串,表示整数str = reader.readLine();return Integer.parseInt(str);case '$'://多行字符串reader.readLine();return reader.readLine();case '-'://错误StringBuilder sb = new StringBuilder();String line;while((line = reader.readLine())!=null){sb.append(line);}throw new RuntimeException(sb.toString());default:return "";}
//        String response;
//        while ((response=reader.readLine())!=null){
//            System.out.println(response);
//        }
//        reader.close();
//        socket.close();}private static void sendCommend(Socket socket, byte[][] commends) throws IOException {String s = String.format("*%d\r\n", commends.length);OutputStream os = socket.getOutputStream();os.write(s.getBytes());//os.write("*3\r\n".getBytes());for (byte[] commend : commends) {os.write("$".getBytes());os.write(Integer.toString(commend.length).getBytes());os.write("\r\n".getBytes());os.write(commend);os.write("\r\n".getBytes());}os.flush();socket.shutdownOutput();//告诉服务器发送已完毕}public static void get(String key,boolean isSerialize){StringRedisSerializer keySerializer = new StringRedisSerializer();RedisSerializer<List> valueSerializer = new GenericObjectSerializer<>(List.class);try {byte[] keyData = keySerializer.serialize(key);byte[][] commands = {"get".getBytes(),keyData};//{"get".getBytes(),"name".getBytes()}Socket socket = new Socket("localhost",6379);sendCommend(socket,commands);if (isSerialize){byte[] result = receiveMsg(socket);List list = valueSerializer.deserialize(result);System.out.println(list);//进行了反序列化后的结果}else {Object result = getResult(socket);System.out.println(result);}} catch (IOException | ClassNotFoundException e) {throw new RuntimeException(e);}}public static void set(String key,Object value,boolean isSerialize){StringRedisSerializer keySerializer = new StringRedisSerializer();GenericObjectSerializer<Object> valueSerializer = new GenericObjectSerializer<>(Object.class);try {byte[] keyData = keySerializer.serialize(key);byte[] valueData;if(isSerialize){valueData = valueSerializer.serialize(value);}else {valueData = String.valueOf(value).getBytes();}byte[][] commands  = new byte[][]{"set".getBytes(), keyData, valueData};Socket socket = new Socket("localhost",6379);sendCommend(socket,commands);Object result = getResult(socket);System.out.println(result);} catch (IOException e) {throw new RuntimeException(e);}}/*** 这个方法是将序列化结果的字节数组读取出来* @param socket* @return*/public static byte[] receiveMsg(Socket socket){try {InputStream in = socket.getInputStream();while (true){char c = (char) in.read();if(c == '\r'){c = (char) in.read();if(c == '\n')break;}}ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[2048];int len;while ((len = in.read(buffer))!=-1){baos.write(buffer,0,len);}byte[] result = baos.toByteArray();baos.close();return result;} catch (IOException e) {throw new RuntimeException(e);}}
}

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

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

相关文章

基于嵌入式Linux的高性能车载娱乐系统设计与实现 —— 融合Qt、FFmpeg和CAN总线技术

随着汽车智能化的发展&#xff0c;车载娱乐系统已成为现代汽车的标配。本文介绍了一个基于Linux的车载娱乐系统的设计与实现过程。该系统集成了音视频娱乐、导航、车辆信息显示等功能&#xff0c;旨在提供安全、便捷、丰富的驾驶体验。 1. 项目概述 随着汽车智能化的发展&…

postgresql删除用户

背景 **角色与用户**&#xff1a;在 PostgreSQL 中&#xff0c;用户和组的概念是通过“角色”来统一实现的。角色可以有登录权限&#xff08;在这种情况下&#xff0c;它们通常被称为“用户”&#xff09;&#xff0c;也可以没有&#xff08;在这种情况下&#xff0c;它们通常用…

解决VSCode自动识别文件编码

在VScode 的 设置界面 输入 autoGuess 关键字 &#xff0c;勾选启用即可自动识别&#xff01;&#xff01;&#xff01;

高通Android 12 设置Global属性为null问题

1、最近在做app调用framework.jar需求&#xff0c;尝试在frameworks/base/packages/SettingsProvider/res/values/defaults.xml增加属性 <integer name"def_xxxxx">1</integer> 2、在frameworks\base\packages\SettingsProvider\src\com\android\provide…

LabVIEW电路产品功能自动检测系统

开发基于LabVIEW的电路产品功能自动检测系统。该系统通过整合先进的硬件和软件技术&#xff0c;实现了电路产品的自动化测试&#xff0c;显著提高了测试效率和准确性&#xff0c;对于提升电子产品的可靠性和工作效率具有重要意义。 项目背景 在电子制造业中&#xff0c;电路产…

从0到1搭建数据中台(4):neo4j初识及安装使用

在数据中台中&#xff0c;neo4j作为图数据库&#xff0c;可以用于数据血缘关系的存储 图数据库的其他用于主要用于知识图谱&#xff0c;人物关系的搭建&#xff0c;描述实体&#xff0c;关系&#xff0c;以及实体属性 安装 在官网 https://neo4j.com/ 下载安装包 neo4j-co…

Python 在Word表格中插入、删除行或列

Word文档中的表格可以用于组织和展示数据。在实际应用过程中&#xff0c;有时为了调整表格的结构或适应不同的数据展示需求&#xff0c;我们可能会需要插入、删除行或列。以下提供了几种使用Python在Word表格中插入或删除行、列的方法供参考&#xff1a; 文章目录 Python 在Wo…

数据结构(Java):力扣 二叉树面试OJ题(二)【进阶】

目录 &#x1f48e; 1、题一&#xff1a;二叉树的层序遍历 &#x1f31f; 1.1 思路1&#xff08;递归求解&#xff09; &#x1f31f; 1.1.1 思路1代码 &#x1f506; 1.2 思路2&#xff08;队列求解&#xff09; &#x1f506; 1.2.1 思路2代码 &#x1f48e; 2、题二&…

MySQL1

新建产品库mydb6_product: mysql> create database mydb6_product; mysql> use mydb6_product; 建立employees表&#xff1a; mysql> create table employees(id int primary key, name varchar(50) not null, age int, gender varchar(10) not null default unknow…

无需业务改造,一套数据库满足 OLTP 和 OLAP,GaiaDB 发布并行查询能力

在企业中通常存在两类数据处理场景&#xff0c;一类是在线事务处理场景&#xff08;OLTP&#xff09;&#xff0c;例如交易系统&#xff0c;另一类是在线分析处理场景&#xff08;OLAP&#xff09;&#xff0c;例如业务报表。 OLTP 数据库擅长处理数据的增、删、改&#xff0c…

首个WebAgent在线评测框架和流程数据管理平台来了,GPT-4、Qwen登顶闭源和开源榜首!

在当今科技迅速发展的时代&#xff0c;大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;正以前所未有的速度改变着我们与数字世界的互动方式。基于LLM的智能代理&#xff08;LLM Agent&#xff09;&#xff0c;从简单的信息搜索到复杂的网页操作&…

【Spring Cloud】掌握Gateway核心技术,实现高效路由与转发

目录 前言示例创建一个服务提供者创建网关 创建common子项目 前言 Spring Cloud Gateway 是一个基于 Spring Boot 的非阻塞 API 网关服务&#xff0c;它提供了动态路由、请求断言、过滤器等功能。 以下是关于 Spring Cloud Gateway 的示例&#xff1a; 示例 创建一个服务提…

ECMP等价多路由机制,大模型训练负载均衡流量极化冲突原因,万卡(大规模)集群语言模型(LLM)训练流量拥塞特点

大规模集群&#xff0c;大语言模型(LLM)训练流量特点&#xff0c;ECMP&#xff08;Equal-Cost Multi-Path Routing&#xff09;流量极化拥塞原因。 视频分享在这&#xff1a; 2.1 ECMP等价多路由&#xff0c;大模型训练流量特点&#xff0c;拥塞冲突极化产生原因_哔哩哔哩_bi…

【Docker】Docker-harbor私有仓库部署与管理

目录 一.Harbor 概述 1.什么是Harbor 2.Harbor的特性 3.Harbor的构成 二.Harbor 部署 1.部署 Docker-Compose 服务 2.部署 Harbor 服务 3.启动 Harbor 4.创建新项目 5.创建用户 6.本地上传镜像 7.从Harbor下载镜像 三.镜像同步 1.定时拉取 2.主动推送 四.管理 …

阿里云开源 Qwen2-Audio 音频聊天和预训练大型音频语言模型

Qwen2-Audio由阿里巴巴集团Qwen团队开发&#xff0c;它能够接受各种音频信号输入&#xff0c;对语音指令进行音频分析或直接文本回复。与以往复杂的层次标签不同&#xff0c;Qwen2-Audio通过使用自然语言提示简化了预训练过程&#xff0c;并扩大了数据量。 喜好儿网 Qwen2-Au…

1.厦门面试

1.Vue的生命周期阶段 vue生命周期分为四个阶段 第一阶段&#xff08;创建阶段&#xff09;&#xff1a;beforeCreate&#xff0c;created 第二阶段&#xff08;挂载阶段&#xff09;&#xff1a;beforeMount&#xff08;render&#xff09;&#xff0c;mounted 第三阶段&#…

人工智能导论-专家系统

专家系统 概述 本章主要介绍专家系统的概念、原理&#xff0c;创建过程&#xff0c;并补充知识发现与数据挖掘内容 **重点&#xff1a;**专家系统的工作原理和体系结构,知识获取的过程和模式 **难点&#xff1a;**如何设计和创建专家系统 AI第2次高峰(60年代) - 费根鲍姆 …

Flutter动画详解第二篇之显式动画(Explicit Animations)

目录 前言 一、定义 1.AnimationController 1.常用属性 1. value 2. status 3. duration 2.常用方法 1.forward 2.reverse 3.repeat 4.stop 5. reset 6. animateTo(double target, {Duration? duration, Curve curve Curves.linear}) 7.animateBack(double ta…

C# 智慧大棚nmodbus4

窗体 &#xff1a;图表&#xff08;chart&#xff09;&#xff1a; 下载第三方&#xff1a; nmodbus4:可以实现串口直连&#xff0c;需要创建串口对象设置串口参数配置Serialport 如果需要把串口数据表通过tcp进行网口传递 需要创建tcpclient对象 ModbusSerialMaster master; /…

MyPostMan 迭代文档管理、自动化接口闭环测试工具(自动化测试篇)

MyPostMan 是一款类似 PostMan 的接口请求软件&#xff0c;按照 项目&#xff08;微服务&#xff09;、目录来管理我们的接口&#xff0c;基于迭代来管理我们的接口文档&#xff0c;文档可以导出和通过 url 实时分享&#xff0c;按照迭代编写自动化测试用例&#xff0c;在不同环…