最近和学长做项目的时候用到了Gson,虽然之前学过,但是很久不碰又模糊了,现在给自己做做总结
前言
Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用于为序列化Java对象对JSON对象,或者反序列化JSON字符串为Java对象。Json是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,广泛应用于各种数据的交互中,尤其是服务器和客户端的交互
基本概念
- Serialization:序列化,使Java对象到Json字符串的过程
- Deserialization:反序列化,字符串转成Java对象
- Json数据中的jsonElement有下面四种类型:
- jsonPrimitive – 例如一个字符串或者整型
- jsonObject — 一个一JsonElement名字(类型为String)作为索引的集合。也就是说可以把JsonObject看做值为JsonElement的键值对集合。
- jsonArray – JsonElement的集合。注意数组的元素可以是四种类型中的任意一种
- JsonNull – 值为null
Gson目标
- 提供易于使用的机制toString和构造函数(工厂方法)将Java转换成Json,反之亦然
- 允许将先前存在的不可修改对象转换为Json或从Json转换
- 允许对象的自定义表示
- 支持 任意复杂的对象
- 生成紧凑可读的Json输出
Gson处理对象的几个重要点
- 推荐把成员变量都声明成private
- 如果某个字段被transient这个java关键词修饰,就不会被序列化或者反序列化
- 下面的实现方式能够正确的处理null
1.当序列化的时候,如果对象的某个字段为null,是不会输出到Json字符串中的
2.当反序列化的时候,某个字段在Json字符串中找不到对应的值,就会被赋值为null - 如果一个字段是synthetic的,它会被忽视,也即是不应该被序列化或者反序列化
- 内部类(或者匿名类)或者局部类的某个字段和外部类的某个字段一样的话,就会被忽视,不会被序列化或者反序列化
Gson中的一些注解
1 @SerializedName
该注解能指定该字段在Json中对应的字段名称
public class Box {
@SerializedName("w")
private int width;
@SerializedName("h")
private int height;
@SerializedName("d")
private int depth;
// Methods removed for brevity
}
也就是说{“w”:10,”h”:20,”d”:30} 这个JSON 字符串能够被解析到上面的width,height和depth字段中。
2 @Expose注解
该注解能够指定该字段是否能够序列化或者反序列化,模式都是支持
public class Account {
@Expose(deserialize = false)
private String accountNumber;
@Expose
private String iban;
@Expose(serialize = false)
private String owner;
@Expose(serialize = false, deserialize = false)
private String address;
private String pin;
}
需要注意的通过builder.excludeFieldsWithoutExposeAnnotation()
方法使该注解生效
final GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
final Gson gson = builder.create();
3 @Since和@Until注解
Since代表“自从”,Until代表“一直到”。它们都是针对该字段生效的版本。比如说@Since(1.2)代表从版本1.2之后才生效,@Until(0.9)代表着在0.9版本之前都是生效的。
public class SoccerPlayer {
private String name;
@Since(1.2)
private int shirtNumber;
@Until(0.9)
private String country;
private String teamName;
// Methods removed for brevity
}
也就是说我们利用方法builder.setVersion(1.0)定义版本1.0,如下:
final GsonBuilder builder = new GsonBuilder();
builder.setVersion(1.0);
final Gson gson = builder.create();
final SoccerPlayer account = new SoccerPlayer();
account.setName("Albert Attard");
account.setShirtNumber(10); // Since version 1.2
account.setTeamName("Zejtun Corinthians");
account.setCountry("Malta"); // Until version 0.9
final String json = gson.toJson(account);
System.out.printf("Serialised (version 1.0)%n %s%n", json);
由于shirtNumber和country作用版本分别是1.2之后和0.9之前,所以在这里不会得到序列化,所以输出的结果:
Serialised (version 1.0)
{"name":"Albert Attard","teamName":"Zejtun Corinthians"}
Gson泛型
{"code":"0","message":"success","data":[]}
我们真正需要的data所包含的数据,而code只使用一次,message则几乎不用。如果Gson不支持泛型或不知道Gson支持泛型的同学一定会这么定义POJO。
public class UserResponse {
public int code;
public String message;
public User data;
}
当其它接口的时候又重新定义一个XXResponse将data的类型改成XX,很明显code,和message被重复定义了多次,通过泛型的话我们可以将code和message字段抽取到一个Result的类中,这样我们只需要编写data字段所对应的POJO即可,更专注于我们的业务逻辑。如:
public class Result<T> {
public int code;
public String message;
public T data;
}
那么对于data字段是User时则可以写为 Result ,当是个列表的时候为 Result
Gson流式序列化
手动方式
手动的方式就是使用stream包下的JsonReader类来手动实现反序列化,和Android中使用pull解析XML是比较类似的。
String json = "{\"name\":\"怪盗kidou\",\"age\":\"24\"}";
User user = new User();
JsonReader reader = new JsonReader(new StringReader(json));
reader.beginObject(); // throws IOException
while (reader.hasNext()) {
String s = reader.nextName();
switch (s) {
case "name":
user.name = reader.nextString();
break;
case "age":
user.age = reader.nextInt(); //自动转换
break;
case "email":
user.email = reader.nextString();
break;
}
}
reader.endObject(); // throws IOException
System.out.println(user.name); // 怪盗kidou
System.out.println(user.age); // 24
System.out.println(user.email); // ikidou@example.com
其实自动方式最终都是通过JsonReader来实现的,如果第一个参数是String类型,那么Gson会创建一个StringReader转换成流操作。
自动方式
可以看出用红框选中的部分就是我们要找的东西。
提示:PrintStream(System.out) 、StringBuilder、StringBuffer和*Writer都实现了Appendable接口。
两种方法的比较
Gson gson = new Gson();
User user = new User("怪盗kidou",24,"ikidou@example.com");
gson.toJson(user,System.out); // 写到控制台
JsonWriter writer = new JsonWriter(new OutputStreamWriter(System.out));
writer.beginObject() // throws IOException
.name("name").value("怪盗kidou")
.name("age").value(24)
.name("email").nullValue() //演示null
.endObject(); // throws IOException
writer.flush(); // throws IOException
//{"name":"怪盗kidou","age":24,"email":null}
提示:除了beginObject、endObject还有beginArray和endArray,两者可以相互嵌套,注意配对即可。beginArray后不可以调用name方法,同样beginObject后在调用value之前必须要调用name方法。
Gson序列化
英文Serialize和format都对应序列化,这是一个Java对象到Json字符串的过程。
public class Book {
private String[] authors;
private String isbn10;
private String isbn13;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
{
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"isbn-10": "032133678X",
"isbn-13": "978-0321336781",
"authors": [
"Joshua Bloch",
"Neal Gafter"
]
}
你肯定能发现JSON数据中出现了isbn-10和isbn-13, 我们怎么把字段数据isbn10和isbn13转化为JSON数据需要的isbn-10和isbn-13,Gson当然为我们提供了对应的解决方案
1 序列化方案1
采用上面提到的@SerializedName注解
public class Book {
private String[] authors;
@SerializedName("isbn-10")
private String isbn10;
@SerializedName("isbn-13")
private String isbn13;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
Book book = new Book();
book.setAuthor(new String[]{"Jshua Bioch","Neal Gafter"});
book.setIsbn10("032133678X");
book.setIsbn13("978-0321336781");
book.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
Gson gson = new Gson();
String json = gson.toJson(book);
Log.d("json",json);
结果:
{"author":["Jshua Bioch","Neal Gafter"],"isbn-10":"032133678X","isbn-13":"978-0321336781","title":"Java Puzzlers: Traps, Pitfalls, and Corner Cases"}
2 序列化方案2
利用JsonSerializer类
public class BookSerialiser implements JsonSerializer {
@Override
public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("title", book.getTitle());
jsonObject.addProperty("isbn-10", book.getIsbn10());
jsonObject.addProperty("isbn-13", book.getIsbn13());
final JsonArray jsonAuthorsArray = new JsonArray();
for (final String author : book.getAuthors()) {
final JsonPrimitive jsonAuthor = new JsonPrimitive(author);
jsonAuthorsArray.add(jsonAuthor);
}
jsonObject.add("authors", jsonAuthorsArray);
return jsonObject;
}
}
下面对序列化过程进行大致的分析:
- JsonSerializer是一个接口,我们需要提供自己的实现,来满足自己的序列化要求。
public interface JsonSerializer<T> {
/**
*Gson 会在解析指定类型T数据的时候触发当前回调方法进行序列化
*
* @param T 需要转化为Json数据的类型,对应上面的Book
* @return 返回T指定的类对应JsonElement
*/jsonObject.addProperty...
jsonObject.add...
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
}
- 首先在上面的代码中,我们需要创建的是一个JsonElement对象,这里对应的Book是一个对象,所以创建一个JsonObject类型。
final JsonObject jsonObject = new JsonObject();
- 然后我们将相应字段里面的数据填充到jsonObject里面
jsonObject.addProperty...
jsonObject.add...
所以最后返回还是一个JsonElement类型,这里对应的是JsonObject。完成了javaBean->JSON数据的转化
同样需要配置:
// Configure GSON
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final Book javaPuzzlers = new Book();
javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
javaPuzzlers.setIsbn10("032133678X");
javaPuzzlers.setIsbn13("978-0321336781");
javaPuzzlers.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });
// Format to JSON
final String json = gson.toJson(javaPuzzlers);
System.out.println(json);
我们使用GsonBuilder可以导出null值,格式化输出,日期时间
`Gson gson = new GsonBuilder()
//序列化null
.serializeNulls()
// 设置日期时间格式,另有2个重载方法
// 在序列化和反序化时均生效
.setDateFormat(“yyyy-MM-dd”)
// 禁此序列化内部类
.disableInnerClassSerialization()
//生成不可执行的Json(多了 )]}’ 这4个字符)
.generateNonExecutableJson()
//禁止转义html标签
.disableHtmlEscaping()
//格式化输出
.setPrettyPrinting()
.create();
`
这里对应的是
gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser())
方法进行JsonSerializer的配置。在上面例子中,通过调用gsonBuilder.setPrettyPrinting();
方法还告诉了Gson对生成的JSON对象进行格式化
Gson反序列化
英文parse和deserialise对应反序列化,这是一个字符串转换成Java对象的过程,
{
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"isbn-10": "032133678X",
"isbn-13": "978-0321336781",
"authors": [
"Joshua Bloch",
"Neal Gafter"
]
}
转换成对应的Book实体类
1 反序列化方案1
利用@SerializedName注解
public class Book {
private String[] authors;
@SerializedName("isbn-10")
private String isbn10;
@SerializedName(value = "isbn-13", alternate = {"isbn13","isbn.13"})
private String isbn13;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
可以看到这里我们在@SerializedName注解使用了一个value,alternate字段,value也就是默认的字段,对序列化和反序列化都有效,alternate只有反序列化才有效果。也就是说一般服务器给我们JSON数据的时候可能同样的一个图片,表示“image,img,icom”等,我们利用alternate字段就能解决这个问题,全部转化为我们实体类中的图片字段
2 反序列化方案2
我们再序列化的时候使用的是JsonSerialize,这里对应使用JsonDeserializer
我们将解析到的json数据传递给Book的seeter方法即可
public class BookDeserializer implements JsonDeserializer<Book> {
@Override
public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final JsonElement jsonTitle = jsonObject.get("title");
final String title = jsonTitle.getAsString();
final String isbn10 = jsonObject.get("isbn-10").getAsString();
final String isbn13 = jsonObject.get("isbn-13").getAsString();
final JsonArray jsonAuthorsArray = jsonObject.get("authors").getAsJsonArray();
final String[] authors = new String[jsonAuthorsArray.size()];
for (int i = 0; i < authors.length; i++) {
final JsonElement jsonAuthor = jsonAuthorsArray.get(i);
authors[i] = jsonAuthor.getAsString();
}
final Book book = new Book();
book.setTitle(title);
book.setIsbn10(isbn10);
book.setIsbn13(isbn13);
book.setAuthors(authors);
return book;
}
}
分析:
- 因为我们可以发现上面的JSON数据是一个{}大括号包围的,也就意味着这是一个Json对象。所以首先我们通过
final JsonObject jsonObject = json.getAsJsonObject();将我们的JsonElement转化为JsonObject - 通过jsonObject.get(“xxx”).getAsString()的形式获取相应String的值
- 通过jsonObject.get(“xx”).getAsJsonArray();获取相应的json数组,并遍历出其中的相应字段值
- 通过setter方法,将获取到的值设置给Book类。
- 最终返回的是 Book的对象实例。完成了JSON->javaBean的转化
- 同样需要配置
- 关于从本地流中读取Json数据可以使用 InputStreamReader完成
// Configure Gson
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer());
Gson gson = gsonBuilder.create();
// The JSON data
try(Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part1/sample.json"), "UTF-8")){
// Parse JSON to Java
Book book = gson.fromJson(reader, Book.class);
System.out.println(book);
}
TypeAdapter介绍
上面提到的JsonSerializer
和JsonDeserializer解析的时候都利用到了一个中间件-JsonElement,比如下方的序列化过程。可以看到我们在把Java对象转化为JSON字符串的时候都会用到这个中间件JsonElement
而TypeAdapter的使用正是去掉了这个中间层,直接用流来解析数据,极大程度上提高了解析效率
TypeAdapter作为一个抽象类提供两个抽象方法。分别是write()和read()方法,也对应着序列化和反序列化。
当我们为User.class 注册了 TypeAdapter之后,只要是操作User.class 那些之前介绍的@SerializedName 、FieldNamingStrategy、Since、Until、Expos通通都黯然失色,失去了效果,只会调用我们实现的UserTypeAdapter.write(JsonWriter, User) 方法,我想怎么写就怎么写。
实例
package com.javacreed.examples.gson.part1;
public class Book {
private String[] authors;
private String isbn;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
package com.javacreed.examples.gson.part1;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class BookTypeAdapter extends TypeAdapter {
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
in.endObject();
return book;
}
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginObject();
out.name("isbn").value(book.getIsbn());
out.name("title").value(book.getTitle());
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
out.endObject();
}
}
同样,这里设置TypeAdapter之后还是需要配置,可以注意到的是gsonBuilder.registerTypeAdapter(xxx)方法进行注册在我们之前的JsonSerializer和JsonDeserializer中也有使用:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
final Gson gson = gsonBuilder.create();
1 TypeAdapter中的write方法
write()方法中会传入JsonWriter,和需要被序列化的Book对象的实例,采用和PrintStream类似的方式写入到JsonWriter中
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginObject();
out.name("isbn").value(book.getIsbn());
out.name("title").value(book.getTitle());
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
out.endObject();
}
- out.beginObject()产生(如果我们希望的是一个数组对象,对应的使用beginArray())
out.name("isbn").value(book.getIsbn()); out.name("title").value(book.getTitle());
分别获取book中isbn和title字段并且设置给json对象中的lisbn和title。也就是说上面这段代码,会在json对象中产生
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
- 通过上面整个代码会产生JSON对象:
{
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"authors": "Joshua Bloch;Neal Gafter"
}
- 注意,在最后需要调用out.endObject()方法,否则会报出JsonSyntaxException
2 TypeAdapter中的read方法
read()方法将会传入一个JsonReader对象实例并返回反序列化的对象。
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
in.endObject();
return book;
}
- 同样是通过in.beginObject和in.endObject对应解析
- 通过
while (in.hasNext()) {
来完成每个JsonElement的遍历,并且通过switch….case的方法获取Json对象中的键值对。
switch (in.nextName()) {
}
}
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
完整代码:
package com.javacreed.examples.gson.part1;
import java.io.IOException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Main {
public static void main(final String[] args) throws IOException {
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final Book book = new Book();
book.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });
book.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
book.setIsbn("978-0321336781");
final String json = gson.toJson(book);
System.out.println("Serialised");
System.out.println(json);
final Book parsedBook = gson.fromJson(json, Book.class);
System.out.println("\nDeserialised");
System.out.println(parsedBook);
}
}
TypeAdapter处理简洁的Json数据
["978-0321336781","Java Puzzlers: Traps, Pitfalls, and Corner Cases","Joshua Bloch","Neal Gafter"]
我们需要按照一定的顺序来解析这个数据
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginArray();
out.value(book.getIsbn());
out.value(book.getTitle());
for (final String author : book.getAuthors()) {
out.value(author);
}
out.endArray();
}
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginArray();
book.setIsbn(in.nextString());
book.setTitle(in.nextString());
final List authors = new ArrayList<>();
while (in.hasNext()) {
authors.add(in.nextString());
}
book.setAuthors(authors.toArray(new String[authors.size()]));
in.endArray();
return book;
}
TypeAdapter解析内置对象
比如:
public class Book {
private Author[] authors;
private String isbn;
private String title;
class Author {
private int id;
private String name;
//为了代码简洁,这里移除getter和setter方法等
}
//为了代码简洁,这里移除getter和setter方法等
}
Json对象:
{
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"authors": [
{
"id": 1,
"name": "Joshua Bloch"
},
{
"id": 2,
"name": "Neal Gafter"
}
]
}
下面分别展示write和read方法:
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginObject();
out.name("isbn").value(book.getIsbn());
out.name("title").value(book.getTitle());
out.name("authors").beginArray();
for (final Author author : book.getAuthors()) {
out.beginObject();
out.name("id").value(author.getId());
out.name("name").value(author.getName());
out.endObject();
}
out.endArray();
out.endObject();
}
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
in.beginArray();
final List authors = new ArrayList<>();
while (in.hasNext()) {
in.beginObject();
final Author author = new Author();
while (in.hasNext()) {
switch (in.nextName()) {
case "id":
author.setId(in.nextInt());
break;
case "name":
author.setName(in.nextString());
break;
}
}
authors.add(author);
in.endObject();
}
book.setAuthors(authors.toArray(new Author[authors.size()]));
in.endArray();
break;
}
}
in.endObject();
return book;
}
TypeAdapter对JSON和Java对象之间的序列化和反序列化可以通过上面的方法进行操作。其实在解决解析内置对象的序列化和反序列化的时候我们也可以通过JsonDeserializer或者JsonSerializer进行操作,序列化过程如下:
@Override
public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("isbn", book.getIsbn());
jsonObject.addProperty("title", book.getTitle());
final JsonElement jsonAuthros = context.serialize(book.getAuthors());
jsonObject.add("authors", jsonAuthros);
return jsonObject;
}
这里通过JsonSerializationContext提供的context对象直接解析,一定程度上提供了JSON对象序列化(反序列化)的一致性。
GSON性能分析
首先我们提供一个大一点的数据
public class LargeData {
private long[] numbers;
public void create(final int length) {
numbers = new long[length];
for (int i = 0; i < length; i++) {
numbers[i] = i;
}
}
public long[] getNumbers() {
return numbers;
}
}
第一部分 JsonSerializer的直接使用
package com.javacreed.examples.gson.part1;
import java.lang.reflect.Type;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class LargeDataSerialiser implements JsonSerializer<LargeData> {
@Override
public JsonElement serialize(final LargeData data, final Type typeOfSrc, final JsonSerializationContext context) {
final JsonArray jsonNumbers = new JsonArray();
for (final long number : data.getNumbers()) {
jsonNumbers.add(new JsonPrimitive(number));
}
final JsonObject jsonObject = new JsonObject();
jsonObject.add("numbers", jsonNumbers);
return jsonObject;
}
}
上面的代码实现了从java对象>转换>JSON数组的序列化过程。下面代码实现了配置和初始化的过程,被写入文件
package com.javacreed.examples.gson.part1;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Main {
public static void main(final String[] args) throws IOException {
// Configure GSON
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LargeData.class, new LargeDataSerialiser());
gsonBuilder.setPrettyPrinting();//对GSON结果格式化
final Gson gson = gsonBuilder.create();
final LargeData data = new LargeData();
data.create(10485760);
final String json = gson.toJson(data);
final File dir = new File("target/part1");
dir.mkdirs();
try (PrintStream out = new PrintStream(new File(dir, "output.json"), "UTF-8")) {
out.println(json);
}
System.out.println("Done");
}
}
我们来看看内存消耗情况:
上面的LargeData在这里会消耗89M的内存,从java对象转化为JSON字符串的过程将会消耗大概16s的事件并且需要超过1GB的内存。也就是说,序列化1MB的数据我们需要大约11MB的工作空间
在这里我们可以看到,有四个方块代表不同阶段(这里没有使用IO缓冲区,所以用灰色表示)整个过程从java对象(蓝色),然后由LargeDataSerializer类创建的JSONElement对象(红色),然后这些临时的对象又被转化为JSON字符串(绿色)
第二部分 TypeAdapter的直接使用
TypeAdapter相比于上面的方法,并没有使用JSONElment对象,而是直接将Java对象转化为JSON对象
public class LargeDataTypeAdapter extends TypeAdapter<LargeData> {
@Override
public LargeData read(final JsonReader in) throws IOException {
throw new UnsupportedOperationException("Coming soon");
}
@Override
public void write(final JsonWriter out, final LargeData data) throws IOException {
out.beginObject();
out.name("numbers");
out.beginArray();
for (final long number : data.getNumbers()) {
out.value(number);
}
out.endArray();
out.endObject();
}
}
public class Main {
public static void main(final String[] args) throws IOException {
// Configure GSON
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LargeData.class, new LargeDataTypeAdapter());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final LargeData data = new LargeData();
data.create(10485760);
final String json = gson.toJson(data);
final File dir = new File("target/part2");
dir.mkdirs();
try (PrintStream out = new PrintStream(new File(dir, "output.json"), "UTF-8")) {
out.println(json);
}
System.out.println("Done");
}
}
上面代码完成了从java对象>传化>JSON字符串并最终写入文件的过程。
和最初的那个方法一样,这里的LargeData对象将会需要89MB的内存,从java对象转化为JSON字符串的过程需要消耗4s的时间,大概650MB的内存。也就是说,序列化 1MB的数据,大概需要7。5MB的内存空间。相比于之前的第一种JsonSerializer方法,这里减少了接近一半的内存消耗
第三部分 TypeAdapter的流式处理
public class Main {
public static void main(final String[] args) throws IOException {
// Configure GSON
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LargeData.class, new LargeDataTypeAdapter());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final LargeData data = new LargeData();
data.create(10485760);
final File dir = new File("target/part3");
dir.mkdirs();
try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir,
"output.json")), "UTF-8"))) {
gson.toJson(data, out);
}
System.out.println("Done");
}
}
可以看到的是同样的最初产生的数据是89MB,序列化过程将java对象转化为JSON字符串花了大概三秒钟的时间,消耗大概160MB的内存。也就是说序列化1MB的数据我们需要大概2MB的内存空间。相比于之前的两种方法,有了很大的改进。
这个方法同样的是使用了两个阶段。不过在上面一个示例中的绿色方块部分在这里没有使用,这里直接完成了java对象到IO 缓冲区的转化并写入文件。
虽然这里并不是Gson的关系,但是我们使用Gson的方法极大的减少了内存消耗,所以说在使用开源库的时候,能够正确高效的使用API也显得尤为重要。
第四部分 JsonSerializer的流式处理
public class Main {
public static void main(final String[] args) throws IOException {
// Configure GSON
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LargeData.class, new LargeDataSerialiser());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final LargeData data = new LargeData();
data.create(10485760);
final File dir = new File("target/part4");
dir.mkdirs();
try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir,
"output.json")), "UTF-8"))) {
gson.toJson(data, out);
}
System.out.println("Done");
}
}
这里可以看到三个阶段完成的工作消耗了11s的时间,730MB的内存空间。也就是说1:8的比例。可以相比上面的例子,知道这里使用JSONSerializer产生了JSONElement对象消耗了很多的内存。
结论
在上面的分析过程中,我们采用了GSON的两种不同的方然完成了序列化一个大数据的过程,并且比较了不同的方法之间的差异。上面的第三种方法(TypeAdapter的流式处理)被论证为最合适的,消耗最少内存的一种方法。
Gson主要分成两部分,一个就是数据拆解,一个是数据封装。
本文来源: