Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?_trans buffer的作用-程序员宅基地

技术标签: 计算机网络  Protocol Buffer  

前言

  • 习惯用 Json、XML 数据存储格式的你们,相信大多都没听过Protocol Buffer
  • Protocol Buffer 其实 是 Google出品的一种轻量 & 高效的结构化数据存储格式,性能比 Json、XML 真的强!太!多!

    由于 Google出品,我相信Protocol Buffer已经具备足够的吸引力

  • 今天,我将讲解为什么Protocol Buffer的性能如此的好: 
    a. 序列化速度 & 反序列化速度快 
    b. 数据压缩效果好,即序列化后的数据量体积小

阅读本文前请先阅读: 
1. 快来看看Google出品的Protocol Buffer,别只会用Json和XML了 
2. 手把手教你如何安装Protocol Buffer 
3. 这是一份很有诚意的 Protocol Buffer 语法详解


目录

目录


1. 定义

一种 结构化数据 的数据存储格式(类似于 XML、Json )

  1. Google 出品 (开源)
  2. Protocol Buffer 目前有两个版本:proto2 和 proto3
  3. 因为proto3 还是beta 版,所以本次讲解是 proto2

2. 作用

通过将 结构化的数据 进行 串行化(序列化),从而实现 数据存储 / RPC 数据交换的功能

  1. 序列化: 将 数据结构或对象 转换成 二进制串 的过程
  2. 反序列化:将在序列化过程中所生成的二进制串 转换成 数据结构或者对象 的过程

3. 特点

  • 对比于 常见的 XML、Json 数据存储格式,Protocol Buffer有如下特点:

Protocol Buffer 特点


4. 应用场景

传输数据量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景

如 即时IM (QQ、微信)的需求场景


总结

在 传输数据量较大的需求场景下,Protocol BufferXML、Json 更小、更快、使用 & 维护更简单!


5. 使用流程

关于 Protocol Buffer 的使用流程,具体请看我写的文章:快来看看Google出品的Protocol Buffer,别只会用Json和XML了


6. 知识基础

6.1 网络通信协议
  • 序列化 & 反序列化 属于通讯协议的一部分
  • 通讯协议采用分层模型:TCP/IP模型(四层) & OSI 模型 (七层)

通信协议结构

  • 序列化 / 反序列化 属于 TCP/IP模型 应用层 和 OSI`模型 展示层的主要功能:

    1. (序列化)把 应用层的对象 转换成 二进制串
    2. (反序列化)把 二进制串 转换成 应用层的对象
  • 所以, Protocol Buffer属于 TCP/IP模型的应用层 & OSI模型的展示层

6.2 数据结构、对象与二进制串

不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。

a. 对于数据结构和对象
  • 对于面向对象的语言(如Java):对象 = Object = 类的实例化;在Java中最接近数据结构 即 POJOPlain Old Java Object),或Javabean(只有 setter/getter 方法的类)

  • 对于半面向对象的语言(如C++),对象 = class,数据结构 = struct

b. 二进制串
  • 对于C++,因为具有内存操作符,所以 二进制串 容易理解:C++的字符串可以直接被传输层使用,因为其本质上就是以 '\0' 结尾的存储在内存中的二进制串
  • 对于 Java,二进制串 = 字节数组 =byte[] 
    1. byte 属于 Java 的八种基本数据类型 
    2. 二进制串 容易和 String混淆:String 一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。

6.3 T - L - V 的数据存储方式
  • 定义 
    即 Tag - Length - Value标识 - 长度 - 字段值 存储方式

  • 作用 
    以 标识 - 长度 - 字段值 表示单个数据,最终将所有数据拼接成一个 字节流,从而 实现 数据存储 的功能

    其中 Length可选存储,如 储存Varint编码数据就不需要存储Length

  • 示意图

最终存储的字节流

  • 优点 
    从上图可知,T - L - V 存储方式的优点是 
    1. 不需要分隔符 就能 分隔开字段,减少了 分隔符 的使用
    2. 各字段 存储得非常紧凑,存储空间利用率非常高
    3. 若 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要编码 

      相应字段在解码时才会被设置为默认值


7. 序列化原理解析

请记住Protocol Buffer的 三个关于数据存储 的重要结论:

  • 结论1: Protocol Buffer将 消息里的每个字段 进行编码后,再利用T - L - V 存储方式 进行数据的存储,最终得到的是一个 二进制字节流

    序列化 = 对数据进行编码 + 存储

  • 结论2:Protocol Buffer对于不同数据类型 采用不同的 序列化方式(编码方式 & 数据存储方式),如下图: 
    数据类型 对应的编码方式

从上表可以看出: 
1. 对于存储Varint编码数据,就不需要存储字节长度 Length,所以实际上Protocol Buffer的存储方式是 T - V; 
2. 若Protocol Buffer采用其他编码方式(如LENGTH_DELIMITED)则采用T - L - V

  • 结论3:因为 Protocol Buffer对于数据字段值的 独特编码方式 & T - L - V数据存储方式,使得 Protocol Buffer序列化后数据量体积如此小

下面,我将对不同的编码方式 & 数据存储方式进行逐一讲解。


7.1 Wire Type = 0时的编码 & 数据存储方式

Wire Type = 0时的编码 & 数据存储方式

7.1.1 编码方式: Varint & Zigzag
A. Varint编码方式介绍
i. 简介
  • 定义:一种变长的编码方式
  • 原理:用字节 表示 数字:值越小的数字,使用越少的字节数表示
  • 作用:通过减少 表示数字 的字节数 从而进行数据压缩 
    如: 
    - 对于  int32 类型的数字,一般需要 4个字节 表示; 
    2. 若采用  Varint编码,对于很小的  int32 类型 数字,则可以用 1个字节 来表示 
    3. 虽然大的数字会需要 5 个 字节 来表示,但大多数情况下,消息都不会有很大的数字,所以采用  Varint方法总是可以用更少的字节数来表示数字
ii. 原理介绍
  • 源码分析
private void writeVarint32(int n) {                                                                                    
  int idx = 0;  
  while (true) {  
    if ((n & ~0x7F) == 0) {  
      i32buf[idx++] = (byte)n;  
      break;  
    } else {  
      i32buf[idx++] = (byte)((n & 0x7F) | 0x80);  
      // 步骤1:取出字节串末7位
      // 对于上述取出的7位:在最高位添加1构成一个字节
      // 如果是最后一次取出,则在最高位添加0构成1个字节

      n >>>= 7;  
      // 步骤2:通过将字节串整体往右移7位,继续从字节串的末尾选取7位,直到取完为止。
    }  
  }  
  trans_.write(i32buf, 0, idx); 
      // 步骤3: 将上述形成的每个字节 按序拼接 成一个字节串
      // 即该字节串就是经过Varint编码后的字节
}   

 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

从上面可看出:Varint 中每个 字节 的最高位 都有特殊含义:

  • 如果是 1,表示后续的 字节 也是该数字的一部分
  • 如果是 0,表示这是最后一个字节,且剩余 7位 都用来表示数字 

    所以,当使用Varint解码时时,只要读取到最高位为0的字节时,就表示已经是Varint的最后一个字节


因此:
  • 小于 128 的数字 都可以用 1个字节 表示;
  • 大于 128 的数字,比如 300,会用两个字节来表示:10101100 00000010

下面,我将用两个个例子来说明Varint编码方式的使用

  • 目的:对 数据类型为Int32 的 字段值为296 和字段值为104 进行Varint编码
  • 以下是编码过程

Varint编码过程

从上面可以看出:

  • 对于 int32 类型的数字,一般需要 4 个字节 来表示;
  • 但采用 Varint 方法,对于很小的 Int32 类型 数字(小于256),则可以用 1个字节 来表示;

    以此类推,比如300也只需要2个字节

  • 虽然大的数字会需要 5 个字节 来表示,但大多数情况下,消息都不会有很大的数字

  • 所以采用 Varint 方法总是可以用更少的字节数来表示数字,从而更好地实现数据压缩

下面继续看如何解析经过Varint 编码的字节

Varint 编码方式的不足

  • 背景:在计算机内,负数一般会被表示为很大的整数 

    因为计算机定义负数的符号位为数字的最高位

  • 问题:如果采用 Varint编码方式 表示一个负数,那么一定需要 5 个 byte(因为负数的最高位是1,会被当做很大的整数去处理)
  • 解决方案: Protocol Buffer 定义了 sint32 / sint64 类型表示负数,通过先采用 Zigzag 编码(将 有符号数 转换成 无符号数),再采用 Varint编码,从而用于减少编码后的字节数 
    表示负数时采用Zigzag编码

1. 对于 int32 / int64  类型的字段值(正数), Protocol Buffer 直接采用  Varint 编码 
2. 对于 sint32 / sint64  类型的字段值(负数), Protocol Buffer 会先采用  Zigzag  编码,再采用  Varint 编码
  • 总结:为了更好地减少 表示负数时 的字节数,Protocol Buffer在 Varint编码上又增加了Zigzag 编码方式进行编码
  • 下面将详细介绍 Zigzag编码方式
B. Zigzag编码方式详解
i. 简介
  • 定义:一种变长的编码方式
  • 原理:使用 无符号数 来表示 有符号数字;
  • 作用:使得绝对值小的数字都可以采用较少 字节 来表示; 
    特别是对 表示负数的数据 能更好地进行数据压缩
b. 原理
  • 源码分析

public int int_to_zigzag(int n)
// 传入的参数n = 传入字段值的二进制表示(此处以负数为例)
// 负数的二进制 = 符号位为1,剩余的位数为 该数绝对值的原码按位取反;然后整个二进制数+1
{
        return (n <<1) ^ (n >>31);   
        // 对于sint 32 数据类型,使用Zigzag编码过程如下:
        // 1. 将二进制表示数 左移1位(左移 = 整个二进制左移,低位补0)
        // 2. 将二进制表示数 右移31位 
              // 对于右移:
              // 首位是1的二进制(有符号数),是算数右移,即右移后左边补1
              // 首位是0的二进制(无符号数),是逻辑左移,即右移后左边补0
        // 3. 将上述二者进行异或

        // 对于sint 64 数据类型 则为: return  (n << 1> ^ (n >> 63) ;
}


// 附:将Zigzag值解码为整型值
public int zigzag_to_int(int n) 
{
        return(n >>> 1) ^ -(n & 1);
// 右移时,需要用不带符号的移动,否则如果第一位数据位是1的话,就会补1
}


 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 实例说明:将 -2进行 Zigzag编码:

Zigzag编码

  • Zigzag 编码 是补充 Varint编码在 表示负数 的不足,从而更好的帮助 Protocol Buffer进行数据的压缩
  • 所以,如果提前预知字段值是可能取负数的时候,记得采用sint32 / sint64 数据类型
总结

Protocol Buffer 通过VarintZigzag编码后大大减少了字段值占用字节数。

7.1.2 存储方式:T - V
  • 消息字段的标识号、数据类型 & 字段值经过 Protocol Buffer采用 Varint & Zigzag 编码后,以 T - V 方式进行数据存储 

    对于 Varint & Zigzag 编码,省略了T - L - V中的字节长度Length


Varint & Zigzag数据存储方式  下面将详细介绍`T - V` 存储方式中的存储细节:`Tag` & `Value`
1. Tag
  • 定义:经过 Protocol Buffer采用Varint & Zigzag编码后 的消息字段 标识号 & 数据类型 的值
  • 作用:标识 字段

    1. 存储了字段的标识号(field_number)和 数据类型(wire_type),即Tag = 字段数据类型(wire_type) + 标识号(field_number
    2. 占用 一个字节 的长度(如果标识号超过了16,则占用多一个字节的位置)
    3. 解包时,Protocol Buffer根据 Tag 将 Value 对应于消息中的 字段
  • 具体使用

// Tag 的具体表达式如下
 Tag  = (field_number << 3) | wire_type
// 参数说明:
// field_number:对应于 .proto文件中消息字段的标识号,表示这是消息里的第几个字段
// field_number << 3:表示 field_number = 将 Tag的二进制表示 右移三位 后的值 
// field_num左移3位不会导致数据丢失,因为表示范围还是足够大地去表示消息里的字段数目

//  wire_type:表示 字段 的数据类型
//  wire_type = Tag的二进制表示 的最低三位值
//   wire_type的取值
 enum WireType { 
      WIRETYPE_VARINT = 0, 
      WIRETYPE_FIXED64 = 1, 
      WIRETYPE_LENGTH_DELIMITED = 2, 
      WIRETYPE_START_GROUP = 3, 
      WIRETYPE_END_GROUP = 4, 
      WIRETYPE_FIXED32 = 5
   };

// 从上面可以看出,`wire_type`最多占用 3位 的内存空间(因为 3位 足以表示 0-5 的二进制)

//  以下是 wire_type 对应的 数据类型 表
 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

wire_type对应数据类型

  • 实例说明
// 消息对象
 message person
 { 
    required int32     id = 1;  
    // wire type = 0,field_number =1 
    required string    name = 2;  
    // wire type = 2,field_number =2 
  }

//  如果一个Tag的二进制 = 0001 0010
标识号 = field_number = field_number  << 3 =右移3位 =  0000 0010 = 2
数据类型 = wire_type = 最低三位表示 = 010 = 2
 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2. Value

经过 Protocol Buffer采用Varint & Zigzag编码后 的消息字段的值

7.1.3 实例说明

下面通过一个实例进行整个编码过程的说明:

  • 消息说明
message Test
{

required int32 id1 = 1;

required int32 id2 = 2;
}

// 在代码中给id1 附上1个字段值:296
// 在代码中给id2 附上1个字段值:296
Test.setId1(300);
Test.setId2(296);

// 编码结果为:二进制字节流 = [8,-84,2,16, -88, 2]
 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 整个编码过程如下

编码过程


7.2 Wire Type = 1& 5时的编码&数据存储方式

Wire Type = 1& 5时的编码&数据存储方式

  • 64(32)-bit编码方式较简单:编码后的数据具备固定大小 = 64位(8字节) / 32位(4字节) 
    两种情况下,都是高位在后,低位在前
  • 采用T - V方式进行数据存储,同上。

7.3 Wire Type = 2时的 编码 & 数据存储方式

Wire Type = 2时的编码&数据存储方式

  • 对于编码方式:

编码方式

  • 数据存储方式: T - L - V

数据存储示意图

此处主要讲解三种数据类型:

  • String类型
  • 嵌套消息类型(Message
  • 通过packed修饰的 repeat 字段(即packed repeated fields

1. String类型

字段值(即V) 采用UTF-8编码

编码 & 存储方式

  • 例子:
message Test2
{
    required string str = 2;
}

// 将str设置为:testing
Test2.setStr(“testing”)

// 经过protobuf编码序列化后的数据以二进制的方式输出
// 输出为:18, 7, 116, 101, 115, 116, 105, 110, 103

 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

实例

2. 嵌套消息类型(Message)

  • 存储方式:T - L - V 
    1. 外部消息的 V即为 嵌套消息的字段 
    2. 在 T - L -V 里嵌套了一系列的 T - L -V
    3. 编码方式:字段值(即V) 根据字段的数据类型采用不同编码方式

编码 & 存储方式
  • 实例 
    定义如下嵌套消息:
message Test2
{
    required string str = 1;
    required int32 id1 = 2;


}

message Test3 {
  required Test2 c = 1;
}

// 将Test2中的字段str设置为:testing
// 将Test2中的字段id1设置为:296
// 编码后的字节为:10 ,12 ,18,7,116, 101, 115, 116, 105, 110, 103,16,-88,2
 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

编码 & 存储方式如下

编码 & 存储方式

3. 通过packed修饰的 repeat 字段

repeated 修饰的字段有两种表达方式:

message Test
{
    repeated int32 Car = 4 ;
    // 表达方式1:不带packed=true

    repeated int32 Car = 4 [packed=true];
    // 表达方式2:带packed=true
    // proto 2.1 开始可使用

// 区别在于:是否连续存储repeated类型数据
}


// 在代码中给`repeated int32 Car`附上3个字段值:3、270、86942

Test.setCar(3);
Test.setCar(270);
Test.setCar(86942);
 
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 背景:对于同一个 repeated字段、多个字段值来说,他们的Tag都是相同的,即数据类型 & 标识号都相同

    repeated类型可以看成是数组

  • 问题:若以传统的多个 T - V对存储(不带packed=true),则会导致Tag的冗余,即相同的Tag存储多次;

不带pack的存储方式

  • 解决方案:采用带packed=true 的 repeated 字段存储方式,即将相同的 Tag 只存储一次、添加 repeated 字段下所有字段值的长度Length、连续存储 repeated 字段值,组成一个大的Tag - Length - Value -Value -Value对,即T - L - V - V - V对。

带pack的存储方式

通过采用带packed=true 的 repeated 字段存储方式,从而更好地压缩序列化后的数据长度。

特别注意
  • Protocol Buffer 的 packed修饰只用于repeated字段 或 基本类型的repeated字段
  • 用在其他字段,编译 .proto 文件时会报错

8. 特别注意

  • 注意1:若 required字段没有被设置字段值,那么在IsInitialized()进行初始化检查会报错并提示失败 

    所以 required字段必须要被设置字段值

  • 注意2:序列化顺序 是根据 Tag标识号 从小到大 进行编码

    和 .proto文件内 字段定义的数据无关

  • 注意3:T - V的数据存储方式保证了Protobuf的版本兼容:高<->低 或 低<->高都可以适配

    若新版本 增加了 required 字段, 旧版本 在数据解码时会认为IsInitialized() 失败,所以慎用 required字段


9. 使用建议

根据上面的序列化原理分析,我总结出以下Protocol Buffer的使用建议

通过下面建议能有效降低序列化后数据量的大小

  • 建议1:多用 optional或 repeated修饰符 
    因为若optional 或 repeated 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要进行编码

    相应的字段在解码时才会被设置为默认值

  • 建议2:字段标识号(Field_Number)尽量只使用 1-15,且不要跳动使用 
    因为Tag里的Field_Number是需要占字节空间的。如果Field_Number>16时,Field_Number的编码就会占用2个字节,那么Tag在编码时也就会占用更多的字节;如果将字段标识号定义为连续递增的数值,将获得更好的编码和解码性能

  • 建议3:若需要使用的字段值出现负数,请使用 sint32 / sint64,不要使用int32 / int64 
    因为采用sint32 / sint64数据类型表示负数时,会先采用Zigzag编码再采用Varint编码,从而更加有效压缩数据

  • 建议4:对于repeated字段,尽量增加packed=true修饰 
    因为加了packed=true修饰repeated字段采用连续数据存储方式,即T - L - V - V -V方式


10. 序列化 & 反序列化过程

  • Protocol Buffer除了序列化 & 反序列化后的数据体积小,序列化 & 反序列化的速度也非常快
  • 下面我将讲解序列化 & 反序列化的序列化过程

10.1 Protocol Buffer的序列化 & 反序列化过程

序列化过程如下: 
1. 判断每个字段是否有设置值,有值才进行编码 
2. 根据 字段标识号&数据类型 将 字段值 通过不同的编码方式进行编码

由于: 
a. 编码方式简单(只需要简单的数学运算 = 位移等等) 
b. 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成

所以Protocol Buffer的序列化速度非常快。

反序列化过程如下: 
1. 调用 消息类的 parseFrom(input) 解析从输入流读入的二进制字节数据流

从上面可知,Protocol Buffer解析过程只需要通过简单的解码方式即可完成,无需复杂的词法语法分析,因此 解析速度非常快。 
2. 将解析出来的数据 按照指定的格式读取到 JavaC++Phyton 对应的结构类型中

由于: 
a. 解码方式简单(只需要简单的数学运算 = 位移等等) 
b. 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成

所以Protocol Buffer的反序列化速度非常快。

10.2 对比于XML 的序列化 & 反序列化过程

XML的反序列化过程如下: 
1. 从文件中读取出字符串 
2. 将字符串转换为 XML 文档对象结构模型 
3. 从 XML 文档对象结构模型中读取指定节点的字符串 
4. 将该字符串转换成指定类型的变量

上述过程非常复杂,其中,将 XML 文件转换为文档对象结构模型的过程通常需要完成词法文法分析等大量消耗 CPU 的复杂计算。

因为序列化 & 反序列化过程简单,所以序列化 & 反序列化过程速度非常快,这也是 Protocol Buffer效率高的原因


11.总结

  • Protocol Buffer的序列化 & 反序列化简单 & 速度快的原因是: 
    a. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等) 
    b. 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成

  • Protocol Buffer的数据压缩效果好(即序列化后的数据量体积小)的原因是: 
    a. 采用了独特的编码方式,如VarintZigzag编码方式等等 
    b. 采用T - L - V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑


接下来我会讲解Protocol Buffer的源码分析,有兴趣可以继续关注Carson_Ho的安卓开发笔记


请帮顶或评论点赞!因为你的鼓励是我写作的最大动力!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zxh2075/article/details/78275432

智能推荐

深入了解C语言(函数的参数传递和函数使用参数的方法) _c语言中prog03_06了解函数-程序员宅基地

文章浏览阅读1k次。 深入了解C语言(函数的参数传递和函数使用参数的方法) 深入了解C语言(函数的参数传递和函数使用参数的方法)tangl_99(原作) C语言生成的代码在执行效率上比其它高级语言都高.现在让我们来看看C语言生成的代码具体是什么样子的.当你看完本文对于C语言的了解一定会更深一步了. 本文通过一个个实际案例程序来讲解C语言. 研究案例一 工具: Turboc C v2.0,Debug_c语言中prog03_06了解函数

Android App开发-简单控件(4)——按钮触控和图像显示_通过按钮的点击事件控制图片的现实和隐藏-程序员宅基地

文章浏览阅读1.1k次,点赞18次,收藏14次。本节介绍了按钮控件的常见用法,包括:如何设置大小写属性与点击属性,如何响应按钮的点击事件和长按事件,如何禁用按钮又该如何启用按钮,等等。_通过按钮的点击事件控制图片的现实和隐藏

数据类型分类 转换、复杂类型(复合、引用、对象类型)、ypeof的返回值、运算符02_"ypeof callback == \"function\"如何写"-程序员宅基地

文章浏览阅读73次。day02 数据类型1.回顾什么叫js?基于对象和事件驱动的解释性脚本语言js的组成ECMAScript:JavaScript的标准DOM:Document Object Model 文档对象模型BOM:Browser Object Model 浏览器对象模型JavaScript和ECMAScript的关系?前者是后者的具体实现后者是前者的标准引入方式变量存储数据的容器声明变量:var 变量名var a; //undefined命名规则:1_"ypeof callback == \"function\"如何写"

FX3/CX3 JLINK 调试_ezusbsuite_qsg.pdf-程序员宅基地

文章浏览阅读2.1k次。FX3 JLINK调试是一个有些麻烦的事情,经常有些莫名其妙的问题。 设置参见 c:\Program Files (x86)\Cypress\EZ-USB FX3 SDK\1.3\doc\firmware 下的 EzUsbSuite_UG.pdf 文档。 常见问题: 1.装了多个版本的jlink,使用了未注册或不适当的版本 选择一个正确的版本。JLinkARM_V408l,JLinkA_ezusbsuite_qsg.pdf

用openGL+QT简单实现二进制stl文件读取显示并通过鼠标旋转缩放_qopengl如何鼠标控制旋转-程序员宅基地

文章浏览阅读2.6k次。** 本文仅通过用openGL+QT简单实现二进制stl文件读取显示并通过鼠标旋转缩放, 是比较入门的级别,由于个人能力有限,新手级别,所以未能施加光影灯光等操作, 未能让显示的stl文件更加真实。****效果图:**1. main.cpp```cpp#include "widget.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); _qopengl如何鼠标控制旋转

刘焕勇&王昊奋|ChatGPT对知识图谱的影响讨论实录-程序员宅基地

文章浏览阅读943次,点赞22次,收藏19次。以大规模预训练语言模型为基础的chatgpt成功出圈,在近几日已经给人工智能板块带来了多次涨停,这足够说明这一风口的到来。而作为曾经的风口“知识图谱”而言,如何找到其与chatgpt之间的区别,找好自身的定位显得尤为重要。形式化知识和参数化知识在表现形式上一直都是大家考虑的问题,两种技术都应该有自己的定位与价值所在。知识图谱构建往往是抽取式的,而且往往包含一系列知识冲突检测、消解过程,整个过程都能溯源。以这样的知识作为输入,能在相当程度上解决当前ChatGPT的事实谬误问题,并具有可解释性。

随便推点

Oracle LiveLabs实验:Introduction to Oracle Spatial Studio_oracle_spatial 可视化-程序员宅基地

文章浏览阅读601次。本实验介绍了适用于 Oracle Spatial Studio。他既可以在云上,也可以在本地作为Java应用部署。介绍详见这里。此实验申请地址在这里,时间为120分钟。此实验的帮助见这里。本实验使用的地图为OpenStreetMap,即免费的维基世界地图。此实验会自动创建一个ADW,需要通过OCI Console完成初始化配置,然后可以通过网页访问Spatial Studio简介在本次研讨会中,您将探索 Spatial Studio 用于自助式空间分析和可视化的功能。 使用交通事故、警察局和警察_oracle_spatial 可视化

UG\NX二次开发 改变Block UI界面的尺寸_ug二次开发 调整 对话框大小-程序员宅基地

文章浏览阅读1.3k次。改变Block UI界面的尺寸_ug二次开发 调整 对话框大小

基于深度学习的股票预测(完整版,有代码)_基于深度学习的股票操纵识别研究python代码-程序员宅基地

文章浏览阅读1.3w次,点赞18次,收藏291次。基于深度学习的股票预测数据获取数据转换LSTM模型搭建训练模型预测结果数据获取采用tushare的数据接口(不知道tushare的筒子们自行百度一下,简而言之其免费提供各类金融数据 , 助力智能投资与创新型投资。)python可以直接使用pip安装tushare!pip install tushareCollecting tushare Downloading https://files.pythonhosted.org/packages/17/76/dc6784a1c07ec040e74_基于深度学习的股票操纵识别研究python代码

中科网威工业级防火墙通过电力行业测评_电力行业防火墙有哪些-程序员宅基地

文章浏览阅读2k次。【IT168 厂商动态】 近日,北京中科网威(NETPOWER)工业级防火墙通过了中国电力工业电力设备及仪表质量检验测试中心(厂站自动化及远动)测试,并成为中国首家通过电力协议访问控制专业测评的工业级防火墙生产厂商。   北京中科网威(NETPOWER)工业级防火墙专为工业及恶劣环境下的网络安全需求而设计,它采用了非X86的高可靠嵌入式处理器并采用无风扇设计,整机功耗不到22W,具备极_电力行业防火墙有哪些

第十三周 ——项目二 “二叉树排序树中查找的路径”-程序员宅基地

文章浏览阅读206次。/*烟台大学计算机学院 作者:董玉祥 完成日期: 2017 12 3 问题描述:二叉树排序树中查找的路径 */#include #include #define MaxSize 100typedef int KeyType; //定义关键字类型typedef char InfoType;typedef struct node

C语言基础 -- scanf函数的返回值及其应用_c语言ignoring return value-程序员宅基地

文章浏览阅读775次。当时老师一定会告诉你,这个一个"warning"的报警,可以不用管它,也确实如此。不过,这条报警信息我们至少可以知道一点,就是scanf函数调用完之后是有一个返回值的,下面我们就要对scanf返回值进行详细的讨论。并给出在编程时利用scanf的返回值可以实现的一些功能。_c语言ignoring return value

推荐文章

热门文章

相关标签