使用 Jackson 操作 JSON 数据
文章目录
- 1. 工具简介
- 2. 简单使用
- 2.1. 数据绑定
- 2.1.1. JavaBean 转 JSON 字符串
- 2.1.2. JSON 字符串转 JavaBean
- 2.1.3. JSON 字符串转 Map 集合
- 2.1.4. JavaBean 写入到文件
- 2.1.5. JavaBean 转字节数组
- 2.2. JSON 树模型
- 2.2.1. 构建 JSON 树模型
- 2.2.2. JSON 树转 JSON 字符串
- 2.2.3. 解析 JSON 树模型
- 2.3. 流式 API
- 3. 注解使用
- 3.1. @JsonProperty
- 3.2. @JsonIgnore
- 3.3. @JsonIgnoreProperties
- 3.4. @JsonIgnoreType
- 3.5. @JsonInclude
- 3.6. @JsonFormat
- 3.7. @JsonPropertyOrder
- 3.8. @JsonRootName
- 3.9. @JsonAnySetter 和 @JsonAnyGetter
- 3.9.1. @JsonAnyGetter
- 3.9.2. @JsonAnySetter
- 3.9.3. 示例代码
- 3.10. @JsonNaming
- 4. Jackson 配置
1. 工具简介
Jackson 项目 GitHub 主页
Baeldung 上关于 JSON 的使用
涉及到一些不常见的用法或功能,可以到以上两个地址查看,网上博客类的资料相对较少。
Jackson
核心模块是所有扩展的基础。目前有3
个核心模块(从Jackson 2.x
开始):
Streaming
:(jackson-core
)定义了低级流API
,包括JSON
特性的实现。Annotations
:(jackson-annotations
)包含标准的Jackson
注解。Databind
:(jackson-databind
)实现了数据绑定(和对象序列化)支持,它依赖于Streaming
和Annotations
包。
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>${jackson.version.core}</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>${jackson-annotations-version}</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency>
SpringBoot
中使用的话引入web
依赖,就直接引入了Jackson
:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
依赖如下:
2. 简单使用
Jackson
提供了三种JSON
的处理方式。分别是数据绑定
,JSON树模型
,流式API
。下面分别介绍这三种方式。
2.1. 数据绑定
import lombok.Data;
@Data
public class Student {private Long id;private String name;private Integer age;private String sex;private String[] interest;
}
public class Test {public static void main(String[] args) throws IOException {Student student = new Student();student.setId(1L);student.setName("zhangsan");student.setAge(20);student.setInterest(new String[]{"music", "coding"});ObjectMapper mapper = new ObjectMapper();//测试代码......}
}
2.1.1. JavaBean 转 JSON 字符串
String studentStr = mapper.writeValueAsString(student);
System.out.println(studentStr);
//{"id":1,"name":"zhangsan","age":20,"sex":null,"interest":["music","coding"]}
2.1.2. JSON 字符串转 JavaBean
Student stu = mapper.readValue(studentStr, Student.class);
System.out.println(stu);
//Student(id=1, name=zhangsan, age=20, sex=null, interest=[music, coding])
2.1.3. JSON 字符串转 Map 集合
//对泛型的反序列化,使用TypeReference可以明确的指定反序列化的类型。
//import com.fasterxml.jackson.core.type.TypeReference;
Map<String, Object> map = mapper.readValue(studentStr, new TypeReference<Map<String, Object>>(){});
System.out.println(map);
//{id=1, name=zhangsan, age=20, sex=null, interest=[music, coding]}
2.1.4. JavaBean 写入到文件
//写到文件
mapper.writeValue(new File("/json.txt"), student);
//从文件中读取
Student student1 = mapper.readValue(new File("/json.txt"), Student.class);
System.out.println(student1);
//Student(id=1, name=zhangsan, age=20, sex=null, interest=[music, coding])
2.1.5. JavaBean 转字节数组
//写为字节流
byte[] bytes = mapper.writeValueAsBytes(student);
//从字节流读取
Student student2 = mapper.readValue(bytes, Student.class);
System.out.println(student2);
//Student(id=1, name=zhangsan, age=20, sex=null, interest=[music, coding])
2.2. JSON 树模型
Jackson
树模型结构,可以通过path
,get
,JsonPointer
等进行操作,适合用来获取大JSON
中的字段,比较灵活。缺点是如果需要获取的内容较多,会显得比较繁琐。
2.2.1. 构建 JSON 树模型
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
public class Test {public static void main(String[] args) throws IOException {//构建JSON树ObjectMapper mapper = new ObjectMapper();ObjectNode root = mapper.createObjectNode();root.put("id", 1L);root.put("name", "zhangsan");root.put("age", 20);ArrayNode interest = root.putArray("interest");interest.add("music");interest.add("coding");//测试代码......}
}
2.2.2. JSON 树转 JSON 字符串
String json = mapper.writeValueAsString(root);
System.out.println(json);
//{"id":1,"name":"zhangsan","age":20,"interest":["music","coding"]}
2.2.3. 解析 JSON 树模型
//将JSON字符串转为JSON树
JsonNode rootNode = mapper.readTree(json);
//解析值,使用path或者get
Long id = rootNode.path("id").asLong();
System.out.println(id);//1
String name = rootNode.path("name").asText();
System.out.println(name);//zhangsan
Integer age = rootNode.get("age").asInt();
System.out.println(age);//20
//解析数组
JsonNode arrayNode = rootNode.get("interest");
if (arrayNode.isArray()){for (JsonNode jsonNode : arrayNode){System.out.println(jsonNode.asText());//music//coding}
}
path
和get
方法看起来很相似,其实它们的细节不同。
path
方法取不存在的值的时候会返回一个"missing node"
,该"missing node"
的isMissingNode
方法返回值为true
,如果调用该node
的asText
方法的话,结果是一个空字符串。
System.out.println(rootNode.path("notExist").isMissingNode());//true
System.out.println(rootNode.path("notExist").asText());//空串
get
方法取不存在的值的时候,直接会返回null
。
System.out.println(rootNode.get("notExist") == null);//true
System.out.println(rootNode.get("notExist"));//null
当key
存在,而value
为null
的时候,get
和path
都会返回一个NullNode
节点。该节点的asText
方法返回null
字符串。
String s = "{\"nullNode\":null}";
JsonNode jsonNode = mapper.readTree(s);
System.out.println(jsonNode.get("nullNode") instanceof NullNode);//true
System.out.println(jsonNode.get("nullNode"));//null
System.out.println(jsonNode.path("nullNode") instanceof NullNode);//true
System.out.println(jsonNode.path("nullNode"));//null
使用JsonPointer
解析JSON
树。
String s1 = "{\"obj\": {\"name\": \"wang\",\"class\": \"3\"}}";
JsonNode jsonNode1 = mapper.readTree(s1);
JsonPointer jsonPointer = JsonPointer.valueOf("/obj/name");
JsonNode node = jsonNode1.at(jsonPointer);
System.out.println(node.asText());//wang
2.3. 流式 API
参考地址:JackSon的几种用法
流式API
是一套比较底层的API
,速度快,但是使用起来特别麻烦。它主要是有两个核心类,一个是JsonGenerator
,用来生成JSON
,另一个是JsonParser
,用来读取JSON
内容。
import com.fasterxml.jackson.core.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Test {public static void main(String[] args) throws IOException {JsonFactory factory = new JsonFactory();String s = "{\"id\": 1,\"name\": \"小明\",\"array\": [\"1\", \"2\"]," +"\"test\":\"I'm test\",\"nullNode\":null,\"base\": {\"major\": \"物联网\",\"class\": \"3\"}}";//这里就举一个比较简单的例子,Generator的用法就是一个一个write即可。File file = new File("/json.txt");JsonGenerator jsonGenerator = factory.createGenerator(file, JsonEncoding.UTF8);//对象开始jsonGenerator.writeStartObject();//写入一个键值对jsonGenerator.writeStringField("name", "小光");//对象结束jsonGenerator.writeEndObject();//关闭jsonGeneratorjsonGenerator.close();//读取刚刚写入的jsonFileInputStream inputStream = new FileInputStream(file);int i = 0;final int SIZE = 1024;byte[] buf = new byte[SIZE];StringBuilder sb = new StringBuilder();while ((i = inputStream.read(buf)) != -1) {System.out.println(new String(buf,0,i));}inputStream.close();//JsonParser解析的时候,思路是把json字符串根据边界符分割为若干个JsonToken,这个JsonToken是一个枚举类型。//下面这个小例子,可以看出JsonToken是如何划分类型的。JsonParser parser = factory.createParser(s);while (!parser.isClosed()){JsonToken token = parser.currentToken();System.out.println(token);parser.nextToken();}JsonParser jsonParser = factory.createParser(s);//下面是一个解析的实例while (!jsonParser.isClosed()) {JsonToken token = jsonParser.nextToken();if (JsonToken.FIELD_NAME.equals(token)) {String currentName = jsonParser.currentName();token = jsonParser.nextToken();if ("id".equals(currentName)) {System.out.println("id:" + jsonParser.getValueAsInt());} else if ("name".equals(currentName)) {System.out.println("name:" + jsonParser.getValueAsString());} else if ("array".equals(currentName)) {token = jsonParser.nextToken();while (!JsonToken.END_ARRAY.equals(token)) {System.out.println("array:" + jsonParser.getValueAsString());token = jsonParser.nextToken();}}}}}
}
3. 注解使用
参考地址:注解文档
3.1. @JsonProperty
使用在JavaBean
的字段上,指定一个字段用于JSON
映射,默认情况下映射的JSON
字段与注解的字段名称相同。该注解有三个属性:
value
:用于指定映射的JSON
的字段名称。常用。index
:用于指定映射的JSON
的字段顺序。defaultValue
:定义为元数据的文本默认值。注意:core databind
不使用该属性,它目前只公开给扩展模块使用。
@JsonProperty(value = “user_name”)
3.2. @JsonIgnore
可用于字段、getter/setter、构造函数参数上,作用相同,都会对相应的字段产生影响。使相应字段不参与序列化和反序列化,也就是说,向 getter 添加该注解会禁用 setter。除非 setter 上有@JsonProperty
注解,在这种情况下,这被认为是一个“分割属性”,启用了“setter”,但没有“getter”(“只读”,因此属性可以从输入读取,但不是写输出)。
3.3. @JsonIgnoreProperties
该注解是类注解。该注解在 Java 类和 JSON 不完全匹配的时候使用。
- 在序列化为 JSON 的时候,
@JsonIgnoreProperties({"prop1", "prop2"})
会忽略 pro1 和 pro2 两个属性。 - 在从 JSON 反序列化为 Java 类的时候,
@JsonIgnoreProperties(ignoreUnknown=true)
会忽略所有没有 getter 和 setter 的属性,也就是忽略类中不存在的字段。
3.4. @JsonIgnoreType
该注解是类注解,序列化为 JSON 的时候会排除所有指定类型的字段。
3.5. @JsonInclude
用于定义在序列化时是否不应包含某些“非值”(null 值或空值)的注解。可以用于每个字段上,也可以用于类上(表示用于类的所有属性)。
//忽略类中值为null的字段
@JsonInclude(value = JsonInclude.Include.NON_NULL)
//忽略类中值为空的字段。对于字符串,即忽略null或空字符串
@JsonInclude(Include.NON_EMPTY)
3.6. @JsonFormat
用于字段上,预期类型行为的通用注释;例如,可以用来指定序列化日期/时间值时使用的格式。
java.util.Date
使用如下,java.sql.Date
类似。(注意时区问题,这里添加了timezone = "GMT+8"
)
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
public class DateModel {@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date date;public Date getDate() {return date;}public void setDate(Date date) {this.date = date;}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Date;
public class Test {public static void main(String[] args) throws JsonProcessingException {DateModel dateModel = new DateModel();dateModel.setDate(new Date());ObjectMapper mapper = new ObjectMapper();System.out.println(mapper.writeValueAsString(dateModel));//{"date":"2020-01-01 12:16:54"}}
}
但是注意如果JavaBean
中的时间字段使用的是JDK8
新增的时间日期(LocalDate / LocalTime / LocalDateTime
)字段的话,直接这样使用是不起作用的。我们需要添加其他匹配,具体可参考GitHub
上的说明:Jackson格式化JDK8日期。
- 添加
jackson-datatype-jsr310
的maven
配置,SpringBoot
的web
模块会自动引入。 - 需要进行模块注册。具体看下面的示例代码。
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class DateModel {@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime date;public LocalDateTime getDate() {return date;}public void setDate(LocalDateTime date) {this.date = date;}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDateTime;
public class Test {public static void main(String[] args) throws JsonProcessingException {DateModel dateModel = new DateModel();dateModel.setDate(LocalDateTime.now());ObjectMapper mapper = new ObjectMapper();//自动发现并注册模块mapper.findAndRegisterModules();System.out.println(mapper.writeValueAsString(dateModel));//{"date":"2020-01-01 12:40:08"}}
}
3.7. @JsonPropertyOrder
和@JsonProperty
的index
属性类似,指定属性序列化时的顺序。
3.8. @JsonRootName
类注解。用于指定JSON
根属性的名称。生成的JSON
如下所示:
{"Teacher":{"id":2,"name":"wangwu","age":35}}
示例代码:
@JsonRootName("Teacher")
public class Teacher {private Long id;private String name;private Integer age;@JsonIgnore//转换为JSON时不需要的字段,用在属性上。private String sex;//省略Setter/Getter方法@Overridepublic String toString() {return "Teacher{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", sex='" + sex + '\'' +'}';}
}
Teacher teacher = new Teacher();
teacher.setId(2L);
teacher.setName("wangwu");
teacher.setAge(35);
teacher.setSex("男");ObjectMapper mapper = new ObjectMapper();
//开启包装根值的配置
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);//将Java对象转换为JSON字符串
String teacherStr = mapper.writeValueAsString(teacher);
System.out.println(teacherStr);
//{"Teacher":{"id":2,"name":"wangwu","age":35}}//开启了根包装之后,生成的json字符串和java类不对应了,
//所以在反序列化为java类的时候会报错,关闭该属性不会报错,但是值会为空
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);//将JSON字符串转换为Java对象
Teacher tea = mapper.readValue(teacherStr, Teacher.class);
System.out.println(tea);
//Teacher{id=null, name='null', age=null, sex='null'}
3.9. @JsonAnySetter 和 @JsonAnyGetter
这两个属性是用来在序列化和反序列化的时候多余字段可以通过Map
来回转换。也就是JSON
中的字段比对应的JavaBean
中的字段多,可以在JavaBean
中使用一个Map
字段来接收多余的JSON
字段。
3.9.1. @JsonAnyGetter
- 用在非静态方法上,没有参数,方法名随意(可以直接写在
Getter
方法上)。 - 方法返回值必须是
Map
类型。 - 在一个实体类中仅仅用在一个方法上。
- 序列化的时候
JSON
字段的key
就是返回Map
的key
,value
就是Map
的value
。
3.9.2. @JsonAnySetter
- 用在非静态方法上,注解的方法必须有两个参数,第一个是
JSON
字段中的key
,第二个是value
,方法名随意(注意这个方法不是Setter
方法)。 - 也可以用在
Map
对象属性上面,建议用在Map
对象属性上面。 - 反序列化的时候将对应不上的字段全部放到
Map
里面。
3.9.3. 示例代码
import com.fasterxml.jackson.annotation.*;
import lombok.Data;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/*** @author wangbo* @date 2019/11/16 10:47*/
@Data
public class Student {private Long id;private String name;private Integer age;//自定义字段private Map<String, Object> other = new HashMap();@JsonAnyGetterpublic Map<String, Object> getOther() {return other;}@JsonAnySetterpublic void setOther(String key, Object value) {this.other.put(key, value);}
}
import com.fasterxml.jackson.databind.ObjectMapper;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** @author wangbo* @date 2019/11/16 16:54*/
public class Test {public static void main(String[] args) throws IOException {Map<String,Object> map = new HashMap<>();map.put("id", 1L);map.put("name", "菲菲");map.put("age", 20);map.put("score", 90);map.put("sex", "女");ObjectMapper mapper = new ObjectMapper();String s = mapper.writeValueAsString(map);//序列化System.out.println(s);//{"score":90,"sex":"女","name":"菲菲","id":1,"age":20}Student student = mapper.readValue(s, Student.class);//反序列化System.out.println(student);//Student(id=1, name=菲菲, age=20, other={score=90, sex=女})String s1 = mapper.writeValueAsString(student);//序列化System.out.println(s1);//{"id":1,"name":"菲菲","age":20,"score":90,"sex":"女"}}
}
3.10. @JsonNaming
该注解放在类上。序列化的时候该注解可将驼峰命名的字段名转换为下划线分隔的小写字母命名方式的key
。反序列化的时候可以将下划线分隔的小写字母key
转换为驼峰命名的字段名。
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
代码示例:
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
/*** @author wangbo* @date 2019/11/16 10:47*/
@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Student {private Long appId;private String nickName;private Integer nowAge;
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
/*** @author wangbo* @date 2019/11/16 16:54*/
public class Test {public static void main(String[] args) throws IOException {Student student = new Student();student.setAppId(1L);student.setNickName("zhangsan");student.setNowAge(20);ObjectMapper mapper = new ObjectMapper();String s = mapper.writeValueAsString(student);//序列化System.out.println(s);//{"app_id":1,"nick_name":"zhangsan","now_age":20}Student student1 = mapper.readValue(s, Student.class);//反序列化System.out.println(student1);//Student(appId=1, nickName=zhangsan, nowAge=20)}
}
4. Jackson 配置
这里有三个方法,configure
方法接受配置名和要设置的值,Jackson 2.5
版本新加的enable
和disable
方法则直接启用和禁用相应属性,推荐使用后面两个方法。
// 美化输出
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// 强制JSON空字符串("")转换为null对象值
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);// 允许序列化空的POJO类(否则会抛出异常)
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 把java.util.Date, Calendar输出为数字(时间戳)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 在遇到未知属性的时候不抛出异常
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);// 在JSON中允许C/C++ 样式的注释(非标准,默认禁用)
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
// 允许没有引号的字段名(非标准)
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 允许单引号(非标准)
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 强制转义非ASCII字符
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
// 将内容包裹为一个JSON属性,属性名由@JsonRootName注解指定
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
使用 Jackson 操作 JSON 数据相关推荐
- 使用jackson解析json数据详讲
Java操作json数据 json 简介 概念 JSON:JavaScript Object Notations,即javaScript的对象表示法,是javaScript语法的一个子集,相当于jav ...
- Jquery重新学习之五[操作JSON数据]
Jquery操作Json格式的数据在我们平时的项目中也经常运用:最近看Jquery权威指南中就有一章节是对这方面的整理总结:最后通过一个Asp.net结合一般处理程序ashx的实例,基本上能满足项目中 ...
- js之操作JSON数据
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式.同时,JSON是 JavaScript 原生格式,这意 ...
- js操作json数据的一些感受
最近在搞 json 数据传输,直接用的 javascript 对数据进行操作.json数据为: var json ={ "global":"200", &quo ...
- android jackson 解析json字符串,一文搞定Jackson解析JSON数据
Json解析-Jackson使用教程 日常求赞,感谢老板. 一.JSON解析 我这里指的解析是:JSON和JavaObject之间的序列化和反序列化. 如果你的项目进行了前后端分离,那你一定使用过JS ...
- JS操作JSON数据交换
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. 采用完全独立于语言的文本格式,易于人阅读和编写.同时也易于机器解析和生成. JSON采用完全独立于语言的 ...
- Jackson解析JSON数据教程
Json解析-Jackson使用教程 日常求赞,感谢老板. 欢迎关注公众号:其实是白羊.干货持续更新中- 一.JSON解析 我这里指的解析是:JSON和JavaObject之间的序列化和反序列化. 如 ...
- 易语言 精易模块 操作json数据
有勇气的牛排 官方地址: https://www.920vip.net/ 1 单一键值对 json 原始数据 {"status":200,"results":& ...
- python操作json数据_Python操作JSON数据代码示例
#!/usr/bin/env python import json import os def json_test(): return_dic = {} json_data = { 'appid':' ...
最新文章
- HTML5之内嵌框架
- 小站教育携手神策数据,数据赋能留学语言培训教育的智能化探索
- 十大经典排序算法之插入排序及其二分优化
- ASP.NET Core Web 应用程序系列(二)- 在ASP.NET Core中使用Autofac替换自带DI进行批量依赖注入(MVC当中应用)...
- 使用 C++的第三方库 jsoncpp的步骤以及出现的问题
- Hive常用的操作命令
- Canvas X Draw for Mac7.0.2英文版
- Multi thread: std::promise
- Go sync.Map 看一看
- AOP 实现请求参数打印
- 在Ubuntu系统上安装minidwep-gtk软件
- 杰理之ifi_camera跑sfc的启动时间【篇】
- Cesium隐藏太阳、月亮、大气【图说GIS】
- 大龄程序猿的出路在哪里
- matlab中饼图绘制程序,MATLAB中绘制二维饼图的函数是(? ? ) (1.5分)
- ip中继对接_【关关闯关】AR和Cisco 2900通过SIP IP中继对接案例
- 机器学习 | 一个基于机器学习的简单小实践:波斯顿房价预测分析
- 史上最全的 pom.xml 文件详解
- IC卡解密从零开始学1 (也许会有2) 解密工具V2 V3大放送 By:lookyour
- 安卓动态壁纸源码--可直接进入壁纸设置界面+玫瑰花瓣飘落效果+可分享到微博、微信功能--无任何删减2