probuffer java_Protocol Buffer的使用

Probotbuf簡介

在網絡通信和通用數據交換等應用場景中經常使用的技術是 JSON 或 XML,這兩種技術常被用于數據的結構化呈現和序列化。我們可以從兩個方面來看JSON 和 XML與protobuf的異同:一個是數據結構化,一個是數據序列化。這里的數據結構化主要面向開發或業務層面,數據序列化面向通信或存儲層面,當然數據序列化也需要“結構”和“格式”,所以這兩者之間的區別主要在于面向領域和場景不同,一般要求和側重點也會有所不同。數據結構化側重人類可讀性甚至有時會強調語義表達能力,而數據序列化側重效率和壓縮。

JSON、XML 同樣也可以直接被用來數據序列化,實際上很多時候它們也是這么被使用的,例如直接采用 JSON、XML 進行網絡通信傳輸,此時 JSON、XML 就成了一種序列化格式,它發揮了數據序列化的能力。但是經常這么被使用,不代表這么做就是合理。實際將 JSON、XML 直接作用數據序列化通常并不是最優選擇,因為它們在速度、效率、空間上并不是最優。換句話說它們更適合數據結構化而非數據序列化。

扯完 XML 和 JSON,我們來看看 ProtoBuf,同樣的 ProtoBuf 也具有數據結構化的能力,其實也就是上面介紹的 message 定義。我們能夠在 .proto 文件中,通過 message、import、內嵌 message 等語法來實現數據結構化,但是很容易能夠看出,ProtoBuf 在數據結構化方面和 XML、JSON 相差較大,人類可讀性較差,不適合上面提到的 XML、JSON 的一些應用場景。

但是如果從數據序列化的角度你會發現 ProtoBuf 有著明顯的優勢,效率、速度、空間幾乎全面占優,看完后面的 ProtoBuf 編碼的文章,你更會了解 ProtoBuf 是如何極盡所能的壓榨每一寸空間和性能,而其中的編碼原理正是 ProtoBuf 的關鍵所在,message 的表達能力并不是 ProtoBuf 最關鍵的重點。所以可以看出ProtoBuf重點側重于數據序列化而非數據結構化。

最終對這些個人思考做一些小小的總結:

XML、JSON、ProtoBuf 都具有數據結構化和數據序列化的能力

XML、JSON 更注重數據結構化,關注人類可讀性和語義表達能力。ProtoBuf 更注重數據序列化,關注效率、空間、速度,人類可讀性差,語義表達能力不足(為保證極致的效率,會舍棄一部分元信息)

ProtoBuf 的應用場景更為明確,XML、JSON 的應用場景更為豐富。

我們先來看看官方文檔給出的定義和描述:

protocol buffers 是一種語言無關、平臺無關、可擴展的序列化結構數據的方法,它可用于(數據)通信協議、數據存儲等。

Protocol Buffers 是一種靈活,高效,自動化機制的結構數據序列化方法-可類比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡單。

你可以定義數據的結構,然后使用特殊生成的源代碼輕松的在各種數據流中使用各種語言進行編寫和讀取結構數據。你甚至可以更新數據結構,而不破壞由舊數據結構編譯的已部署程序。

簡單來講, ProtoBuf 是結構數據序列化[1] 方法,可簡單類比于 XML[2],其具有以下特點:

語言無關、平臺無關。即 ProtoBuf 支持 Java、C++、Python 等多種語言,支持多個平臺

高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡單

擴展性、兼容性好。你可以更新數據結構,而不影響和破壞原有的舊程序

protobuf3的變化

默認值

protobuf3 刪除了 protobuf2 中用來設置默認值的 default 關鍵字,取而代之的是protobuf3為各類型定義的默認值,也就是約定的默認值,如下表所示:

類型

默認值

bool

false

整形

0

string

空字符串 ""

enum

第一個枚舉元素的值,因為Protobuf3強制要求第一個枚舉元素的值必須是0,所以枚舉的默認值就是0

message

不是null,而是DEFAULT_INSTANCE

可以看出來,protobuf3定義的默認值跟Java中類的屬性的默認值規則并不一樣:Java中,如果類的屬性類型是類,則該屬性默認值是null,而protobuf3中,string、message的默認值都不是null。

枚舉類型

不支持一個proto文件中,多個枚舉中定義相同的枚舉常量名

如下的兩個枚舉,定義在同一個proto文件中:

enum Enum1 {

IDLE = 0;

RUNNING = 1;

}

enum Enum2 {

IDLE = 5;

RUNNING = 6;

}

編譯時,會報出錯誤:IDLE is already defined in "xxx",出現這一錯誤的原因就是:Protobuf3中不允許同一proto中,多個枚舉中使用相同的枚舉值。

枚舉第一個常量的值必須是0

message類型

Java中,message類型的默認值是DEFAULT_INSTANCE,其值相當于空的message,即XXX.newBuilder().build(),這樣對message類型的判空操作就應該是這樣:

// protobuf message

message User {

int32 id = 1;

string name = 2;

string email = 3;

Address address = 4;

}

message Address {

string street = 1;

string building = 2;

}

// Java

if (user.getAddress() != null && user.getAddress() != UserProto.Address.getDefaultInstance()) {

...

} else {

...

}

Protobuf數據類型

基礎類型

proto type

描述

java type

double

雙精度

double

float

單精度

float

int32

32位整數,可變長度,編碼負數效率低,編碼負數推薦使用sint32

int

int64

64位整數,可變長度,編碼負數效率低,編碼負數推薦使用sint64

long

uint32

32位無符號整數,存儲正數時與int32一致,用的較少,一般用int32,長度可變

int

uint64

64位無符號整數,存儲正數與int64一致,用的較少,一般用int64,長度可變

long

sint32

有符號32位,長度可變,編碼負數效率高

int

sint64

有符號64位,長度可變,編碼負數效率高,存儲正數時不推薦使用

long

fixed32

固定4個字節的長度,比uint32更有效率,存儲正數時不推薦使用

int

fixed64

固定8個字節的長度,比uint64更有效率

long

sfixed32

有符號整數,固定4個字節

int

sfixed64

有符號整數,固定8個字節

long

bool

布爾值

boolean

string

字符串

String

bytes

字節數組

ByteString

枚舉類型

枚舉類型中必須包含至少一個元素,并且元素的編號必須從0開始。因為如果沒有設置值的話,可以使用0作為默認值。

定義消息體

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "StudentProto3";

message Student {

string username = 1;

string password = 2;

string email = 3;

sint32 age = 4;

int64 timeSpane = 5;

double value = 6;

Address address = 7;

enum Gender {

MALE = 0;

FEMALE = 1;

}

Gender gender = 8;

}

message Address {

string province = 1;

string city = 2;

// 相當于java中的List

repeated string area = 3;

}

測試demo

StudentProto3.Student studentProto = StudentProto3.Student.newBuilder()

.setUsername("admin")

.setPassword("123456")

.setEmail("3306@qq.com")

.setValue(Double.MAX_VALUE)

// .setAge(Integer.MAX_VALUE)

.setAge(-2)

.setTimeSpane(System.currentTimeMillis())

.setAddress(address)

.setGender(StudentProto3.Student.Gender.MALE)

.build();

嵌套消息類型

Student.proto

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "StudentProto3";

message Student {

string username = 1;

string password = 2;

string email = 3;

sint32 age = 4;

int64 timeSpane = 5;

double value = 6;

Address address = 7;

}

message Address {

string province = 1;

string city = 2;

// 相當于java中的List

repeated string area = 3;

}

StudentPtoto3Demo.java

// 使用protobuf3序列化

StudentProto3.Address address = StudentProto3.Address.newBuilder()

.setProvince("北京")

.setCity("北京")

.addArea("chaoyang")

.addArea("miyun")

.build();

StudentProto3.Student studentProto = StudentProto3.Student.newBuilder()

.setUsername("admin")

.setPassword("123456")

.setEmail("3306@qq.com")

.setValue(Double.MAX_VALUE)

// .setAge(Integer.MAX_VALUE)

.setAge(-2)

.setTimeSpane(System.currentTimeMillis())

.setAddress(address)

.build();

System.out.println(studentProto);

System.out.println(studentProto.toByteArray().length);

repeated類型

repeated相當于java中的List類型,在其內部定義的類型可以是任意的。

reserved類型

當定義文件中的一些字段需要移除,最好不要直接刪除,而是使用reserved標記要刪除的字段,如果有人使用了被標記刪除的字段,編譯器會報錯。有兩種標記刪除方式:

根據字段順序標記

message Demo {

reserved 2, 5, 9 to 11 // 字段順序為2、5,以及9到11的標記為刪除

}

根據字段名稱標記

message Demo {

reserved "name", "age" // 字段名稱為name和age的被標記為刪除

}

Map類型

在ProtoBuf中可以定義Map類型,語法如下:

map map_field = N;

key_type可以是其他的Message類型,string類型,PB類型定義表中(scalar value type)除了浮點類型和byte字節類型以外的其他類型。

需要注意以下幾點:

枚舉類型不能夠作為key_type,value_type可以是除了Map以外的其他任何類型

map不能定義為repeated類型

map不保證順序

定義proto文件

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "MapProto3";

message MapPerson {

map projects = 1;

}

message Project {

string name = 1;

int32 age = 2;

}

測試demo

public class MapProtoDemo {

public static void main(String[] args) {

MapProto3.Project p1 = MapProto3.Project.newBuilder()

.setName("neo")

.setAge(22)

.build();

MapProto3.Project p2 = MapProto3.Project.newBuilder()

.setName("mary")

.setAge(33)

.build();

MapProto3.MapPerson student = MapProto3.MapPerson.newBuilder()

.putProjects("student", p1)

.putProjects("teacher", p2)

.build();

System.out.println(student);

}

}

oneof類型

oneof關鍵字內部可以定義多個field,在使用的時候只能設置一個值。

定義消息體

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "OneOfProto3";

message Test1 {

string name = 1;

int32 age = 2;

oneof test_oneof {

Request req = 3;

Response rep = 4;

}

}

message Request {

string req = 1;

}

message Response {

string rep = 1;

}

測試demo

public class OneOfProtoDemo {

public static void main(String[] args) throws InvalidProtocolBufferException, Descriptors.DescriptorValidationException {

OneOfProto3.Request request = OneOfProto3.Request.newBuilder()

.setReq("request")

.build();

OneOfProto3.Response response = OneOfProto3.Response.newBuilder()

.setRsp("response")

.build();

OneOfProto3.Test1 neo = OneOfProto3.Test1.newBuilder()

.setName("neo")

.setAge(22)

// rep和rsp只能設置其中的一個

// .setReq(request)

.setRsp(response)

.build();

System.out.println(neo);

OneOfProto3.Test1 test1 = OneOfProto3.Test1.parseFrom(neo.toByteArray());

}

}

package包定義

Package包定義,可以防止Message重名問題,類似于java中的包。在java中使用Protobuf的包,有以下兩種方式:

使用package關鍵字定義protobuf的模板消息包名,這種方式可以在多個語言中使用

syntax = "proto3";

package bar.foo;

option java_outer_classname = "OneOfProto3";

message PackageProto {

string name = 1;

int32 age = 2;

}

我們在使用的時候需要如下做:

package com.ray.protobufdemo.entity;

// 導入package處聲明的包

import bar.foo.OneOfProto3;

public class PackageProtoDemo {

public static void main(String[] args) {

OneOfProto3.PackageProto.Builder builder = OneOfProto3.PackageProto.newBuilder();

}

}

使用option java_package語句聲明java的包名,該用法是java獨有的,如果不定義則使用package中的包路徑

option java_package= "com.ray.protobufdemo";

import語法

在Protobuf中,不同的消息可以分別寫在不同的proto文件中,在使用的時候可以使用關鍵字import引用其他消息模板。

protobuf的使用

定義消息的格式

// 聲明使用proto3協議,如果不指定則默認使用proto2協議

syntax = "proto3";

option java_package = "com.ray.protobufdemo";

option java_outer_classname = "AddressBook";

message Person {

string name = 1;

int32 id = 2;

string email = 3;

enum PhoneType {

// 在proto3中,第一個枚舉值的序號必須為0

MOBILE = 0;

HOME = 1;

WORK = 2;

}

message PhoneNumber {

string number = 1;

PhoneType type = 2;

}

repeated PhoneNumber phone = 4;

}

message AddressBook {

// 在AddressBook message中引用另一個message Person

repeated Person person = 1;

}

protobuf消息格式說明:Person消息定義指定了三個字段(名稱/值對),每一個字段對應于要包含在這種類型的消息中的數據。每個字段都有一個名稱和一個類型,以及一個序號。

指定字段類型

在上例中,所有字段都是標量類型:兩個整數(page_number和result_per_page)和一個字符串(query)。但是,您也可以為字段指定復合類型,包括枚舉和其他消息類型。

分配字段編號

如您所見,消息定義中的每個字段都有一個唯一的編號。這些字段編號用于以二進制格式標識您的字段,一旦您的消息類型被使用,就不應該被更改。請注意,1到15范圍內的字段編號需要一個字節來編碼,包括字段編號和字段類型(您可以在協議緩沖區編碼中找到更多信息)。16到2047范圍內的字段編號需要兩個字節。因此,您應該為經常出現的消息元素保留數字1到15。記住為將來可能添加的頻繁出現的元素留出一些空間。

那protobuf是怎么做到向前及向后兼容的呢?靠的就是這個字段的編號,在反序列化的時候,protobuf會從輸入流中讀取出字段編號,然后再設置message中對應的值。如果讀出來的字段編號是message中沒有的,就直接忽略,如果message中有字段編號是輸入流中沒有的,則該字段不會被設置。所以即使通信的兩端存在一方比另一方多出編號,也不會影響反序列化。但是如果兩端同一編號的字段規則或者字段類型不一樣,那就肯定會影響反序列化了。所以一般調整proto文件的時候,盡量選擇加字段或者刪字段,而不是修改字段編號或者字段類型。

您可以指定的最小字段編號為1,最大字段編號為229 - 1,即536,870,911。但是不能使用數字19000到19999 ( FieldDescriptor::kFirstReservedNumber 到FieldDescriptor::kLastReservedNumber),因為它們是為協議緩沖區實現而保留的-如果您在 .proto文件中使用這些保留的數字之一,協議緩沖區編譯器就會報錯。同樣,您也不能使用任何保留字段。

指定字段規則

消息字段可以是以下字段之一:

singular: 可以有零個或其中一個字段(但不超過一個)。

repeated: 該字段可以重復任意次數(包括零次)。重復值的順序將保留在Protocol Buffer中,將重復字段視為動態大小的數組。protobuf處理這個字段的時候,另外加了一個count計數變量,用于標明這個字段有多少個,這樣發送方發送的時候,同時發送了count計數變量和這個字段的起始地址,接收方在接受到數據之后,按照count來解析對應的數據即可。

在java中使用protobuf3

安裝idea插件

14cc690b9235dba660f290d2130a6e7b.png

添加pom依賴

com.google.protobuf

protobuf-java

3.4.0

kr.motd.maven

os-maven-plugin

1.6.2

org.xolstice.maven.plugins

protobuf-maven-plugin

0.5.0

${project.basedir}/src/main/protobuf

com.google.protobuf:protoc:3.1.0:exe:${os.detected.classifier}

compile

定義message

// user.proto

// 定義protobuf

syntax = "proto3";

option java_package = "com.ray.bigdata.protobuf";

// 指定生成的java類名

option java_outer_classname = "DemoModel";

message User {

int32 id = 1;

string name = 2;

string sex = 3;

}

測試demo

package com.ray.bigdata.canal;

import com.google.protobuf.InvalidProtocolBufferException;

import com.ray.bigdata.protobuf.DemoModel;

/**

* 使用protobuf進行數據的序列化和反序列化

*/

public class ProtobufDemo {

public static void main(String[] args) throws InvalidProtocolBufferException {

// 實例化protobuf對象

DemoModel.User.Builder builder = DemoModel.User.newBuilder();

// 給user對象進行賦值

builder.setId(1);

builder.setName("張三");

builder.setSex("男");

// 獲取user對象的屬性值

DemoModel.User userBuilder = builder.build();

System.out.println(userBuilder.getId());

System.out.println(userBuilder.getName());

System.out.println(userBuilder.getSex());

/**

* 數據的序列化和反序列化

* 序列化:可以將對象轉換成字節碼數據存儲到kafka中

* 反序列化:可以將kafka中的數據消費出來,轉換為java對象使用

*/

// 將一個對象序列化成二進制的字節碼數據存儲到kafka中

byte[] bytes = builder.build().toByteArray();

for (byte b: bytes) {

System.out.println(b);

}

// 將kafka中消費的數據反序列化

DemoModel.User user = DemoModel.User.parseFrom(bytes);

System.out.println(user);

System.out.println(user.getName());

System.out.println(user.getSex());

}

}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/378659.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/378659.shtml
英文地址,請注明出處:http://en.pswp.cn/news/378659.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

根據DbSchema生成代碼2

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Threading; using System.IO; using Rocky;namespace Rocky.CodeBuilder {public class DbBuilder : Disposable{#region 字段public even…

22-隨機抽樣一致算法RANSAC

隨機抽樣一致算法(Random sample consensus,RANSAC) 看似復雜,其基本思想就是:隨機選取倆點,然后連接,給定一個容忍范圍,在這個范圍內的點越多越好,然后不斷的迭代進行找兩點之間容忍范圍內點最…

treeset比較器_Java TreeSet比較器()方法與示例

treeset比較器TreeSet類的compare()方法 (TreeSet Class comparator() method) comparator() method is available in java.util package. 比較器()方法在java.util包中可用。 comparator() method is used to get the Comparator object based on customizing order the eleme…

智能車復工日記【1】——菜單索引回顧

博主聯系方式: QQ:1540984562 QQ交流群:892023501 群里會有往屆的smarters和電賽選手,群里也會不時分享一些有用的資料,有問題可以在群里多問問。 菜單回顧 1、系列文章解析結構元素菜單圖示菜單缺點:1、系列文章 【智能車Code review】—曲率計算、最小二乘法擬合 【智能…

[轉載]Oracle 11g R1下的自動內存經管(2)

AMM調整 除現有的用于內存經管的V$視圖外,Oracle 11g還新添加了下面4個視圖用于自動內存經管: ? ◆V$MEMORY_CURRENT_RESIZE_OPS ? ◆V$MEMORY_DYNAMIC_COMPONENTS ? ◆V$MEMORY_RESIZE_OPS ? ◆V$MEMORY_TARGET_ADVICE 轉載于:https://www.cnblogs.…

23-背景建模

幀差法 由于場景中的目標在運動,目標的影像在不同圖像幀中的位置不同。該類算法對時間上連續的兩幀圖像進行差分運算,不同幀對應的像素點相減,判斷灰度差的絕對值,當絕對值超過一定閾值時,即可判斷為運動目標&#xf…

DB2 9 運用開辟(733 考試)認證指南,第 3 部門: XML 數據獨霸(4)

議決運用順序存儲和檢索 XMLXML 編碼字符編碼在汗青上,術語 字符集、字符編碼 和 碼頁 都有雷同的意義:一個字符集和一個二進制碼集,其中每個碼示意一個字符。(碼頁是來自 IBM 的一個術語,示意一個大型主機或 IBM PC 上…

system.setin_Java System類setIn()方法及示例

system.setin系統類setIn()方法 (System class setIn() method) setIn() method is available in java.lang package. setIn()方法在java.lang包中可用。 setIn() method is used to assign again the standard input stream. setIn()方法用于再次分配標準輸入流。 setIn() met…

Opencv——霍夫變換以及遇到的一些問題

目錄問題1 :顏色空間轉換函數參數問題:CV_BGR2GRAY vs CV_GRAY2BGR問題2:cvRound()、cvFloor()、cvCeil()函數用法霍夫變換的含義標準霍夫直線變換霍夫線變換函數參數講解累計概率霍夫變換霍夫變換圓變換原理和算法步驟:霍夫圓變換…

java ssm如何上傳圖片_ssm整合-圖片上傳功能(轉)

本文介紹 ssm (SpringSpringMVCMybatis)實現上傳功能。以一個添加用戶的案例介紹(主要是將上傳文件)。一、需求介紹我們要實現添加用戶的時候上傳圖片(其實任何文件都可以)。文件名:以 博客名日期的年月日時分秒毫秒形式命名如 言曌博客2017082516403213.png路徑&am…

宏定義和內聯函數區別

內聯函數是代碼被插入到調用者代碼處的函數。如同 #define 宏,內聯函數通過避免被調用的開銷來提高執行效率,尤其是它能夠通過調用(“過程化集成”)被編譯器優化。 宏定義不檢查函數參數,返回值什么的,只是…

24-光流估計

光流是空間運動物體在觀測成像平面上的像素運動的“瞬間速度”,根據各個像素點的速度矢量特征,可以對圖像進行動態分析,例如目標跟蹤 亮度恒定:同一點隨著時間的變化,其亮度不會發生改變 小運動:隨著時間的…

java公平索非公平鎖_java中的非公平鎖不怕有的線程一直得不到執行嗎

首先來看公平鎖和非公平鎖,我們默認使用的鎖是非公平鎖,只有當我們顯示設置為公平鎖的情況下,才會使用公平鎖,下面我們簡單看一下公平鎖的源碼,如果等待隊列中沒有節點在等待,則占有鎖,如果已經…

mybatis.net - 5 嵌入資源與引用資源

在SqlMap.config文件中可以有兩種方式引入外部的文件。 一種是通過資源的方式&#xff0c;在文件中表現為 resource&#xff0c;就是引用外部的文件&#xff0c;這里需要保證文件的路徑正確。 <sqlMaps><sqlMap resource"Maps/ProductMap.xml"/><sqlM…

智能車復工日記【3】:圖像處理——基本掃線和基本特征提取和十字補線

博主聯系方式: QQ:1540984562 QQ交流群:892023501 群里會有往屆的smarters和電賽選手,群里也會不時分享一些有用的資料,有問題可以在群里多問問。 目錄 1、系列文章2、前言3、基本掃線(除了進入環島狀態或者坡道或者十字路口的普通掃線)1.基本數據和初步特征4、進一步特征…

short 用equals_Java Short類equals()方法的示例

short 用equals短類equals()方法 (Short class equals() method) equals() method is available in java.lang package. equals()方法在java.lang包中可用。 equals() method is used to check equality or inequality of this Object against the given Object or in other wo…

圖解MySQL數據庫的陳列和把持-4

泉源&#xff1a;網海拾貝 填入一些測試數據&#xff1a; 封閉“MySQL Query Browser”&#xff0c;再從頭翻開它&#xff0c;切換到testtable表&#xff0c;看到了沒有&#xff1f;剛剛輸出的中文變成了“&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&am…

非常好友(C++)

Bessie和其他的所有奶牛的耳朵上都戴有一個射頻識別&#xff08;RFID&#xff09;序列號碼牌。因此農夫John可以機械化地計算他們的數量。很多奶牛都有一個“牛友”。如果奶牛A的序列號的約數之和剛好等于奶牛B的序列號&#xff0c;那么A的牛友就是B。在這里&#xff0c;一個數…

智能車復工日記【2】——普通PID、變結構PID、微分先行PID、模糊PID、專家PID

博主聯系方式: QQ:1540984562 QQ交流群:892023501 群里會有往屆的smarters和電賽選手,群里也會不時分享一些有用的資料,有問題可以在群里多問問。 目錄 系列文章前言普通PID舵機參數:電機參數:變結構PI控制(電機控制,這里對公式進行修改采用正態分布公式)微分先行PID(…

爬動的蠕蟲(C++)

問題描述&#xff1a; 一條蟲子在n英寸深的井底&#xff0c;每次一分鐘爬行u英寸&#xff0c;但是它再次爬行前必須先休息1分鐘&#xff0c;在休息過程中它將滑落d英寸&#xff0c;在反復向上爬行和休息后&#xff0c;多長時間蟲子能爬出這口井&#xff1f;在此過程中&#xf…