Protobuf: 高效数据传输的秘密武器

news2024/11/27 1:39:45

当涉及到网络通信和数据存储时,数据序列化一直都是一个重要的话题;特别是现在很多公司都在推行微服务,数据序列化更是重中之重,通常会选择使用 JSON 作为数据交换格式,且 JSON 已经成为业界的主流。但是 Google 这么大的公司使用的却是一种被称为 Protobuf 的数据交换格式,它是有什么优势吗?这篇文章介绍 Protobuf 的相关知识。

GitHub:https://github.com/protocolbuffers/protobuf

官方文档:https://protobuf.dev/overview/

Protobuf 介绍

Protobuf(Protocol Buffers)是由 Google 开发的一种轻量级、高效的数据交换格式,它被用于结构化数据的序列化、反序列化和传输。相比于 XML 和 JSON 等文本格式,Protobuf 具有更小的数据体积、更快的解析速度和更强的可扩展性。

Protobuf 的核心思想是使用协议(Protocol)来定义数据的结构和编码方式。使用 Protobuf,可以先定义数据的结构和各字段的类型、字段等信息,然后使用Protobuf提供的编译器生成对应的代码用于序列化和反序列化数据。由于 Protobuf 是基于二进制编码的,因此可以在数据传输和存储中实现更高效的数据交换,同时也可以跨语言使用。

相比于 XML 和 JSON,Protobuf 有以下几个优势

  • 更小的数据量:Protobuf 的二进制编码通常比 XML 和 JSON 小 3-10 倍,因此在网络传输和存储数据时可以节省带宽和存储空间。

  • 更快的序列化和反序列化速度:由于 Protobuf 使用二进制格式,所以序列化和反序列化速度比 XML 和 JSON 快得多。

  • 跨语言:Protobuf 支持多种编程语言,可以使用不同的编程语言来编写客户端和服务端。这种跨语言的特性使得 Protobuf 受到很多开发者的欢迎(JSON 也是如此)。

  • 易于维护可扩展:Protobuf 使用 .proto 文件定义数据模型和数据格式,这种文件比 XML 和 JSON 更容易阅读和维护,且可以在不破坏原有协议的基础上,轻松添加或删除字段,实现版本升级和兼容性。

编写 Protobuf

使用 Protobuf 的语言定义文件(.proto)可以定义要传输的信息的数据结构,可以包括各个字段的名称、类型等信息。同时也可以相互嵌套组合,构造出更加复杂的消息结构。

比如想要构造一个地址簿 AddressBook 信息结构。一个 AddressBook 可以包含多个人员 Person 信息,每个 Person 信息可以包含 id、name、email 信息,同时一个 Person 也可以包含多个电话号码信息 PhoneNumber,每个电话号码信息需要指定号码种类,如手机、家庭电话、工作电话等。

如果使用 Protobuf 编写定义文件如下:

// 文件:addressbook.proto
syntax = "proto3";
// 指定 protobuf 包名,防止有相同类名的 message 定义
package com.wdbyte.protobuf;
// 是否生成多个文件
option java_multiple_files = true;
// 生成的文件存放在哪个包下
option java_package = "com.wdbyte.tool.protos";
// 生成的类名,如果没有指定,会根据文件名自动转驼峰来命名
option java_outer_classname = "AddressBookProtos";

message Person {
  // =1,=2 作为序列化后的二进制编码中的字段的唯一标签,也因此,1-15 比 16 会少一个字节,所以尽量使用 1-15 来指定常用字段。
  optional int32 id = 1;
  optional string name = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    optional string number = 1;
    optional PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

Protobuf 文件中的语法解释。

头部全局定义

  • syntax = "proto3";指定 Protobuf 版本为版本3(最新版本)
  • package com.wdbyte.protobuf;指定 Protobuf 包名,防止有相同类名的 message 定义,这个包名是生成的类中所用到的一些信息的前缀,并非类所在包。
  • option java_multiple_files = true; 是否生成多个文件。若 false,则只会生成一个类,其他类以内部类形式提供。
  • option java_package = 生成的类所在包。
  • option java_outer_classname 生成的类名,若无,自动使用文件名进行驼峰转换来为类命名。

消息结构具体定义

message Person 定一个了一个 Person 类。

Person 类中的字段被 optional 修饰,被 optional 修饰说明字段可以不赋值。

  • 修饰符 optional 表示可选字段,可以不赋值。
  • 修饰符 repeated 表示数据重复多个,如数组,如 List。
  • 修饰符 required 表示必要字段,必须给值,否则会报错 RuntimeException,但是在 Protobuf 版本 3 中被移除。即使在版本 2 中也应该慎用,因为一旦定义,很难更改。

字段类型定义

修饰符后面紧跟的是字段类型,如 int32string。常用的类型如下:

  • int32、int64、uint32、uint64:整数类型,包括有符号和无符号类型。

  • float、double:浮点数类型。

  • bool:布尔类型,只有两个值,true 和 false。

  • string:字符串类型。

  • bytes:二进制数据类型。

  • enum:枚举类型,枚举值可以是整数或字符串。

  • message:消息类型,可以嵌套其他消息类型,类似于结构体。

字段后面的 =1,=2 是作为序列化后的二进制编码中的字段的对应标签,因为 Protobuf 消息在序列化后是不包含字段信息的,只有对应的字段序号,所以节省了空间。也因此,1-15 比 16 会少一个字节,所以尽量使用 1-15 来指定常用字段。且一旦定义,不要随意更改,否则可能会对不上序列化信息

编译 Protobuf

使用 Protobuf 提供的编译器,可以将 .proto 文件编译成各种语言的代码文件(如 Java、C++、Python 等)。

下载编译器:https://github.com/protocolbuffers/protobuf/releases/latest

安装完成后可以使用 protoc 命令编译 proto 文件,如编译示例中的 addressbook.proto.

protoc --java_out=./java ./resources/addressbook.proto
# --java_out 指定输出 java 格式文件,输出到 ./java 目录
# ./resources/addressbook.proto 为 proto 文件位置

生成后可以看到生产的类文件。

./
├── java
│   └── com
│       └── wdbyte
│           └── tool
│               ├── protos
│               │   ├── AddressBook.java
│               │   ├── AddressBookOrBuilder.java
│               │   ├── AddressBookProtos.java
│               │   ├── Person.java
│               │   ├── PersonOrBuilder.java
└── resources
    ├── addressbook.proto

使用 Protobuf

使用 Java 语言操作 Protobuf,首先需要引入 Protobuf 依赖。

Maven 依赖:

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.22.3</version>
</dependency>

构造消息对象

// 直接构建
PhoneNumber phoneNumber1 = PhoneNumber.newBuilder().setNumber("18388888888").setType(PhoneType.HOME).build();
Person person1 = Person.newBuilder().setId(1).setName("www.wdbyte.com").setEmail("xxx@wdbyte.com").addPhones(phoneNumber1).build();
AddressBook addressBook1 = AddressBook.newBuilder().addPeople(person1).build();
System.out.println(addressBook1);
System.out.println("------------------");

//  链式构建
AddressBook addressBook2 = AddressBook
    .newBuilder()
    .addPeople(Person.newBuilder()
                     .setId(2)
                     .setName("www.wdbyte.com")
                     .setEmail("yyy@126.com")
                    .addPhones(PhoneNumber.newBuilder()
                                          .setNumber("18388888888")
                                          .setType(PhoneType.HOME)
                    )
    )
    .build();
System.out.println(addressBook2);

输出:

people {
  id: 1
  name: "www.wdbyte.com"
  email: "xxx@wdbyte.com"
  phones {
    number: "18388888888"
    type: HOME
  }
}

------------------
people {
  id: 2
  name: "www.wdbyte.com"
  email: "yyy@126.com"
  phones {
    number: "18388888888"
    type: HOME
  }
}

序列化、反序列化

序列化:将内存中的数据对象序列化为二进制数据,可以用于网络传输或存储等场景。

反序列化:将二进制数据反序列化成内存中的数据对象,可以用于数据处理和业务逻辑。

下面演示使用 Protobuf 进行字符数组和文件的序列化及反序列化过程。

package com.wdbyte.tool.protos;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 
 * @author www.wdbyte.com
 */
public class ProtobufTest2 {

    public static void main(String[] args) throws IOException {
        PhoneNumber phoneNumber1 = PhoneNumber.newBuilder().setNumber("18388888888").setType(PhoneType.HOME).build();
        Person person1 = Person.newBuilder().setId(1).setName("www.wdbyte.com").setEmail("xxx@wdbyte.com").addPhones(phoneNumber1).build();
        AddressBook addressBook1 = AddressBook.newBuilder().addPeople(person1).build();
      
        // 序列化成字节数组
        byte[] byteArray = addressBook1.toByteArray();
        // 反序列化 - 字节数组转对象
        AddressBook addressBook2 = AddressBook.parseFrom(byteArray);
        System.out.println("字节数组反序列化:");
        System.out.println(addressBook2);

        // 序列化到文件
        addressBook1.writeTo(new FileOutputStream("AddressBook1.txt"));
        // 读取文件反序列化
        AddressBook addressBook3 = AddressBook.parseFrom(new FileInputStream("AddressBook1.txt"));
        System.out.println("文件读取反序列化:");
        System.out.println(addressBook3);
    }
}

输出:

字节数组反序列化:
people {
  id: 1
  name: "www.wdbyte.com"
  email: "xxx@wdbyte.com"
  phones {
    number: "18388888888"
    type: HOME
  }
}

文件读取反序列化:
people {
  id: 1
  name: "www.wdbyte.com"
  email: "xxx@wdbyte.com"
  phones {
    number: "18388888888"
    type: HOME
  }
}

Protobuf 为什么高效

在分析 Protobuf 高效之前,我们先确认一下 Protobuf 是否真的高效,下面将 Protobuf 与 JSON 进行对比,分别对比序列化和反序列化速度以及序列化后的存储占用大小

测试工具:JMH,FastJSON,

测试对象:Protobuf 的 addressbook.proto,JSON 的普通 Java 类。

Maven 依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.7</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.33</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.33</version>
    <scope>provided</scope>
</dependency>

先编写与addressbook.proto 结构相同的 Java 类 AddressBookJava.java.

public class AddressBookJava {
    List<PersonJava> personJavaList;

    public static class PersonJava {
        private int id;
        private String name;
        private String email;
        private PhoneNumberJava phones;
        // get...set...
    }

    public static class PhoneNumberJava {
        private String number;
        private PhoneTypeJava phoneTypeJava;
        // get....set....
    }

    public enum PhoneTypeJava {
        MOBILE, HOME, WORK;
    }

    public List<PersonJava> getPersonJavaList() {
        return personJavaList;
    }

    public void setPersonJavaList(List<PersonJava> personJavaList) {
        this.personJavaList = personJavaList;
    }
}

序列化大小对比

分别在地址簿中添加 1000 个人员信息,输出序列化后的数组大小。

package com.wdbyte.tool.protos;

import java.io.IOException;
import java.util.ArrayList;

import com.alibaba.fastjson.JSON;

import com.wdbyte.tool.protos.AddressBook.Builder;
import com.wdbyte.tool.protos.AddressBookJava.PersonJava;
import com.wdbyte.tool.protos.AddressBookJava.PhoneNumberJava;
import com.wdbyte.tool.protos.AddressBookJava.PhoneTypeJava;
import com.wdbyte.tool.protos.Person.PhoneNumber;
import com.wdbyte.tool.protos.Person.PhoneType;

/**
 * @author https://www.wdbyte.com
 */
public class ProtobufTest3 {

    public static void main(String[] args) throws IOException {
        AddressBookJava addressBookJava = createAddressBookJava(1000);
        String jsonString = JSON.toJSONString(addressBookJava);
        System.out.println("json string size:" + jsonString.length());

        AddressBook addressBook = createAddressBook(1000);
        byte[] addressBookByteArray = addressBook.toByteArray();
        System.out.println("protobuf byte array size:" + addressBookByteArray.length);
    }

    public static AddressBook createAddressBook(int personCount) {
        Builder builder = AddressBook.newBuilder();
        for (int i = 0; i < personCount; i++) {
            builder.addPeople(Person.newBuilder()
                .setId(i)
                .setName("www.wdbyte.com")
                .setEmail("xxx@126.com")
                .addPhones(PhoneNumber.newBuilder()
                    .setNumber("18333333333")
                    .setType(PhoneType.HOME)
                )
            );
        }
        return builder.build();
    }

    public static AddressBookJava createAddressBookJava(int personCount) {
        AddressBookJava addressBookJava = new AddressBookJava();
        addressBookJava.setPersonJavaList(new ArrayList<>());
        for (int i = 0; i < personCount; i++) {
            PersonJava personJava = new PersonJava();
            personJava.setId(i);
            personJava.setName("www.wdbyte.com");
            personJava.setEmail("xxx@126.com");

            PhoneNumberJava numberJava = new PhoneNumberJava();
            numberJava.setNumber("18333333333");
            numberJava.setPhoneTypeJava(PhoneTypeJava.HOME);

            personJava.setPhones(numberJava);
            addressBookJava.getPersonJavaList().add(personJava);
        }
        return addressBookJava;
    }
}

输出:

json string size:108910
protobuf byte array size:50872

可见测试中 Protobuf 的序列化结果比 JSON 小了将近一倍左右。

序列化速度对比

使用 JMH 进行性能测试,分别测试 JSON 的序列化和反序列以及 Protobuf 的序列化和反序列化性能情况。每次测试前进行 3 次预热,每次 3 秒。接着进行 5 次测试,每次 3 秒,收集测试情况。

package com.wdbyte.tool.protos;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import com.alibaba.fastjson.JSON;

import com.google.protobuf.InvalidProtocolBufferException;
import com.wdbyte.tool.protos.AddressBook.Builder;
import com.wdbyte.tool.protos.AddressBookJava.PersonJava;
import com.wdbyte.tool.protos.AddressBookJava.PhoneNumberJava;
import com.wdbyte.tool.protos.AddressBookJava.PhoneTypeJava;
import com.wdbyte.tool.protos.Person.PhoneNumber;
import com.wdbyte.tool.protos.Person.PhoneType;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

/**
 * @author https://www.wdbyte.com
 */
@State(Scope.Thread)
@Fork(2)
@Warmup(iterations = 3, time = 3)
@Measurement(iterations = 5, time = 3)
@BenchmarkMode(Mode.Throughput) // Throughput:吞吐量,SampleTime:采样时间
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class ProtobufTest4 {

    private AddressBookJava addressBookJava;
    private AddressBook addressBook;

    @Setup
    public void init() {
        addressBookJava = createAddressBookJava(1000);
        addressBook = createAddressBook(1000);
    }

    @Benchmark
    public AddressBookJava testJSON() {
        // 转 JSON
        String jsonString = JSON.toJSONString(addressBookJava);
        // JSON 转对象
        return JSON.parseObject(jsonString, AddressBookJava.class);
    }

    @Benchmark
    public AddressBook testProtobuf() throws InvalidProtocolBufferException {
        // 转 JSON
        byte[] addressBookByteArray = addressBook.toByteArray();
        // JSON 转对象
        return AddressBook.parseFrom(addressBookByteArray);
    }

    public static AddressBook createAddressBook(int personCount) {
        Builder builder = AddressBook.newBuilder();
        for (int i = 0; i < personCount; i++) {
            builder.addPeople(Person.newBuilder()
                .setId(i)
                .setName("www.wdbyte.com")
                .setEmail("xxx@126.com")
                .addPhones(PhoneNumber.newBuilder()
                    .setNumber("18333333333")
                    .setType(PhoneType.HOME)
                )
            );
        }
        return builder.build();
    }

    public static AddressBookJava createAddressBookJava(int personCount) {
        AddressBookJava addressBookJava = new AddressBookJava();
        addressBookJava.setPersonJavaList(new ArrayList<>());
        for (int i = 0; i < personCount; i++) {
            PersonJava personJava = new PersonJava();
            personJava.setId(i);
            personJava.setName("www.wdbyte.com");
            personJava.setEmail("xxx@126.com");

            PhoneNumberJava numberJava = new PhoneNumberJava();
            numberJava.setNumber("18333333333");
            numberJava.setPhoneTypeJava(PhoneTypeJava.HOME);

            personJava.setPhones(numberJava);
            addressBookJava.getPersonJavaList().add(personJava);
        }
        return addressBookJava;
    }
}

JMH 吞吐量测试结果(Score 值越大吞吐量越高,性能越好):

Benchmark                    Mode  Cnt  Score   Error   Units
ProtobufTest3.testJSON      thrpt   10  1.877 ± 0.287  ops/ms
ProtobufTest3.testProtobuf  thrpt   10  2.813 ± 0.446  ops/ms

JMH 采样时间测试结果(Score 越小,采样时间越小,性能越好):

Benchmark                                          Mode    Cnt   Score   Error  Units
ProtobufTest3.testJSON                           sample  53028   0.565 ± 0.005  ms/op
ProtobufTest3.testProtobuf                       sample  90413   0.332 ± 0.001  ms/op

从测试结果看,不管是吞吐量测试,还是采样时间测试,Protobuf 都优于 JSON。

为什么高效?

Protobuf 是如何实现这种高效紧凑的数据编码和解码的呢?

首先,Protobuf 使用二进制编码,会提高性能;其次 Protobuf 在将数据转换成二进制时,会对字段和类型重新编码,减少空间占用。它采用 TLV 格式来存储编码后的数据。TLV 也是就是 Tag-Length-Value ,是一种常见的编码方式,因为数据其实都是键值对形式,所以在 TAG 中会存储对应的字段和类型信息,Length 存储内容的长度,Value 存储具体的内容。

还记得上面定义结构体时每个字段都对应一个数字吗?如 =1,=2,=3.

message Person {
  optional int32 id = 1;
  optional string name = 2;
  optional string email = 3;
}

在序列化成二进制时候就是通过这个数字来标记对应的字段的,二进制中只存储这个数字,反序列化时通过这个数字找对应的字段。这也是上面为什么说尽量使用 1-15 范围内的数字,因为一旦超过 15,就需要多一个 bit 位来存储。

那么类型信息呢?比如 int32 怎么标记,因为类型个数有限,所以 Protobuf 规定了每个类型对应的二进制编码,比如 int32 对应二进制 000string 对应二进制 010,这样就可以只用三个比特位存储类型信息。

这里只是举例描述大概思想,具体还有一些变化。

详情可以参考官方文档:https://protobuf.dev/programming-guides/encoding/

其次,Protobuf 还会采用一种变长编码的方式来存储数据。这种编码方式能够保证数据占用的空间最小化,从而减少了数据传输和存储的开销。具体来说,Protobuf 会将整数和浮点数等类型变换成一个或多个字节的形式,其中每个字节都包含了一部分数据信息和一部分标识符信息。这种编码方式可以在数据值比较小的情况下,只使用一个字节来存储数据,以此来提高编码效率。

最后,Protobuf 还可以通过采用压缩算法来减少数据传输的大小。比如 GZIP 算法能够将原始数据压缩成更小的二进制格式,从而在网络传输中能够节省带宽和传输时间。Protobuf 还提供了一些可选的压缩算法,如 zlib 和 snappy,这些算法在不同的场景下能够适应不同的压缩需求。

综上所述,Protobuf 在实现高效编码和解码的过程中,采用了多种优化方式,从而在实际应用中能够有效地提升数据传输和处理的效率。

总结

ProtoBuf 是一种轻量、高效的数据交换格式,它具有以下优点:

  • 语言中立,可以支持多种编程语言;
  • 数据结构清晰,易于维护和扩展;
  • 二进制编码,数据体积小,传输效率高
  • 自动生成代码,开发效率高。

但是,ProtoBuf 也存在以下缺点:

  • 学习成本较高,需要掌握其语法规则和使用方法;
  • 需要先定义数据结构,然后才能对数据进行序列化和反序列化,增加了一定的开发成本;
  • 由于二进制编码,可读性较差,这点不如 JSON 可以直接阅读

总体来说,Protobuf 适合用于数据传输和存储等场景,能够提高数据传输效率和减少数据体积。但对于需要人类可读的数据,或需要实时修改的数据,或者对数据的传输效率和体积没那么在意的场景,选择更加通用的 JSON 未尝不是一个好的选择。

参考:https://protobuf.dev/overview/

一如既往,文章代码都存放在 Github.com/niumoo/javaNotes.

文章持续更新,可以微信搜一搜「 程序猿阿朗 」或访问「程序猿阿朗博客 」第一时间阅读。本文 Github.com/niumoo/JavaNotes 已经收录,有很多系列文章,欢迎Star。

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

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

相关文章

《理解了实现再谈网络性能》读书笔记

文章目录 内核是如何接收网络包的1.1 Linux⽹络收包总览1.2 linux 启动创建ksoftirqd进程网络子系统初始化协议栈注册网卡驱动初始化启动网卡 1.3 迎接数据的到来硬中断处理ksoftirqd 内核线程处理软中断网络协议栈处理IP协议层处理 完整流程 内核是如何接收网络包的 1.1 Linu…

使用阿里云服务器建站WordPress博客网站上线全流程

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网以搭建WordPress网站博客为例&#xff0c;来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程&#xff1a; …

常见概率分布及图像

概率分布 文章目录 概率分布[toc]1 离散概率分布1.1 伯努利分布1.2 二项分布1.3 泊松分布 2 连续概率分布2.1 均匀分布2.2 指数分布2.3 正态分布2.4 卡方分布2.5 Student分布3.5 F分布 1 离散概率分布 1.1 伯努利分布 随机变量 X X X仅取两个值&#xff0c; X 0 , 1 X0,1 X0…

【2023 年第三届长三角高校数学建模竞赛】C 题 考研难度知多少 考研情况相关数据下载

【2023 年第三届长三角高校数学建模竞赛】C 题 考研难度知多少 1 题目 C 题 考研难度知多少 据相关媒体报道&#xff0c;2023 年考研可以称得上是“最难”的一年&#xff0c;全国研究生报考人数突破新高达到 474 万人、部分考研学生感染新冠带病赴考、保研名额增多 挤压考研…

依次判断数组a1中的每个元素是否小于数组a2中对应位置的每个元素numpy.less()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 依次判断数组a1中的每个元素 是否小于数组a2中对应位置的每个元素 numpy.less() 选择题 以下程序的运行结果是? import numpy as np a1np.array([1,2,3]) a2np.array([3,2,1]) print("【…

消息推送平台的实时数仓?flink消费kafka消息入到hive

上一次比较大的更新是做了austin的预览地址&#xff0c;把企业微信的应用和机器人消息各种的消息类型和功能给完善了。上一篇文章也提到了&#xff0c;austin常规的功能已经更新得差不多了&#xff0c;剩下的就是各种细节的完善。 不知道大家还记不记得我当时规划austin时&…

免费使用的在线Axure原型工具找到了!

在线 Axure 工具替代即时设计&#xff0c;不需要安装插件&#xff0c;直接在浏览器中打开 Axure 原型文件&#xff0c;操作更加便捷。除了支持 Axure&#xff0c;即时设计还兼容 Figma、Sketch、AdobeXD 等更多文件类型&#xff0c;可用于线框设计、UI/UX 设计、原型交互、免切…

linux内核:笔记3-网络数据包收发流程

1.网络分层中&#xff0c;内核协议栈负责实现网络层和传输层 2.内核对用户进程提供socket接口用以调用 3.Linux内核包含链路层驱动用来驱动网络设备 4.内核网络层用来实现IP、ICMP以及IGMP 5.内核层传输层用来实现TCP和udp协议 6.内核源码中网络设备驱动对应的代码位于dri…

KDZD-100A触摸屏回路电阻测试仪

一、简介 测试仪是根据电力执行标准DL/T845.4-2004&#xff0c;采用高频开关电源技术和数字电路技术相结合设计而成。它适用于开关控制设备回路电阻的测量。其测试电流采用国家标准推荐的直流100A。可在电流100A的情况下直接测得回路电阻&#xff0c;并用数字显示出来。测量准确…

劝你们不要去外包,两年外包生涯做完,感觉自己废了一半....

先说一下自己的情况。大专生&#xff0c;17年通过校招进入湖南某软件公司&#xff0c;干了接近5年的点点点&#xff0c;今年年上旬&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了五年的功能测试…

​TPAMI 2023 | 无监督域适应语义分割新视角:统一图像和特征层次的对齐

©PaperWeekly 原创 作者 | 陈超奇 单位 | 香港大学 研究方向 | 迁移学习 论文标题&#xff1a; I2F: A Unified Image-to-Feature Approach for Domain Adaptive Semantic Segmentation 论文链接&#xff1a; https://arxiv.org/abs/2301.01149&#xff08;预印版&#x…

了解微服务技术栈

在了解微服务技术栈之前&#xff0c;我们先通过图片去了解微服务技术的类别。 微服务技术体系 技术栈模块图 微服务技术对比 服务框架 用JAVA技术开发微服务&#xff0c;比较主流的选择有&#xff1a;Spring Cloud 和 Dubbo。 Spring Cloud是在Spring基础上构建的&#xff0c;…

机器学习之朴素贝叶斯二、情感分析实践

目录标题 一、什么是朴素贝叶斯&#xff1f;二、利用朴素贝叶斯进行情感分析1. 数据类别说明2. 什么是词袋模型3. 数据展示4. 利用词袋模型进行词表构建5. 到了这一步&#xff0c;我们的前期工作都已经准备好了&#xff0c;有了样本的向量化数据&#xff0c;开始进行 朴素贝叶斯…

2023年北京.NET线下技术沙龙来了!大咖分享,还有精品好礼等你

MASA技术团队来北京啦&#xff01; 为了与北京的.NET开发者们更深入的交流学习&#xff0c;我们将在北京市举办一场.NET线下技术沙龙。同时也是希望通过举办这样的线下沙龙&#xff0c;让更多的.NET开发者了解我们&#xff0c;加入.NET开源技术生态&#xff0c;向更多的.NET开…

SpringCloud-服务间通信OpenFeignRibbon

服务间通信&OpenFeign&Ribbon 一、服务间通信1.创建两个服务并注册到服务中心1.1.服务添加相关依赖1.2.编写配置文件application.yml1.3.入口类加上注解启动Eureka Client 2.Product服务中提供调用方法3.User服务中使用RestTemplate调用 二、负载均衡及Ribbon组件1.负载…

《三》包管理工具

包管理工具 npm&#xff1a; npm&#xff1a;Node Package Manager&#xff0c;Node 包管理器&#xff0c;目前已经不仅仅作为 Node 的包管理工具&#xff0c;也作为前端的包管理工具来管理包。 npm 管理的包是存放在一个名为 registry 的仓库中的&#xff0c;发布一个包时是…

AI歌手——超简单一键运用ACE Studio来训练模拟真人唱歌

目录 1.安装ACE Studio 2.打开ACE Studio 3.导入midi或音频 4.调试音频 5.调整人物音色音高 6.调整歌词 7.自动添加呼吸​编辑 8.成品演示&#xff08;周杰伦の稻香——鲤阮&#xff09; 1.安装ACE Studio 安装地址 ACE Studio | Create Limitless Vocals with AI (t…

如何用AI画56个民族的女孩!

大家好&#xff0c;我是菜鸟哥&#xff01; 目前在带领600多个小伙伴一起玩AI&#xff01;Chatgpt现在是最火最出圈的产品&#xff0c;而一旦文字和图像&#xff0c;声音&#xff0c;视频结合可以演化出很多很多的需求。前面我们公众号已经分享了很多很多关于chatgpt的使用和技…

大势智慧软硬件技术答疑第二期

1. 编辑模型视图为什么是空的呢&#xff1f;工程这里也选了obj数据位置。 答&#xff1a;CtrlT选择下瓦块就可以&#xff0c;或者点这个图标。 2. 支持编辑DSM 生成正射吗&#xff1f; 答&#xff1a;重建大师&#xff0c;模方&#xff0c;和dasview均支持直接生产正射和DSM&a…

idea-properties文件编码为iso-8859-1修改为utf-8

一、现状 idea中properties文件编码为iso-8859-1并且为灰色不可修改 二、修改成utf-8 command,打开preferences 如下图操作即可