【序列化与反序列化】关于序列化与反序列化MessagePack的实践
- 在进行序列化操作之前,我们还对系统进行压测,通过
jvisualvm
分析cpu,线程,垃圾回收情况等;运用火焰图async-profiler
分析系统性能,找出程序中占用CPU资源时间最长的代码块。 - 代码放置GitHub:https://github.com/nateshao/leetcode/tree/main/source-code/src/main/java/com/nateshao/source/code/serializable
1.什么是序列化与反序列化?
聊到序列化与反序列化,先看看这个这个是什么或者是干嘛的
定义:序列化是指把
对象
转换为字节序列
的过程;反序列化
是指把字节序列
恢复为对象
的过程;
一般都会知道有两个目的:对象持久化
和网络传输
。
- 对象持久化。实现了数据的持久化,通过序列化可以把数据永久的保存在硬盘上;
- 网络传输。我们知道网络传输的是字节序列,那么利用序列化实现远程通信,即在网络上传递对象的字节序列。 (序列化与反序列化则实现了 进程通信间的对象传送,发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象)
总结:序列化的目的是将对象变成字节序列,这样一来方便持久化存储到磁盘,避免程序运行结束后对象就从内存里消失,另外字节序列也更便于网络运输和传播
2.选择序列化技术有哪些维度
这里并不是说哪个是最好,只能说是各有千秋,所以面对不同的背景采用不同的技术方案。
在同等情况下,编码后的字节数组越大,存储占空间,存储硬件成本高,网络传输时也占带宽,导致系统的吞吐量降低。
- 开发工作量和难度。工作量越少越好对吧,发挥的价值最大。
- 性能:需要考虑性能需求,序列化的速度和性能越高越好。
- 是否支持跨语言,支持的语言种类是否丰富。
- 安全性:对于安全性的需要考量。JDK序列化可能有卡死线程的漏洞。
- 占用空间大小:序列化的结果所占用的空间大小,序列化后的字节数据通常会持久化到硬盘(占用存储资源)或者在网络上传输给其他服务器(占用带宽资源),这个指标当然是越小越好。
- 可维护性:技术流行程序,越流行的技术可维护性就越高,维护成本会越低。
3.为什么采用字节序列MessagePack,有什么依据?
这个要结合当时的项目背景了。当时的项目痛点是:
- 老项目的结构调整比较大,交易性能上不去,所以先采取细节方面的优化。MessagePack是满足性能要求。
- 老系统业务变化不多,MessagePack是满足要求。
- 序列化反序列化效率高(比json快一倍),文件体积小,比json小一倍。MessagePack是满足要求
所以,存在MessagePack也有不好的地方,如果是针对业务变化比较多,那就不适合,需要比较不同的版本,然后选择不同版本去解析。
我在京东內部文章有看到,引用的MessagePack反序列化出现了一些线上的问题,原因是:新增了一些字段导致反序列化失败。
这里对比了JDK(不支持跨语言),JSON,Protobuf,MessagePack几种序列化的方式。
当然,还有其他的XML、Hessian、Kryo(不支持跨语言)、FST(不支持跨语言)Thrift等。
4.JDK序列化
JDK序列化是Java默认自带的序列化方式。在Java中,一个对象要想实现序列化,实现Serializable
接口或者Externalizable
接口即可。其中Externalizable
接口继承自Serializable
接口。
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name = null;
private Integer age = null;
private SerializeDemo01.Sex sex;
//....
}
serialVersionUID
版本控制的作用
- 序列化的时候
serialVersionUID
也会被写入二进制序列 - 反序列化时会检查
serialVersionUID
是否和当前类的serialVersionUID
一致。不一致则会抛出InvalidClassException
异常(序列化之后给类增加字段再反序列化的时候就会报错)
serialVersionUID必须保证实体类在序列化前后严格一致,否则将会导致无法反序列化。
JDK默认的序列化机制。需要实现java.io.Serializable接口
- 序列化后的码流太大:jdk序列化机制编码后的二进制数组大小竟然是二进制编码的5.29倍。
- 不支持跨语言平台调用, 性能较差(序列化后字节数组体积大)
- 序列化性能太低:java序列化的性能只有二进制编码的6.17%左右,Java原生序列化的性能实在太差。
JDK默认的序列化机制很差。所以我们通常不会选择Java默认序列化这种
5.JSON序列化
json格式也是常见的一种,但是在json在解析的时候非常耗时,而且json结构非常占内存。JSON不适合存数字,特别是DOUBLE
这里基于Fastjson ,加载maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
序列化对象
利用JSON.toJSONString方法序列化对象:
UserVO user = ...;String text = JSON.toJSONString(user);
序列化数组
利用JSON.toJSONString方法序列化数组:
UserVO[] users = ...;String text = JSON.toJSONString(users);
序列化集合
利用JSON.toJSONString方法序列化集合(继承至Collection,比如List、Set等集合):
List<UserVO> userList = ...;String text = JSON.toJSONString(userList);
序列化映射
利用JSON.toJSONString方法序列化映射:
Map<Long, UserVO> userMap = ...;String text = JSON.toJSONString(userMap, SerializerFeature.MapSortField);
其中,为了保证每次序列化的映射字符串一致,需要指定序列化参数MapSortField进行排序。
序列化模板对象
利用JSON.toJSONString方法序列化模板对象:
Result<UserVO> result = ...;String text = JSON.toJSONString(result);
代码
package com.nateshao.source.code.serializable.json_Serializable.serializable;
import com.alibaba.fastjson.annotation.JSONField;
/**
* @date Created by 邵桐杰 on 2023/2/25 17:11
* @微信公众号 千羽的编程时光
* @博客 https://nateshao.gitlab.io
* @GitHub https://github.com/nateshao
* Description:
*/
public class User {
/**
* @JSONField 作用:自定义对象属性所对应的 JSON 键名
* @JSONField 的作用对象:
* 1. Field
* 2. Setter 和 Getter 方法
* 注意:
* 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
* 2. 若没有 @JSONField 注解,则直接使用属性名。
*/
@JSONField(name = "NAME")
private String name;
@JSONField(name = "AGE")
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
序列化ObjectTest
package com.nateshao.source.code.serializable.json_Serializable.serializable;
import com.alibaba.fastjson.JSON;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @date Created by 邵桐杰 on 2023/2/25 17:12
* @微信公众号 千羽的编程时光
* @博客 https://nateshao.gitlab.io
* @GitHub https://github.com/nateshao
* Description:
*/
public class ObjectTest {
private static List<User> userList = new ArrayList<User>();
@BeforeAll
public static void setUp() {
userList.add(new User("千羽", 18));
userList.add(new User("千寻", 19));
}
@DisplayName("序列化对象")
@Test
public void testObjectToJson() {
String userJson = JSON.toJSONString(userList.get(0));
System.out.println(userJson); // {"AGE":18,"NAME":"千羽"}
}
@DisplayName("序列化集合")
@Test
public void testListToJson() {
String userListJson = JSON.toJSONString(userList);
System.out.println(userListJson); // [{"AGE":18,"NAME":"千羽"},{"AGE":19,"NAME":"千寻"}]
}
@DisplayName("序列化数组")
@Test
public void testArrayToJson() {
User[] userArray = new User[5];
userArray[0] = new User("zhangsan", 20);
userArray[1] = new User("lisi", 21);
String userArrayJson = JSON.toJSONString(userArray);
System.out.println(userArrayJson); // [{"AGE":20,"NAME":"zhangsan"},{"AGE":21,"NAME":"lisi"},null,null,null]
}
@DisplayName("序列化映射")
@Test
public void testMapToJson() {
Map<Integer, User> userMap = new HashMap<Integer, User>();
userMap.put(1, new User("xiaotie", 10));
userMap.put(2, new User("xiaoliu", 11));
String userMapJson = JSON.toJSONString(userMap);
System.out.println(userMapJson); // {1:{"AGE":10,"NAME":"xiaotie"},2:{"AGE":11,"NAME":"xiaoliu"}}
}
}
反序列化UN_ObjectTest
package com.nateshao.source.code.serializable.json_Serializable.un_serializable;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @date Created by 邵桐杰 on 2023/2/25 17:16
* @微信公众号 千羽的编程时光
* @博客 https://nateshao.gitlab.io
* @GitHub https://github.com/nateshao
* Description:
*/
public class UN_ObjectTest {
@DisplayName("反序列化对象")
@Test
public void testJsonToObject() {
String text = "{"age":18,"name":"千羽"}";
User user = JSON.parseObject(text, User.class);
System.out.println(user); // User{name='千羽', age=18}
}
@DisplayName("反序列化数组")
@Test
public void testJsonToArray() {
String text = "[{"age":18,"name":"千羽"}, {"age":19,"name":"千寻"}]";
User[] users = JSON.parseObject(text, User[].class);
System.out.println(Arrays.toString(users)); // [User{name='千羽', age=18}, User{name='千寻', age=19}]
}
@DisplayName("反序列化集合")
@Test
public void testJsonToCollection() {
String text = "[{"age":18,"name":"千羽"}, {"age":19,"name":"千寻"}]";
// List 集合
List<User> userList = JSON.parseArray(text, User.class);
System.out.println(Arrays.toString(userList.toArray())); // [User{name='千羽', age=18}, User{name='千寻', age=19}]
// Set 集合
Set<User> userSet = JSON.parseObject(text, new TypeReference<Set<User>>() {
});
System.out.println(Arrays.toString(userSet.toArray())); // [User{name='千寻', age=19}, User{name='千羽', age=18}]
}
@DisplayName("反序列化映射")
@Test
public void testJsonToMap() {
String text = "{1:{"age":18,"name":"千羽"}, 2:{"age":19,"name":"千寻"}}";
Map<Integer, User> userList = JSON.parseObject(text, new TypeReference<Map<Integer, User>>() {
});
for (Integer i : userList.keySet()) {
System.out.println(userList.get(i));
}
/*
User{name='千羽', age=18}
User{name='千寻', age=19}
*/
}
}
json序列化总结
好处
- 简单易用开发成本低
- 跨语言
- 轻量级数据交换
- 非冗长性(对比xml标签简单括号闭环)
缺点
- 体积大,影响高并发
- 无版本检查,自己做兼容
- 片段的创建和验证过程比一般的XML复杂
- 缺乏命名空间导致信息混合
6.Protobuf
Protobuf是谷歌推出的,是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于通信协议、数据存储等。序列化后体积小,一般用于对传输性能有较高要求的系统。前期需要额外配置环境变量,学习他的语法也是需要时间。
但是要使用Protobuf会相对来说麻烦一些,因为他有自己的语法,有自己的编译器,如果需要用到的话必须要去投入成本在这个技术的学习中。
Protobuf有个缺点就是传输的每一个类的结构都要生成相对应的proto文件,如果某个类发生修改,还要重新生成该类对应的proto文件。另外,Protobuf对象冗余,字段很多,生成的类较大,占用空间。维护成本较高。
// 声明语法,不显示声明默认是2.0的语法。
syntax = "proto3";
// 指定模板类的包路径
option java_package = "com.test.basic.java.serialize.proto.dto";
// 指定模板类的名称,名称必须是有实际业务意义的
option java_outer_classname = "UserProto";
// 定义用户对象
message User {
// 名字
string name = 1;
// 年龄
int32 age = 2;
}
// 声明语法,不显示声明默认是2.0的语法。
syntax = "proto3";
// 指定模板类的包路径
option java_package = "com\nateshao\source\code\serializable\protobuf_Serializable\User.proto";
// 指定模板类的名称,名称必须是有实际业务意义的
option java_outer_classname = "UserProto";
// 定义用户对象
message User {
// 名字
string name = 1;
// 年龄
int32 age = 2;
}
7.MessagePack(推荐)
- 序列化反序列化效率高(比json快一倍),文件体积小,比json小一倍。
- 兼容json数据格式
- 跨语言,多语言支持(超多)
不好的地方:
- 缺乏复杂模型支持。msgpack对复杂的数据类型(List、Map)支持的不够,序列化没有问题,但是反序列化回来就很麻烦,尤其是对于java开发人员。
- 维护成本较高。msgpack通过value的顺序来定位属性的,需要在不同的语言中都要维护同样的模型以及模型中属性的顺序。
- 不支持模型嵌套。msgpack无法支持在模型中包含和嵌套其他自定义的模型(如weibo模型中包含comment的列表)。
上手
通过配置msgpack的maven依赖
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.12</version>
</dependency>
封装MsgPackTemplate抽象类,对原有msgpack进行二次加工,比如int,Short,Byte,BigDecimal进行read和write处理
MsgPackTemplate.java
package com.nateshao.source.code.serializable.msgpack_Serializable;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import org.msgpack.packer.Packer;
import org.msgpack.template.CharacterTemplate;
import org.msgpack.template.Template;
import org.msgpack.unpacker.Unpacker;
/**
* @date Created by 邵桐杰 on 2023/2/25 23:11
* @微信公众号 千羽的编程时光
* @博客 https://nateshao.gitlab.io
* @GitHub https://github.com/nateshao
* Description:
*/
public abstract class MsgPackTemplate <T> implements Template<T> {
public void write(Packer pk, T v) throws IOException {
write(pk, v, false);
}
public T read(Unpacker u, T to) throws IOException {
return read(u, to, false);
}
public BigDecimal readBigDecimal(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
String temp = u.readString();
if (temp != null) {
return new BigDecimal(temp);
}
return null;
}
public Date readDate(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
long temp = u.readLong();
if (temp > 0) {
return new Date(temp);
}
return null;
}
public String readString(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readString();
}
public Integer readInteger(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readInt();
}
public Byte readByte(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readByte();
}
public Short readShort(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readShort();
}
public Float readFloat(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readFloat();
}
public Double readDouble(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readDouble();
}
public Character readCharacter(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.read(CharacterTemplate.getInstance());
}
public Long readLong(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readLong();
}
public Boolean readBoolean(Unpacker u) throws IOException {
if (u.trySkipNil()) {
return null;
}
return u.readBoolean();
}
public void writeBigDecimal(Packer pk, BigDecimal v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.toString());
}
}
public void writeDate(Packer pk, Date v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.getTime());
}
}
public void writeString(Packer pk, String v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v);
}
}
public void writeInt(Packer pk, int v) throws IOException {
pk.write(v);
}
public void writeInteger(Packer pk, Integer v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.intValue());
}
}
public void writeByte(Packer pk, Byte v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.byteValue());
}
}
public void writeShort(Packer pk, Short v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.shortValue());
}
}
public void writeFloat(Packer pk, Float v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.floatValue());
}
}
public void writeDouble(Packer pk, Double v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.doubleValue());
}
}
public void writeCharacter(Packer pk, Character v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.charValue());
}
}
public void writeLong(Packer pk, Long v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.longValue());
}
}
public void writeLong(Packer pk, long v) throws IOException {
pk.write(v);
}
public void writeBoolean(Packer pk, Boolean v) throws IOException {
if (v == null) {
pk.writeNil();
} else {
pk.write(v.booleanValue());
}
}
}
然后针对实体类进行读和写的序列化与反序列化操作。
User.java
package com.nateshao.source.code.serializable.msgpack_Serializable;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
private static final long serialVersionUID = 4719921525393585541L;
protected String id;
private String userID;
private String type;
private BigDecimal rate;
private Date createTime;
private Date UpdateTime;
}
UserTemplate.java
package com.nateshao.source.code.serializable.msgpack_Serializable;
import org.msgpack.packer.Packer;
import org.msgpack.unpacker.Unpacker;
import java.io.IOException;
/**
* protected String id;
* private String userID;
* private String type;
* private BigDecimal rate;
* private Date createTime;
* private Date UpdateTime;
*/
public class UserTemplate extends MsgPackTemplate<User> {
public void write(Packer packer, User user, boolean b) throws IOException {
if (user == null) {
packer.write(user);
return;
}
// 字段保持一致
writeString(packer, user.id);
writeString(packer, user.getUserID());
writeString(packer, user.getType());
writeBigDecimal(packer, user.getRate());
writeDate(packer, user.getCreateTime());
writeDate(packer, user.getUpdateTime());
}
public User read(Unpacker unpacker, User user, boolean req) throws IOException {
if (!req && unpacker.trySkipNil()) {
return null;
}
if (unpacker == null) return new User();
user.setId(readString(unpacker));
user.setUserID(readString(unpacker));
user.setType(readString(unpacker));
user.setRate(readBigDecimal(unpacker));
user.setCreateTime(readDate(unpacker));
user.setUpdateTime(readDate(unpacker));
return user;
}
}
参考文献:
- 官网:MessagePack: It's like JSON. but fast and small:https://msgpack.org/)
- msgpack-java:https://github.com/msgpack/msgpack-java
- MessagePack for C#译文:快速序列化组件MessagePack介绍 - 晓晨Master - 博客园:https://www.cnblogs.com/stulzq/p/8039933.html
- 原理分析和使用:https://www.jianshu.com/p/8c24bef40e2f
- https://blog.csdn.net/wzngzaixiaomantou/article/details/123031809
- https://blog.csdn.net/weixin_44299027/article/details/129050484
- https://www.cnblogs.com/juno3550/p/15431623.html
- Protobuf:https://blog.csdn.net/wxw1997a/article/details/116755542
作者:京东零售 邵桐杰
来源:京东云开发者社区
【序列化与反序列化】关于序列化与反序列化MessagePack的实践的更多相关文章
- Flink1.4.0中反序列化及序列化类变化
Flink1.4.0中,反序列化及序列化时继承的类,有一些被标记为了“@deprecated”,路径上也有变化: 1.AbstractDeserializationSchema 以前路径 org.ap ...
- java序列化和反序列化及序列化方式
平时我们在Java内存中的对象,是无 法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即 存储对象中的状态 ...
- 【Django drf】 序列化类常用字段类和字段参数 定制序列化字段的两种方式 关系表外键字段的反序列化保存 序列化类继承ModelSerializer 反序列化数据校验源码分析
目录 序列化类常用字段类和字段参数 常用字段类 常用字段参数 选项参数 通用参数 序列化类高级用法之source source填写类中字段 source填写模型类中方法 source支持跨表查询 定制 ...
- drf-day3——drf整体流程、APIView执行流程及源码分析、Request对象源码分析、序列化器介绍和使用、反序列化的使用、反序列化的校验
目录 一.drf 整体内容 二.APIView执行流程--源码分析(难,了解) 2.1 基于APIView+JsonResponse编写接口 2.2 基于APIView+Response 写接口 2. ...
- 序列化战争:主流序列化框架Benchmark
序列化战争:主流序列化框架Benchmark GitHub上有这样一个关于序列化的Benchmark,被好多文章引用.但这个项目考虑到完整性,代码有些复杂.为了个人学习,自己实现了个简单的Benchm ...
- [.net 面向对象程序设计进阶] (13) 序列化(Serialization)(五) Json 序列化利器 Newtonsoft.Json 及 通用Json类
[.net 面向对象程序设计进阶] (13) 序列化(Serialization)(五) Json 序列化利器 Newtonsoft.Json 及 通用Json类 本节导读: 关于JSON序列化,不能 ...
- .NET 二进制序列化器,SOAP序列化器,XML序列化器
这里就不说JSON序列化了,只介绍三种:二进制序列化器,SOAP序列化器,XML序列化器 直接上代码: /// <summary> /// 二进制序列化器. /// 最节省流量,压缩程度最 ...
- 字定义JSON序列化支持datetime格式序列化
字定义JSON序列化支持datetime格式序列化 由于json.dumps无法处理datetime日期,所以可以通过自定义处理器来做扩展,如: import json from datetime i ...
- Atitit php序列化 php的serialize序列化和json序列化
Atitit php序列化 php的serialize序列化和json序列化 PHP 对不同类型的数据用不同的字母进行标示,Yahoo 开发网站提供的Using Serialized PHP with ...
- 从LocalDateTime序列化探讨全局一致性序列化
日拱一卒无有尽,功不唐捐终入海. 楔子 前两周发了三篇SpringSecurity和一篇征文,这周打算写点简单有用易上手的文章,换换脑子,休息一下. 今天要写的是这篇:从LocalDateTime序列 ...
随机推荐
- matplotlab可视化学习
1 使用pip安装 使用 Python 包管理器 pip 来安装 Matplotlib 是一种最轻量级的方式.打开 CMD 命令提示符窗口,并输入以下命令: pip install matplotli ...
- LeeCode数组问题:二分查找
LeeCode 704 二分查找 题目描述: 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标, ...
- Go For Web:踏入Web大门的第一步——Web 的工作方式
前言: 本文作为解决如何通过 Golang 来编写 Web 应用这个问题的前瞻,对 Golang 中的 Web 基础部分进行一个简单的介绍.目前 Go 拥有成熟的 Http 处理包,所以我们去编写一个 ...
- PHPCMSV9 单文件上传功能代码
后台有"多文件上传"功能,但是对于有些情况,我们只需要上传一个文件,而使用多文件上传功能上传一个文件,而调用时调用一个文件太麻烦了. 所以我就自己动手,参考其他字段类型的网站,研究 ...
- 微信小程序隐藏页面滚动条
开发小程序时,经常会碰到页面长度超过屏幕高度,然后下拉时会出现滚动条,对于一些有强迫症的人来说是不可忍受的. 网上看了好多,写的.都评论有起作用或者不起作用的. 我在这分享一个全局隐藏滚动条的方式. ...
- 企名片Js逆向思路
企名片Js逆向思路 这个案例不算难,简单说一下思路. 目标链接:https://www.qimingpian.cn/finosda/project/pinvestment 网站更新了https://w ...
- python中的一些解码和编码
开头 最近爬取百度贴吧搜索页的时候遇到一个url的编码问题,颇为头疼,记录下来防止下次忘记 工具网站 解码编码的工具网站推荐 http://tool.chinaz.com/tools/urlencod ...
- Vue2积分商城项目
一.清空项目非必要文件和用户片段,路径提示的配置 views 下面的文件只保留 Home.vue ,其余删除,删除 components/HelloWorld.vue,并且 Home.vue 中不再引 ...
- Rocky 9 Linux 软件安装 neovim 和 git
目录 编辑器 Neovim 版本控制工具 Git RHEL 系列软件安装介绍 软件安装包简介 源码包安装 rpm包安装 yum & dnf 在线安装 脚本安装包 rockyLinux 介绍软件 ...
- 2023-01-13:joxit/docker-registry-ui是registry的web界面工具之一。请问部署在k3s中,yaml如何写?
2023-01-13:joxit/docker-registry-ui是registry的web界面工具之一.请问部署在k3s中,yaml如何写? 答案2023-01-13: yaml如下: apiV ...