【序列化與反序列化】關於序列化與反序列化MessagePack的實踐

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/07/03/17523231.html
-Advertisement-
Play Games

序列化的目的是將對象變成位元組序列,這樣一來方便持久化存儲到磁碟,避免程式運行結束後對象就從記憶體里消失,另外位元組序列也更便於網路運輸和傳播 ...


1.什麼是序列化與反序列化?

聊到序列化與反序列化,先看看這個這個是什麼或者是幹嘛的

定義:序列化是指把對象轉換為位元組序列的過程;反序列化是指把位元組序列恢復為對象的過程;

一般都會知道有兩個目的:對象持久化網路傳輸

  1. 對象持久化。實現了數據的持久化,通過序列化可以把數據永久的保存在硬碟上;
  2. 網路傳輸。我們知道網路傳輸的是位元組序列,那麼利用序列化實現遠程通信,即在網路上傳遞對象的位元組序列。 (序列化與反序列化則實現了 進程通信間的對象傳送,發送方需要把這個Java對象轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為Java對象)

總結:序列化的目的是將對象變成位元組序列,這樣一來方便持久化存儲到磁碟,避免程式運行結束後對象就從記憶體里消失,另外位元組序列也更便於網路運輸和傳播

2.選擇序列化技術有哪些維度

這裡並不是說哪個是最好,只能說是各有千秋,所以面對不同的背景採用不同的技術方案。

在同等情況下,編碼後的位元組數組越大,存儲占空間,存儲硬體成本高,網路傳輸時也占帶寬,導致系統的吞吐量降低。

  1. 開發工作量和難度。工作量越少越好對吧,發揮的價值最大。
  2. 性能:需要考慮性能需求,序列化的速度和性能越高越好。
  3. 是否支持跨語言,支持的語言種類是否豐富。
  4. 安全性:對於安全性的需要考量。JDK序列化可能有卡死線程的漏洞。
  5. 占用空間大小:序列化的結果所占用的空間大小,序列化後的位元組數據通常會持久化到硬碟(占用存儲資源)或者在網路上傳輸給其他伺服器(占用帶寬資源),這個指標當然是越小越好。
  6. 可維護性:技術流行程式,越流行的技術可維護性就越高,維護成本會越低。

3.為什麼採用位元組序列MessagePack,有什麼依據?

這個要結合當時的項目背景了。當時的項目痛點是:

  1. 老項目的結構調整比較大,交易性能上不去,所以先採取細節方面的優化。MessagePack是滿足性能要求
  2. 老系統業務變化不多,MessagePack是滿足要求
  3. 序列化反序列化效率高(比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版本控制的作用

  1. 序列化的時候serialVersionUID也會被寫入二進位序列
  2. 反序列化時會檢查serialVersionUID是否和當前類的serialVersionUID一致。不一致則會拋出InvalidClassException 異常(序列化之後給類增加欄位再反序列化的時候就會報錯)

serialVersionUID必須保證實體類在序列化前後嚴格一致,否則將會導致無法反序列化。

JDK預設的序列化機制。需要實現java.io.Serializable介面

  1. 序列化後的碼流太大:jdk序列化機制編碼後的二進位數組大小竟然是二進位編碼的5.29倍。
  2. 不支持跨語言平臺調用, 性能較差(序列化後位元組數組體積大)
  3. 序列化性能太低: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序列化總結

  • 好處

    1. 簡單易用開發成本低
    2. 跨語言
    3. 輕量級數據交換
    4. 非冗長性(對比xml標簽簡單括弧閉環)
  • 缺點

    1. 體積大,影響高併發
    2. 無版本檢查,自己做相容
    3. 片段的創建和驗證過程比一般的XML複雜
    4. 缺乏命名空間導致信息混合

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(推薦)

  1. 序列化反序列化效率高(比json快一倍),文件體積小,比json小一倍。
  2. 相容json數據格式
  3. 跨語言,多語言支持(超多)

不好的地方:

  1. 缺乏複雜模型支持。msgpack對複雜的數據類型(List、Map)支持的不夠,序列化沒有問題,但是反序列化回來就很麻煩,尤其是對於java開發人員。
  2. 維護成本較高。msgpack通過value的順序來定位屬性的,需要在不同的語言中都要維護同樣的模型以及模型中屬性的順序。
  3. 不支持模型嵌套。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;
    }
}

參考文獻:

  1. https://blog.csdn.net/wzngzaixiaomantou/article/details/123031809
  2. https://blog.csdn.net/weixin_44299027/article/details/129050484
  3. https://www.cnblogs.com/juno3550/p/15431623.html
  4. Protobuf:https://blog.csdn.net/wxw1997a/article/details/116755542

作者:京東零售 邵桐傑

來源:京東雲開發者社區


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ![](https://img2023.cnblogs.com/blog/3076680/202306/3076680-20230628121233652-2011697937.png) # 1. 完全的解耦 ## 1.1. 各台伺服器、層級和應用程式解耦得越徹底,集成點、層疊失效、響應緩慢和線程阻 ...
  • ### 前言 今天在對接阿裡雲OSS對象存儲, 把這過程記錄下來 ### 鏈接 阿裡雲的內容很多,文檔是真的難找又難懂 本文主要是用的PostObject API 加上 Callback參數 PostObject -> [https://help.aliyun.com/document_detail ...
  • 某日二師兄參加XXX科技公司的C++工程師開發崗位第31面: > 面試官:`strcpy`函數使用過吧? > > 二師兄:用過。 > > 面試官:這個函數有什麼作用? > > 二師兄:主要用做字元串複製,將於字元從一個位置複製到另一個位置。 > > 面試官:`strncpy`函數也使用過吧,和`st ...
  • 開源地址:https://gitee.com/chejiangyi/jar-protect 介紹 java 本身是開放性極強的語言,代碼也容易被反編譯,沒有語言層面的一些常規保護機制,jar包很容易被反編譯和破解。 受classfinal(已停止維護)設計啟發,針對springboot日常項目開發, ...
  • # 一. 安裝Go語言開發環境 ## 1. Wondows下搭建Go開發環境 ### (1). 下載SDK工具包 **sdk下載地址為:**[__https://go.dev/dl/__](https://go.dev/dl/) ![](https://tcs-devops.aliyuncs.com ...
  • 前幾天在項目讀取resources目錄下的文件時碰到一個小坑,明明在本地是可以正常運行的,但是一發到測試環境就報錯了,說找不到文件,報錯信息是:class path resource [xxxx] cannot be resolved to absolute file path because it... ...
  • 哈嘍大家好,我是鹹魚 今天我們從幾個方面來比較一些現在流行的兩個 python web 框架——Flask 和 Django,突出它們的主要特性、優缺點和簡單案例 到最後,大家將更好地瞭解哪個框架更適合自己的特定需求 參考鏈接:https://djangocentral.com/flask-vs-d ...
  • GO 語言中 chan 的理解 ### chan 的底層實現是怎麼樣的? > chan 是 Go 語言中的一個關鍵字,用於實現併發通信。chan 可以用於在不同的 goroutine 之間傳遞數據,實現數據的同步和非同步傳輸。 在底層實現上,chan 是通過一個結構體來表示的,這個結構體包含了一個指向 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...