android解析jason框架,Android 数据交换解析框架Gson使用详解

原标题:Android 数据交换解析框架Gson使用详解

Json 是一种文本形式的数据交换格式,比 xml 更为轻量。Json 的解析和生成的方式很多,在 Android 平台上最常用的类库有 Gson 和 FastJson 两种,这里要介绍的是 Gson。

Gson主页:https://github.com/google/gson

一、Gson的基本用法1.1、Gson对象

在进行序列化与反序列操作前,需要先实例化一个 com .google.gson.Gson 对象,获取 Gson 对象的方法有两种

//通过构造函数来获取

Gson gson = newGson();

//通过 GsonBuilder 来获取,可以进行多项特殊配置

Gson gson = newGsonBuilder().create();

1.2、生成 Json

利用 Gson 可以很方便地生成 Json 字符串,通过使用 addProperty 的四个重载方法

publicstaticvoidmain(String[] args){

JsonObject jsonObject = newJsonObject();

jsonObject.addProperty( "String", "leavesC");

jsonObject.addProperty( "Number_Integer", 23);

jsonObject.addProperty( "Number_Double", 22.9);

jsonObject.addProperty( "Boolean", true);

jsonObject.addProperty( "Char", 'c');

System. out.println();

System. out.println(jsonObject);

}

67f13ac1fb8ba5634db195c90b08bce3.png

addProperty 方法底层调用的是 add(String property, JsonElement value) 方法,即将基本数据类型转化为了 JsonElement对象,JsonElement 是一个抽象类,而 JsonObject继承了 JsonElement ,因此我们可以通过 JsonObject 自己来构建一个 JsonElement

publicstaticvoidmain(String[] args){

JsonObject jsonObject = newJsonObject();

jsonObject.addProperty( "String", "leavesC");

jsonObject.addProperty( "Number", 23);

jsonObject.addProperty( "Number", 22.9);

jsonObject.addProperty( "Boolean", true);

jsonObject.addProperty( "Char", 'c');

JsonObject jsonElement = newJsonObject();

jsonElement.addProperty( "Boolean", false);

jsonElement.addProperty( "Double", 25.9);

jsonElement.addProperty( "Char", 'c');

jsonObject. add( "JsonElement", jsonElement);

System. out.println();

System. out.println(jsonObject);

}

3cd5614fb76d3b57c3558e1630488d55.png

1.3、Json与数组、List的转化

Json数组 与 字符串数组

publicstaticvoidmain(String[] args){

//Json数组 转为 字符串数组

Gson gson = newGson();

String jsonArray = "["https://github.com/leavesC","https://www.jianshu.com/u/9df45b87cfdf","Java","Kotlin","Git","GitHub"]";

String[] strings = gson.fromJson(jsonArray, String[].class);

System. out.println( "Json数组 转为 字符串数组: ");

for(String string: strings) {

System. out.println( string);

}

//字符串数组 转为 Json数组

jsonArray = gson.toJson(jsonArray, newTypeToken() {

}.getType());

System. out.println( "n字符串数组 转为 Json数组: ");

System. out.println(jsonArray);

}

2f4ae944e1dc4fad3804658bc308e802.png

Json数组 与 List

publicstaticvoidmain(String[] args){

//Json数组 转为 List

Gson gson = newGson();

String jsonArray = "["https://github.com/leavesC","https://www.jianshu.com/u/9df45b87cfdf","Java","Kotlin","Git","GitHub"]";

List stringList = gson.fromJson(jsonArray, newTypeToken>() {

}.getType());

System. out.println( "nJson数组 转为 List: ");

for(String string: stringList) {

System. out.println( string);

}

//List 转为 Json数组

jsonArray = gson.toJson(stringList, newTypeToken>() {

}.getType());

System. out.println( "nList 转为 Json数组: ");

System. out.println(jsonArray);

}

7f30bec5c03acfa7494233c2e41e2661.png

1.4、序列化与反序列化

Gson 也提供了 toJson() 和 fromJson() 两个方法用于转化 Model 与 Json,前者实现了序列化,后者实现了反序列化

首先,声明一个 User 类

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassUser{

privateString name;

privateintage;

privatebooleansex;

publicUser(String name, intage, booleansex){

this.name = name;

this.age = age;

this.sex = sex;

}

@Override

publicString toString(){

return"User{"+

"name='"+ name + '''+

", age="+ age +

", sex="+ sex +

'}';

}

}

序列化的方法很简单,调用 gson 对象的 toJson 方法,传入要序列化的对象

publicstaticvoidmain(String[] args){

//序列化

User user = newUser( "leavesC", 24, true);

Gson gson = newGson();

System. out.println();

System. out.println(gson.toJson(user));

}

6f36e259523176c97d24ac310b7d23cb.png

反序化的方式也类似

publicstaticvoidmain(String[] args){

//反序列化

String userJson = "{"name":"leavesC","age":24,"sex":true}";

Gson gson = newGson();

User user = gson.fromJson(userJson, User.class);

System. out.println();

System. out.println(user);

}

二、属性重命名

继续使用上一节声明的 User 类,根据 User 类声明的各个属性名,移动端的开发者希望接口返回的数据格式即是如下这样的

{ "name": "leavesC", "age": 24, "sex": true}

如果没有和服务器端沟通好或者是 API 改版了,接口返回的数据格式可能是这样的

{ "Name": "leavesC", "age": 24, "sex": true}

{ "userName": "leavesC", "age": 24, "sex": true}

如果继续使用上一节介绍的方法,那无疑会解析出错

例如

publicstaticvoidmain(String[] args){

//反序列化

String userJson = "{"userName":"leavesC","age":24,"sex":true}";

Gson gson = newGson();

User user = gson.fromJson(userJson, User.class);

System. out.println();

System. out.println(user);

}

name 属性值解析不到,所以为 null

d39ed07b35021d495a6c5db2348e878c.png

此时为了兼顾多种格式的数据,就需要使用 SerializedName注解

根据 SerializedName 的声明来看,SerializedName 包含两个属性值,一个是字符串,一个是字符串数组,而字符串数组含有默认值

@ Documented

@Retention(RetentionPolicy.RUNTIME)

@Target({ ElementType.FIELD, ElementType.METHOD})

public@ interfaceSerializedName {

Stringvalue();

String[]alternate() default{};

}

SerializedName 的作用是为了在序列化或反序列化时,指导 Gson 如果将原有的属性名和其它特殊情况下的属性名联系起来

例如,修改 User 类,为 name 声明 SerializedName 注解,注解值为 userName

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassUser{

@SerializedName( "userName")

privateString name;

privateintage;

privatebooleansex;

}

在序列时,Json 格式就会相应改变

publicstaticvoidmain(String[] args){

//序列化

User user = newUser( "leavesC", 24, true);

Gson gson = newGson();

System. out.println();

System. out.println(gson.toJson(user));

}

edba7e9ef23dc14ccce067c1084378be.png

在反序列化时也一样,能够解析到正确的属性值

publicstaticvoidmain(String[] args){

//反序列化

String userJson = "{"userName":"leavesC","age":24,"sex":true}";

Gson gson = newGson();

User user = gson.fromJson(userJson, User.class);

System. out.println();

System. out.println(user);

}

5e6a10beeebf4eee74c49921d8b300fb.png

还有个问题没解决,为了应对多种属性名不一致的情况,难道我们要声明多个 User 类吗?这显然是不现实的,所以还需要为 User 类设置多个备选属性名,这就需要用到 SerializedName 注解的另一个属性值 alternate了。

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassUser{

@SerializedName( value= "userName", alternate = { "user_name", "Name"})

privateString name;

privateintage;

privateboolean sex;

}

以下几种情况都能够被正确的反序列化

publicstaticvoidmain(String[] args){

//反序列化

Gson gson = newGson();

String userJson = "{"userName":"leavesC","age":24,"sex":true}";

User user = gson.fromJson(userJson, User.class);

System. out.println();

System. out.println(user);

userJson = "{"user_name":"leavesC","age":24,"sex":true}";

user = gson.fromJson(userJson, User.class);

System. out.println();

System. out.println(user);

userJson = "{"Name":"leavesC","age":24,"sex":true}";

user = gson.fromJson(userJson, User.class);

System. out.println();

System. out.println(user);

}

d33d148cfabebcfc20978d9ce840de53.png

三、字段过滤

有时候并不是所有的字段都需要进行系列化和反序列化,因此需要对某些字段进行排除,有四种方法可以来实现这种需求。

3.1、基于@Expose注解

Expose 注解包含两个属性值,且均声明了默认值。Expose 的含义即为“暴露”,即用于对外暴露字段,serialize 用于指定是否进行序列化,deserialize 用于指定是否进行反序列化。如果字段不声明 Expose 注解,则意味着不进行序列化和反序列化操作,相当于两个属性值均为 false 。此外,Expose 注解需要和 GsonBuilder 构建的 Gson 对象一起使用才能生效。

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.FIELD})

public@interfaceExpose {

booleanserialize()defaulttrue;

booleandeserialize()defaulttrue;

}

Expose 注解的注解值声明情况有四种

@Expose(serialize = true, deserialize = true) //序列化和反序列化都生效

@Expose(serialize = false, deserialize = true) //序列化时不生效,反序列化时生效

@Expose(serialize = true, deserialize = false) //序列化时生效,反序列化时不生效

@Expose(serialize = false, deserialize = false) //序列化和反序列化都不生效,和不写注解一样

现在来看个例子,修改 User 类

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassUser{

@Expose(serialize = true, deserialize = true) //序列化和反序列化都生效

privateString a;

@Expose(serialize = false, deserialize = true) //序列化时不生效,反序列化时生效

privateString b;

@Expose(serialize = true, deserialize = false) //序列化时生效,反序列化时不生效

privateString c;

@Expose(serialize = false, deserialize = false) //序列化和反序列化都不生效,和不写注解一样

privateString d;

privateString e;

publicUser(String a, String b, String c, String d, String e){

this.a = a;

this.b = b;

this.c = c;

this.d = d;

this.e = e;

}

@Override

publicString toString(){

return"User{"+

"a='"+ a + '''+

", b='"+ b + '''+

", c='"+ c + '''+

", d='"+ d + '''+

", e='"+ e + '''+

'}';

}

}

按照如上的注解值,只有声明了 Expose 注解且 serialize 值为 true 的字段才能被序列化,只有声明了 Expose 注解且 deserialize 值为 true 的字段才能被反序列化

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

User user = newUser( "A", "B", "C", "D", "E");

System. out.println();

System. out.println(gson.toJson(user));

String json = "{"a":"A","b":"B","c":"C","d":"D","e":"E"}";

user = gson.fromJson(json, User.class);

System. out.println();

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

}

4394491630278e6a04f245ae7b360a12.png

3.2、基于版本

Gson 提供了 @Since 和 @Until 两个注解基于版本对字段进行过滤,@Since 和 @Until 都包含一个 Double 属性值,用于设置版本号。Since 的意思是“自……开始”,Until 的意思是“到……为止”,一样要和 GsonBuilder 配合使用。

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.FIELD, ElementType.TYPE})

public@interfaceSince {

doublevalue();

}

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.FIELD, ElementType.TYPE})

public@interfaceUntil {

doublevalue();

}

当版本( GsonBuilder 设置的版本) 大于或等于 Since 属性值或小于 Until 属性值时字段会进行序列化和反序列化操作,而没有声明注解的字段都会加入序列化和反序列操作

现在来看个例子,修改 User 类

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassUser{

@Since(1.4)

privateString a;

@Since(1.6)

privateString b;

@Since(1.8)

privateString c;

@Until(1.6)

privateString d;

@Until(2.0)

privateString e;

publicUser(String a, String b, String c, String d, String e) {

this.a = a;

this.b = b;

this.c = c;

this.d = d;

this.e = e;

}

@Override

publicString toString() {

return"User{"+

"a='"+ a + '''+

", b='"+ b + '''+

", c='"+ c + '''+

", d='"+ d + '''+

", e='"+ e + '''+

'}';

}

}

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder().setVersion( 1.6).create();

User user = newUser( "A", "B", "C", "D", "E");

System. out.println();

System. out.println(gson.toJson(user));

String json = "{"a":"A","b":"B","c":"C","d":"D","e":"E"}";

user = gson.fromJson(json, User.class);

System. out.println();

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

}

ef5e26eb473ec606637b48a7146abb25.png

3.3、基于访问修饰符

访问修饰符由 java.lang.reflect.Modifier提供 int 类型的定义,而 GsonBuilder 对象的 excludeFieldsWithModifiers方法接收一个 int 类型可变参数,指定不进行序列化和反序列化操作的访问修饰符字段

看个例子

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

public classModifierSample{

public StringpublicField = "public";

protected StringprotectedField = "protected";

private StringprivateField = "private";

StringdefaultField = "default";

final StringfinalField = "final";

staticStringstaticField = "static";

}

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder().excludeFieldsWithModifiers(Modifier.PRIVATE, Modifier.STATIC).create();

ModifierSample modifierSample = newModifierSample();

System. out.println(gson.toJson(modifierSample));

}

e1aa4e61440b16a27591d9dd8b649ffd.png

3.4、基于策略

GsonBuilder 类包含 setExclusionStrategies(ExclusionStrategy... strategies)方法用于传入不定长参数的策略方法,用于直接排除指定字段名或者指定字段类型

看个例子

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassStrategies{

privateString stringField;

privateintintField;

privatedoubledoubleField;

publicStrategies(String stringField, intintField, doubledoubleField){

this.stringField = stringField;

this.intField = intField;

this.doubleField = doubleField;

}

@ Override

publicString toString(){

return"Strategies{"+

"stringField='"+ stringField + '''+

", intField="+ intField +

", doubleField="+ doubleField +

'}';

}

}

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder().setExclusionStrategies( newExclusionStrategy() {

@ Override

publicboolean shouldSkipField(FieldAttributes fieldAttributes) {

//排除指定字段名

returnfieldAttributes.getName(). equals( "intField");

}

@ Override

publicboolean shouldSkipClass(Class> aClass) {

//排除指定字段类型

returnaClass.getName(). equals( double.class.getName());

}

}).create();

Strategies strategies = newStrategies( "stringField", 111, 11.22);

System. out.println();

System. out.println(gson.toJson(strategies));

String json = "{"stringField":"stringField","intField":111,"doubleField":11.22}";

strategies = gson.fromJson(json, Strategies.class);

System. out.println();

System. out.println(strategies);

}

字段名为 "intField" 和字段类型为 double 的字段都会被排除掉

8e627ce0ea746b032b11816ae49eaf65.png

setExclusionStrategies 方法在序列化和反序列化时都会生效,如果只是想指定其中一种情况下的排除策略或分别指定排除策略,可以改为使用以下两个方法

addSerializationExclusionStrategy(ExclusionStrategy strategy);

addDeserializationExclusionStrategy(ExclusionStrategy strategy);

四、个性化配置4.1、输出 null

对于 Gson 而言,在序列化时如果某个属性值为 null 的话,那么在序列化时该字段不会参与进来,如果想要显示输出该字段的话,可以通过 GsonBuilder 进行配置

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassStrategies{

privateString stringField;

privateintintField;

privatedoubledoubleField;

}

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder()

.serializeNulls() //输出null

.create();

Strategies strategies = newStrategies( null, 24, 22.333);

System. out.println();

System. out.println(gson.toJson(strategies));

}

f2e7aa8a8c1ecb0b88228a6afc516966.png

4.2、格式化输出Json

默认的序列化后的 Josn 字符串并不太直观,可以选择格式化输出

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder()

.serializeNulls() //输出null

.setPrettyPrinting() //格式化输出

.create();

Strategies strategies = newStrategies( null, 24, 22.333);

System. out.println();

System. out.println(gson.toJson(strategies));

}

daf62ec3da88c8ca10c3b7ca0c6ee5b5.png

4.3、格式化时间

Gson 也可以对时间值进行格式化

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassStrategies{

privateDate date;

privateDate date2;

publicStrategies(Date date, Date date2){

this.date = date;

this.date2 = date2;

}

@ Override

publicString toString(){

SimpleDateFormat simpleDateFormat = newSimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS", Locale.CHINA);

return"Strategies{"+

"date="+ simpleDateFormat.format(date) +

", date2="+ simpleDateFormat.format(date2) +

'}';

}

}

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder()

.setPrettyPrinting() //格式化输出

.setDateFormat( "yyyy-MM-dd HH:mm:ss:SSS") //格式

化时间

.create();

Date date = newDate();

Strategies strategies = newStrategies(date, newDate(date.getTime() + 1000000));

System. out.println();

System. out.println(gson.toJson(strategies));

String json = "{n"+

" "date": "2018-03-17 19:38:50:033",n"+

" "date2": "2018-03-17 19:55:30:033"n"+

"}";

System. out.println();

System. out.println(gson.fromJson(json, Strategies.class));

}

5da19c369be7c26ceff18af60a81bc90.png

五、TypeAdapter

TypeAdapter 是一个泛型抽象类,用于接管某种类型的序列化和反序列化过程,包含两个抽象方法,分别用于自定义序列化和反序列化过程

publicabstractvoidwrite(JsonWriter var1, T var2)throwsIOException;

publicabstractT read(JsonReader var1)throwsIOException;

下面看个简单的例子

/**

* 作者:chenZY

* 时间:2018/3/17 18:32

* 描述:https://github.com/leavesC

*/

publicclassUser{

privateString name;

privateintage;

privatebooleansex;

publicUser(){

}

publicUser(String name, intage, booleansex){

this.name = name;

this.age = age;

this.sex = sex;

}

@Override

publicString toString(){

return"User{"+

"name='"+ name + '''+

", age="+ age +

", sex="+ sex +

'}';

}

}

定义 TypeAdapter 的子类 UserTypeAdapter 来接管 User 类的序列化和反序列化过程

这里设定当 User 类序列化时 Json 中的Key值都是大写字母开头,反序列化时支持“name”和“Name”两种不同的 Json 风格

publicclassUserTypeAdapterextendsTypeAdapter{

@Override

publicvoidwrite(JsonWriter jsonWriter, User user)throwsIOException{

//流式序列化成对象开始

jsonWriter.beginObject();

//将Json的Key值都指定为大写字母开头

jsonWriter.name( "Name").value(user.getName());

jsonWriter.name( "Age").value(user.getAge());

jsonWriter.name( "Sex").value(user.isSex());

//流式序列化结束

jsonWriter.endObject();

}

@Override

publicUser read(JsonReader jsonReader)throwsIOException{

User user = newUser();

//流式反序列化开始

jsonReader.beginObject();

while(jsonReader.hasNext()) {

switch(jsonReader.nextName()) {

//首字母大小写均合法

case"name":

case"Name":

user.setName(jsonReader.nextString());

break;

case"age":

user.setAge(jsonReader.nextInt());

break;

case"sex":

user.setSex(jsonReader.nextBoolean());

break;

}

}

//流式反序列化结束

jsonReader.endObject();

returnuser;

}

}

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder().registerTypeAdapter(User.class, newUserTypeAdapter()).create();

User user = newUser( "leavesC", 24, true);

System. out.println();

System. out.println(gson.toJson(user));

String json = "{"Name":"leavesC","age":24,"sex":true}";

user = gson.fromJson(json, User.class);

System. out.println();

System. out.println(user);

}

可以看到 User 类按照预定义的策略来完成序列化和反序列化了

ef6cf0163a5711877ef5496398d30046.png

六、JsonSerializer 和 JsonDeserializer

TypeAdapter 将序列化和反序列操作都接管了过来,其实 Gson 还提供了只接管序列化过程的接口,即 JsonSerializer

看个例子

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder().registerTypeAdapter(User.class, newJsonSerializer() {

@ Override

publicJsonElement serialize(User user, Type type, JsonSerializationContext jsonSerializationContext) {

JsonObject jsonObject = newJsonObject();

jsonObject.addProperty( "NameHi", user.getName());

jsonObject.addProperty( "Sex", user.isSex());

jsonObject.addProperty( "Age", user.getAge());

returnjsonObject;

}

}).create();

User user = newUser( "leavesC", 24, true);

System. out.println();

System. out.println(gson.toJson(user));

}

b1400a09105e404b0c61b6e76f4e73c4.png

相对应的,JsonDeserializer 接口提供了反序列化的接口

publicstaticvoidmain(String[] args){

Gson gson = newGsonBuilder().registerTypeAdapter(User.class, newJsonDeserializer() {

@ Override

publicUser deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {

JsonObject jsonObject = jsonElement.getAsJsonObject();

String name = null;

//同时支持 userName 和 name 两种情况

if(jsonObject.has( "userName")) {

name = jsonObject. get( "userName").getAsString();

} elseif(jsonObject.has( "name")) {

name = jsonObject. get( "name").getAsString();

}

intage = jsonObject. get( "age").getAsInt();

boolean sex = jsonObject. get( "sex").getAsBoolean();

returnnewUser(name, age, sex);

}

}).create();

String json = "{"userName":"leavesC","sex":true,"age":24}";

User user = gson.fromJson(json, User.class);

System. out.println();

System. out.println(user);

json = "{"name":"leavesC","sex":true,"age":24}";

user = gson.fromJson(json, User.class);

System. out.println();

System. out.println(user);

}

2b55027ce7451ee5b867953704466751.png

这里有个比较麻烦的地方,那就是在使用 TypeAdapter 、JsonSerializer和 JsonDeserializer时,总需要调用 registerTypeAdapter方法进行注册,那有没有更简单的注册方法呢?

有的,Gosn 还提供了另一个注解 @JsonAdapter用于进行简单的声明

类似于这样,声明了 User 类的序列化或反序列化操作由 UserTypeAdapter 完成,注解的优先级高于 registerTypeAdapter方法

@JsonAdapter(UserTypeAdapter.class)

publicclassUser{

}

七、TypeAdapterFactory

TypeAdapterFactory 是用于创建 TypeAdapter 的工厂类,通过参数 TypeToken 来查找确定对应的 TypeAdapter,如果没有就返回 null 并由 Gson 默认的处理方法来进行序列化和反序列化操作,否则就由用户预定义的 TypeAdapter 来进行处理

Gson gson = newGsonBuilder().registerTypeAdapterFactory( newTypeAdapterFactory() {

@Override

public TypeAdapter create(Gson gson, TypeToken typeToken){

//如果 Gson 需要与 User 类相关的 TypeAdapter ,则返回我们自己定义的 TypeAdapter 对象

if(typeToken.getType().getTypeName(). equals(User.class.getTypeName())) {

return(TypeAdapter) newUserTypeAdapter();

}

//找不到则返回null

returnnull;

}

}).create();

八、结语

这一篇文章好像写得太长了一点?Gson 的知识点介绍到这里也差不多了,以后如果还发现新内容的话我会继续补充,现在就先这样啦

GitHub 主页: leavesC -> https://github.com/leavesC

责任编辑:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值