Java 序列化详解

XML和JSON是两种经常在网络使用的数据表示格式,这里我们介绍如何使用Java读写XML和JSON。 

一、XML 概述

1、XML简介

我们都知道对象是不能在网络中直接传输的,不过还有补救的办法。

XML(Extensible Markup Language)可扩展标记语言,本身就被设计用来存储数据,任何一个对象都可以用XML来描述。XML是可以作为对象信息的载体在网络中传输,因为它是文本形式的。

例如,一个描述书籍的XML文档可能如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>

XML有几个特点:一是纯文本,默认使用UTF-8编码,二是可嵌套,适合表示结构化数据。如果把XML内容存为文件,那么它就是一个XML文件,例如book.xml。此外,XML内容经常通过网络作为消息传输。

2、XML的结构

XML有固定的结构,首行必定是<?xml version="1.0"?>,可以加上可选的编码。紧接着,如果以类似<!DOCTYPE note SYSTEM "book.dtd">声明的是文档定义类型(DTD:Document Type Definition),DTD是可选的。接下来是XML的文档内容,一个XML文档有且仅有一个根元素,根元素可以包含任意个子元素,元素可以包含属性,例如,<isbn lang="CN">1234567</isbn>包含一个属性lang="CN",且元素必须正确嵌套。如果是空元素,可以用<tag/>表示。

由于使用了<、>以及引号等标识符,如果内容出现了特殊符号,需要使用&???;表示转义。

例如,Java<tm>必须写成:

<name>Java&lt;tm&gt;</name>

常见的特殊字符如下:

字符表示
<&lt;
>&gt;
&&amp;
"&quot;
'&apos;

格式正确的XML(Well Formed)是指XML的格式是正确的,可以被解析器正常读取。而合法的XML是指,不但XML格式正确,而且它的数据结构可以被DTD或者XSD验证。

DTD文档可以指定一系列规则,例如:

  • 根元素必须是book
  • book元素必须包含nameauthor等指定元素
  • isbn元素必须包含属性lang
  • ...

如何验证XML文件的正确性呢?最简单的方式是通过浏览器验证。可以直接把XML文件拖拽到浏览器窗口,如果格式错误,浏览器会报错。

和结构类似的HTML不同,浏览器对HTML有一定的“容错性”,缺少关闭标签也可以被解析,但XML要求严格的格式,任何没有正确嵌套的标签都会导致错误。

XML是一个技术体系,除了我们经常用到的XML文档本身外,XML还支持:

  • DTD和XSD:验证XML结构和数据是否有效;
  • Namespace:XML节点和属性的名字空间;
  • XSLT:把XML转化为另一种文本;
  • XPath:一种XML节点查询语言;
  • ...

实际上,XML的这些相关技术实现起来非常复杂,在实际应用中很少用到,通常了解一下就可以了。

3、DOM

因为XML是一种树形结构的文档,它有两种标准的解析API:

  • DOM:一次性读取XML,并在内存中表示为树形结构。
  • SAX:以流的形式读取XML,使用事件回调。

我们先来看如何使用DOM来读取XML。

DOM是Document Object Model的缩写,DOM模型就是把XML结构作为一个树形结构处理,从根节点开始,每个节点都可以包含任意个子节点。

我们以下面的XML为例:

<?xml version="1.0" encoding="UTF-8" ?>
<book id="1">
    <name>Java核心技术</name>
    <author>Cay S. Horstmann</author>
    <isbn lang="CN">1234567</isbn>
    <tags>
        <tag>Java</tag>
        <tag>Network</tag>
    </tags>
    <pubDate/>
</book>

如果解析为DOM结构,它大概长这样:

注意到最顶层的document代表XML文档,它是真正的“根”,而<book>虽然是根元素,但它是document的一个子节点。

Java提供了DOM API来解析XML,它使用下面的对象来表示XML的内容:

  • Document:代表整个XML文档;
  • Element:代表一个XML元素;
  • Attribute:代表一个元素的某个属性。

使用DOM API解析一个XML文档的代码如下:

InputStream input = Main.class.getResourceAsStream("/book.xml");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(input);

DocumentBuilder.parse()用于解析一个XML,它可以接收InputStream,File或者URL,如果解析无误,我们将获得一个Document对象,这个对象代表了整个XML文档的树形结构,需要遍历以便读取指定元素的值:

void printNode(Node n, int indent) {
    for (int i = 0; i < indent; i++) {
        System.out.print(' ');
    }
    switch (n.getNodeType()) {
    case Node.DOCUMENT_NODE: // Document节点
        System.out.println("Document: " + n.getNodeName());
        break;
    case Node.ELEMENT_NODE: // 元素节点
        System.out.println("Element: " + n.getNodeName());
        break;
    case Node.TEXT_NODE: // 文本
        System.out.println("Text: " + n.getNodeName() + " = " + n.getNodeValue());
        break;
    case Node.ATTRIBUTE_NODE: // 属性
        System.out.println("Attr: " + n.getNodeName() + " = " + n.getNodeValue());
        break;
    default: // 其他
        System.out.println("NodeType: " + n.getNodeType() + ", NodeName: " + n.getNodeName());
    }
    for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
        printNode(child, indent + 1);
    }
}

解析结构如下:

Document: #document
 Element: book
  Text: #text = 
    
  Element: name
   Text: #text = Java核心技术
  Text: #text = 
    
  Element: author
   Text: #text = Cay S. Horstmann
  Text: #text = 
  ...

对于DOM API解析出来的结构,我们从根节点Document出发,可以遍历所有子节点,获取所有元素、属性、文本数据,还可以包括注释,这些节点被统称为Node,每个Node都有自己的Type,根据Type来区分一个Node到底是元素,还是属性,还是文本,等等。

使用DOM API时,如果要读取某个元素的文本,需要访问它的Text类型的子节点,所以使用起来还是比较繁琐的。

4、SAX

使用DOM解析XML的优点是用起来省事,但它的主要缺点是内存占用太大。

另一种解析XML的方式是SAX。SAX是Simple API for XML的缩写,它是一种基于流的解析方式,边读取XML边解析,并以事件回调的方式让调用者获取数据。因为是一边读一边解析,所以无论XML有多大,占用的内存都很小。

SAX解析会触发一系列事件:

  • startDocument:开始读取XML文档;
  • startElement:读取到了一个元素,例如<book>
  • characters:读取到了字符;
  • endElement:读取到了一个结束的元素,例如</book>
  • endDocument:读取XML文档结束。

如果我们用SAX API解析XML,Java代码如下:

InputStream input = Main.class.getResourceAsStream("/book.xml");
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(input, new MyHandler());

关键代码SAXParser.parse()除了需要传入一个InputStream外,还需要传入一个回调对象,这个对象要继承自DefaultHandler:

class MyHandler extends DefaultHandler {
    public void startDocument() throws SAXException {
        print("start document");
    }

    public void endDocument() throws SAXException {
        print("end document");
    }

    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        print("start element:", localName, qName);
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
        print("end element:", localName, qName);
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        print("characters:", new String(ch, start, length));
    }

    public void error(SAXParseException e) throws SAXException {
        print("error:", e);
    }

    void print(Object... objs) {
        for (Object obj : objs) {
            System.out.print(obj);
            System.out.print(" ");
        }
        System.out.println();
    }
}

运行SAX解析代码,可以打印出下面的结果:

start document
start element:  book
characters:
     
start element:  name
characters: Java核心技术
end element:  name
characters:
     
start element:  author
...

如果要读取<name>节点的文本,我们就必须在解析过程中根据startElement()和endElement()定位当前正在读取的节点,可以使用栈结构保存,每遇到一个startElement()入栈,每遇到一个endElement()出栈,这样,读到characters()时我们才知道当前读取的文本是哪个节点的。可见,使用SAX API仍然比较麻烦。

总结:

  • XML使用嵌套结构的数据表示方式,支持格式验证;
  • XML常用于配置文件、网络消息传输等;
  • Java提供的DOM API可以将XML解析为DOM结构,以Document对象表示;
  • DOM可在内存中完整表示XML数据结构;
  • DOM解析速度慢,内存占用大;
  • SAX是一种流式解析XML的API;
  • SAX通过事件触发,读取速度快,消耗内存少;
  • 调用方必须通过回调方法获得解析过程中的数据;

二、XML 序列化和反序列化

序列化指把对象通过流的方式存储到文件中,反序列化则是指把文件中的字节内容读出来并还原成 Java 对象。

1、XmlSerializer类

XmlSerializer类:将对象序列化到 XML 文档中和从 XML 文档中反序列化对象。XmlSerializer 使您得以控制如何将对象编码到 XML 中。

XML 序列化是将对象的公共属性 (Property)(如Student的Name属性)和字段转换为序列格式(这里是指 XML)以便存储或传输的过程。反序列化则是从 XML 输出中重新创建原始状态的对象。因此,可以将序列化视为将对象的状态保存到流或缓冲区的方法。

例如,ASP.NET 使用 XmlSerializer 类对 XML Web services 消息进行编码。

看一段最简单的Xml序列化代码:

class Program
{
    static void Main(string[] args)
    {
        int i = 10;
        //声明Xml序列化对象实例serializer
        XmlSerializer serializer = new XmlSerializer(typeof(int));
        //执行序列化并将序列化结果输出到控制台
        serializer.Serialize(Console.Out, i);
        Console.Read();
    }
}

上面代码对int i进行了序列化,并将序列化的结果输出到了控制台,输出结果如下:

<?xml version="1.0" encoding="gb2312"?>
<int>10</int>

可以将上述序列化的xml进行反序列化,如下代码:

static void Main(string[] args)
{
    using (StringReader rdr = new StringReader(@"<?xml version=""1.0"" encoding=""gb2312""?>
<int>10</int>"))
    {
        //声明序列化对象实例serializer 
        XmlSerializer serializer = new XmlSerializer(typeof(int));
        //反序列化,并将反序列化结果值赋给变量i
        int i = (int)serializer.Deserialize(rdr);
        //输出反序列化结果
        Console.WriteLine("i = " + i);
        Console.Read();
    }
}

以上代码用最简单的方式说明了xml序列化和反序列化的过程,.Net系统类库为我们做了大量的工作,序列化和反序列化都非常简单。但是在现实中业务需求往往比较复杂,不可能只简单的序列化一个int变量,显示中我们需要对复杂类型进行可控制的序列化。

[XmlRoot("cat")]
//要求不序列化Speed属性
[XmlIgnore]
[XmlAttribute]
[XmlElement]

可以使用XmlElement指定属性序列化为子节点(默认情况会序列化为子节点);或者使用XmlAttribute特性制定属性序列化为Xml节点的属性;还可以通过XmlIgnore特性修饰要求序列化程序不序列化修饰属性。

2、对象序列化和反序列化

首先我们先定义实体类:

  public class People
    {
        //XmlAttribute:指定XmlSerializer将该类成员序列化为XML属性
        [XmlAttribute]
        public string Name { get; set; }
        [XmlAttribute]
        public int Age { get; set; }
    }

    [XmlRoot]
    public class Student : People
    {
  
        
//定义SClass属性的序列化为Student节点的属性

        [XmlElement]
        public string SClass { get; set; }
        [XmlElement]
        public int Number { get; set; }
    }

第一步:将实体类序列化为XML文档,代码如下:

    Student stu = new Student() { Age = 12, Number = 23, Name = "张三", SClass = "高一(2)班" };
    XmlSerializer ser = new XmlSerializer(typeof(Student));
    StringWriter writer = new StringWriter();
    ser.Serialize(writer,stu);
    MessageBox.Show(writer.ToString());

在弹出框,出现的结果是:

这样,我们就序列化成功了。

第二步:现在我们来进行反序列化测试: 

    //将Xml反序列为Student对象
    StringReader reader = new StringReader(writer.ToString());
    //Deserialize反序列化指定TextReader包含的Xml文档,当然,不仅仅可以是TextReader,还可以是Stream等等,具体看起构造函数参数即可知道
    Student stu2= (Student)ser.Deserialize(reader);

我们用上面得到XML数据进行反序列化测试。查看运行结果,ok!

3、列表序列化和反序列化

和上面一样,序列化学生列表(People类和Student类和上面代码一样)。

    List<Student> stuList = new List<Student>();
    stuList.Add(new Student() { Age = 10, Number = 1, Name = "Tom", SClass = "Class One" });
    stuList.Add(new Student() { Age = 11, Number = 2, Name = "Jay", SClass = "Class Two" });
    stuList.Add(new Student() { Age = 12, Number = 3, Name = "Pet", SClass = "Class One" });
    stuList.Add(new Student() { Age = 13, Number = 4, Name = "May", SClass = "Class Three" });
    stuList.Add(new Student() { Age = 14, Number = 5, Name = "Soy", SClass = "Class Two" });


     //序列化
    XmlSerializer ser = new XmlSerializer(typeof(List<Student>));
    StringWriter writer = new StringWriter();
    //将学生列表序列化为Xml数据
    ser.Serialize(writer, stuList);

    //反序列化
    //要先将构造StringReader,作为Deserialize()的初始化参数
    StringReader reader = new StringReader(writer.ToString());
    //别忘了从Object到List<Student>,否则会报错。。
    List<Student> stuList2 = (List<Student>)ser.Deserialize(reader);

运行结果是(注意:根是ArrayOfStudent不是Student了):

4、字典序列化和反序列化 

在XmlSerializer中,不支持Dirctionary<>类型的对象,所以在序列化这种最常见类型的时候,只能按照它的格式先创建一个可以序列化的类型,然后,将数据存储在该可序列化的类型中,然后再进行序列化即可。

    Dictionary<string, int> dic = new Dictionary<string, int>();
    dic.Add("第一",1);
    dic.Add("第二", 2);
    dic.Add("第三", 3);
    dic.Add("第四", 4);

    List<DictionaryList> dicList = new List<DictionaryList>();
    foreach (var a in dic)
    {
      DictionaryList dicl = new DictionaryList() {  Name=a.Key, Value=a.Value};
      dicList.Add(dicl);
       
    }
    //序列化
    XmlSerializer ser = new XmlSerializer(typeof(List<DictionaryList>));
    StringWriter writer = new StringWriter();
    //序列化为Xml数据
    ser.Serialize(writer, dicList);
    MessageBox.Show(writer.ToString());

    //反序列化
    StringReader reader = new StringReader(writer.ToString());            
    List<DictionaryList> stuList2 = (List<DictionaryList>)ser.Deserialize(reader);

运行结果是:

5、图片序列化和反序列化 

 补充:XmlArray和XmlArrayItem的使用,用在数组中。

 先构造实体类:

    [XmlRoot("cats")]
    public class CatCollection
    {
        [XmlArray("items"), XmlArrayItem("item")]
        public Cat[] Cats { get; set; }
    }

    //[XmlRoot("cat")] 加不加都无所谓的。
    public class Cat
    {
        //定义Color属性的序列化为cat节点的属性
        [XmlAttribute("color")]
        public string Color { get; set; }

        //要求不序列化Speed属性
        [XmlIgnore]
        public int Speed { get; set; }

        //设置Saying属性序列化为Xml子元素
        [XmlElement("saying")]
        public string Saying { get; set; }
    }

 现在,进行序列化:

   //声明一个猫咪对象
   var cWhite = new Cat { Color = "White", Speed = 10, Saying = "White or black,  so long as the cat can catch mice,  it is a good cat" };
   var cBlack = new Cat { Color = "Black", Speed = 10, Saying = "White or black,  so long as the cat can catch mice,  it is a good cat" };

   CatCollection cc = new CatCollection { Cats = new Cat[] { cWhite, cBlack } };

   //序列化这个对象
   XmlSerializer serializer = new XmlSerializer(typeof(CatCollection));
   StringWriter writer = new StringWriter();
   serializer.Serialize(writer,cc);
   MessageBox.Show(writer.ToString());

运行结果是:

6、XmlSerializer内存泄漏问题

为了提高性能,XML 序列化基础结构将动态生成程序集,以序列化和反序列化指定类型。此基础结构将查找并重复使用这些程序集。此行为仅在使用以下构造函数时发生:

XmlSerializer(Type)   XmlSerializer.XmlSerializer(Type, String)

如果使用任何其他构造函数,则会生成同一程序集的多个版本,且绝不会被卸载,这将导致内存泄漏和性能降低。最简单的解决方案是使用先前提到的两个构造函数的其中一个。

否则,必须在 Hashtable 中缓存程序集,如以下示例中所示。

/// <summary>
    /// 提供xml文档序列化 反序列化
    /// </summary>
    public sealed class EncodeHelper
    {
        /// <summary>
        /// 反序列化XML字符串为指定类型
        /// </summary>
        public static object Deserialize(string Xml, Type ThisType)
        {
            XmlSerializer xmlSerializer = new XmlSerializer(ThisType);
            object result;
            try
            {
                using (StringReader stringReader = new StringReader(Xml))
                {
                    result = xmlSerializer.Deserialize(stringReader);
                }
            }
            catch (Exception innerException)
            {
                bool flag = false;
                if (Xml != null)
                {
                    if (Xml.StartsWith(Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble())))
                    {
                        flag = true;
                    }
                }
                throw new ApplicationException(string.Format("Couldn't parse XML: '{0}'; Contains BOM: {1}; Type: {2}.", 
                Xml, flag, ThisType.FullName), innerException);
            }
            return result;
        }

        /// <summary>
        /// 序列化object对象为XML字符串
        /// </summary>
        public static string Serialize(object ObjectToSerialize)
        {
            string result = null ;
            try
            {
            XmlSerializer xmlSerializer = new XmlSerializer(ObjectToSerialize.GetType());
            
            using (MemoryStream memoryStream = new MemoryStream())
            {
                XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, new UTF8Encoding(false));
                xmlTextWriter.Formatting = Formatting.Indented;
                xmlSerializer.Serialize(xmlTextWriter, ObjectToSerialize);
                xmlTextWriter.Flush();
                xmlTextWriter.Close();
                UTF8Encoding uTF8Encoding = new UTF8Encoding(false, true);
                result= uTF8Encoding.GetString(memoryStream.ToArray());
            }
            }
            catch (Exception innerException)
            {
                throw new ApplicationException("Couldn't Serialize Object:" + ObjectToSerialize.GetType().Name, innerException);
            }
            return result;
        }
    }

也就是说我们在使用XmlSerializer序列化,初始化XmlSerializer对象时最好使用下面两个构造函数否则会引起内存泄漏。

XmlSerializer(Type)  XmlSerializer.XmlSerializer(Type, String)

三、JSON 序列化和反序列化

1、JSON 简介

前面我们讨论了XML这种数据格式。XML的特点是功能全面,但标签繁琐,格式复杂。在Web上使用XML现在越来越少,取而代之的是JSON这种数据结构。

JSON是JavaScript Object Notation的缩写,它去除了所有JavaScript执行代码,只保留JavaScript的对象格式。

一个典型的JSON如下:

{
    "id": 1,
    "name": "Java核心技术",
    "author": {
        "firstName": "Abc",
        "lastName": "Xyz"
    },
    "isbn": "1234567",
    "tags": ["Java", "Network"]
}

JSON作为数据传输的格式,有几个显著的优点:

  • JSON只允许使用UTF-8编码,不存在编码问题;
  • JSON只允许使用双引号作为key,特殊字符用\转义,格式简单;
  • 浏览器内置JSON支持,如果把数据用JSON发送给浏览器,可以用JavaScript直接处理。

因此,JSON适合表示层次结构,因为它格式简单,仅支持以下几种数据类型:

  • 键值对:{"key": value}
  • 数组:[1, 2, 3]
  • 字符串:"abc"
  • 数值(整数和浮点数):12.34
  • 布尔值:true或false
  • 空值:null

浏览器直接支持使用JavaScript对JSON进行读写:

// JSON string to JavaScript object:
jsObj = JSON.parse(jsonStr);

// JavaScript object to JSON string:
jsonStr = JSON.stringify(jsObj);

所以,开发Web应用的时候,使用JSON作为数据传输,在浏览器端非常方便。因为JSON天生适合JavaScript处理,所以,绝大多数REST API都选择JSON作为数据传输格式。

现在问题来了:使用Java如何对JSON进行读写?

2、Jackson 序列化与反序列化

1. Jackson简介

在Java中,针对JSON也有标准的JSR 353 API,但是我们在前面讲XML的时候发现,如果能直接在XML和JavaBean之间互相转换是最好的。类似的,如果能直接在JSON和JavaBean之间转换,那么用起来就简单多了。

常用的用于解析JSON的第三方库有:

  • Jackson
  • Gson
  • Fastjson
  • ...

注意到上一节提到的那个可以解析XML的浓眉大眼的Jackson也可以解析JSON,因此我们只需要引入以下Maven依赖:

  • com.fasterxml.jackson.core:jackson-databind:2.12.0

就可以使用下面的代码解析一个JSON文件:

InputStream input = Main.class.getResourceAsStream("/book.json");
ObjectMapper mapper = new ObjectMapper();
// 反序列化时忽略不存在的JavaBean属性:
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Book book = mapper.readValue(input, Book.class);

核心代码是创建一个ObjectMapper对象。

关闭DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES功能使得解析时如果JavaBean不存在该属性时解析不会报错。

2. 序列化

把JavaBean变为JSON,那就是序列化,如果把JSON解析为JavaBean的过程称为反序列化。

要实现JavaBean到JSON的序列化,只需要一行代码:

String json = mapper.writeValueAsString(book);

要把JSON的某些值解析为特定的Java对象,例如LocalDate,也是完全可以的。

例如:

{
    "name": "Java核心技术",
    "pubDate": "2016-09-01"
}

要解析为:

public class Book {
    public String name;
    public LocalDate pubDate;
}

只需要引入标准的JSR 310关于JavaTime的数据格式定义至Maven:

  • com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.0

然后,在创建ObjectMapper时,注册一个新的JavaTimeModule:

ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());

有些时候,内置的解析规则和扩展的解析规则如果都不满足我们的需求,还可以自定义解析。

举个例子,假设Book类的isbn是一个BigInteger:

public class Book {
	public String name;
	public BigInteger isbn;
}

但JSON数据并不是标准的整形格式:

{
    "name": "Java核心技术",
    "isbn": "978-7-111-54742-6"
}

直接解析,肯定报错。这时,我们需要自定义一个IsbnDeserializer,用于解析含有非数字的字符串:

public class IsbnDeserializer extends JsonDeserializer<BigInteger> {
    public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        // 读取原始的JSON字符串内容:
        String s = p.getValueAsString();
        if (s != null) {
            try {
                return new BigInteger(s.replace("-", ""));
            } catch (NumberFormatException e) {
                throw new JsonParseException(p, s, e);
            }
        }
        return null;
    }
}

然后,在Book类中使用注解标注:

public class Book {
    public String name;
    // 表示反序列化isbn时使用自定义的IsbnDeserializer:
    @JsonDeserialize(using = IsbnDeserializer.class)
    public BigInteger isbn;
}

类似的,自定义序列化时我们需要自定义一个IsbnSerializer,然后在Book类中标注@JsonSerialize(using = ...)即可。

3. 反序列化

在反序列化时,Jackson要求Java类需要一个默认的无参数构造方法,否则,无法直接实例化此类。存在带参数构造方法的类,如果要反序列化,注意再提供一个无参数构造方法。

对于enum字段,Jackson按String类型处理,即:

class Book {
    public DayOfWeek start = MONDAY;
}

序列化为:

{
    "start": "MONDAY"
}

对于record类型,Jackson会自动找出它的带参数构造方法,并根据JSON的key进行匹配,可直接反序列化。对record类型的支持需要版本2.12.0以上。

总结:

JSON是轻量级的数据表示方式,常用于Web应用;

Jackson可以实现JavaBean和JSON之间的转换;

可以通过Module扩展Jackson能处理的数据类型;

可以自定义JsonSerializer和JsonDeserializer来定制序列化和反序列化。

3、Fastjson 序列化与反序列化

1. Fastjson 简介

Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。

Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。

Fastjson 源码地址
Fastjson 中文 Wiki

Fastjson 特性:

  • 提供服务器端、安卓客户端两种解析工具,性能表现较好。
  • 提供了 toJSONString() 和 parseObject() 方法来将 Java 对象与 JSON 相互转换。调用toJSONString方 法即可将对象转换成 JSON 字符串,parseObject 方法则反过来将 JSON 字符串转换成对象。
  • 允许转换预先存在的无法修改的对象(只有class、无源代码)。
  • Java泛型的广泛支持。
  • 允许对象的自定义表示、允许自定义序列化类。
  • 支持任意复杂对象(具有深厚的继承层次和广泛使用的泛型类型)。

下载和使用:

你可以在 maven 中央仓库中直接下载,或者配置 Maven 依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>x.x.x</version>  <!-- 根据需要使用特定版本,建议使用最新版本 -->
</dependency>

2. 序列化:toJSONString()

1)序列化对象

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class User {

    /**
     * @JSONField 作用:自定义对象属性所对应的 JSON 键名
     * @JSONField 的作用对象:
     * 1. Field
     * 2. Setter 和 Getter 方法
     * 注意:
     * 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
     * 2. 若没有 @JSONField 注解,则直接使用属性名。
     */
    @JSONField(name="NAME")
    private String name;
    @JSONField(name="AGE")
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class ObjectTest {

    private static List<User> userList = new ArrayList<User>();

    @BeforeAll
    public static void setUp() {
        userList.add(new User("xiaoming", 18));
        userList.add(new User("xiaodan", 19));
    }

    @DisplayName("序列化对象")
    @Test
    public void testObjectToJson() {
        String userJson = JSON.toJSONString(userList.get(0));
        System.out.println(userJson);  // {"AGE":18,"NAME":"xiaoming"}
    }

    @DisplayName("序列化集合")
    @Test
    public void testListToJson() {
        String userListJson = JSON.toJSONString(userList);
        System.out.println(userListJson);  // [{"AGE":18,"NAME":"xiaoming"},{"AGE":19,"NAME":"xiaodan"}]
    }

    @DisplayName("序列化数组")
    @Test
    public void testArrayToJson() {
        User[] userArray = new User[5];
        userArray[0] = new User("zhangsan", 20);
        userArray[1] = new User("lisi", 21);
        String userArrayJson = JSON.toJSONString(userArray);
        System.out.println(userArrayJson);  // [{"AGE":20,"NAME":"zhangsan"},{"AGE":21,"NAME":"lisi"},null,null,null]
    }

    @DisplayName("序列化映射")
    @Test
    public void testMapToJson() {
        Map<Integer, User> userMap = new HashMap<Integer, User>();
        userMap.put(1, new User("xiaotie", 10));
        userMap.put(2, new User("xiaoliu", 11));
        String userMapJson = JSON.toJSONString(userMap);
        System.out.println(userMapJson);  // {1:{"AGE":10,"NAME":"xiaotie"},2:{"AGE":11,"NAME":"xiaoliu"}}
    }

}

2)序列化指定属性字段

利用 JSON.toJSONString 方法序列化指定属性字段,主要通过设置属性预过滤器(SimplePropertyPreFilter)的包含属性字段列表(includes)实现。

主要应用于只想验证某些字段的情况,比如只验证跟测试用例有关的字段。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.*;

class User {

    /**
     * @JSONField 作用:自定义对象属性所对应的 JSON 键名
     * @JSONField 的作用对象:
     * 1. Field
     * 2. Setter 和 Getter 方法
     * 注意:
     * 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
     * 2. 若没有 @JSONField 注解,则直接使用属性名。
     */
    @JSONField(name="NAME")
    private String name;
    @JSONField(name="AGE")
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class ObjectTest {

    @DisplayName("指定所有类的属性字段")
    @Test
    public void testAllClassField() {
        User user = new User("xiaoming", 18);
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter();  // 默认所有类型的类均可转换
        filter.getIncludes().addAll(Arrays.asList("NAME", "AGE"));  // 需存在于 @JSONField
        String text = JSON.toJSONString(user, filter);
        System.out.println(text);  // {"AGE":18,"NAME":"xiaoming"}
    }

    @DisplayName("指定单个类的个别属性字段")
    @Test
    public void testOneClassField() {
        ArrayList<User> users = new ArrayList<>();
        users.add(new User("xiaodan", 18));
        users.add(new User("xiaoxue", 19));
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter(User.class);  // 指定User类
        filter.getIncludes().addAll(Arrays.asList("NAME"));
        String text = JSON.toJSONString(users, filter);
        System.out.println(text);  // [{"NAME":"xiaodan"},{"NAME":"xiaoxue"}]
    }

}

3)序列化排除属性字段

利用 JSON.toJSONString 方法序列化过滤属性字段,主要通过设置属性预过滤器(SimplePropertyPreFilter)的排除属性字段列表(excludes)实现。

主要应用于不想验证某些字段的情况,比如排除无法验证的随机属性字段。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.*;

class User {

    /**
     * @JSONField 作用:自定义对象属性所对应的 JSON 键名
     * @JSONField 的作用对象:
     * 1. Field
     * 2. Setter 和 Getter 方法
     * 注意:
     * 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
     * 2. 若没有 @JSONField 注解,则直接使用属性名。
     */
    @JSONField(name="NAME")
    private String name;
    @JSONField(name="AGE")
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class ObjectTest {

    @DisplayName("排除所有类的属性字段")
    @Test
    public void testAllClassField() {
        User user = new User("xiaoming", 18);
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter();  // 默认所有类型的类均可转换
        filter.getExcludes().addAll(Arrays.asList("NAME"));  // 排除 NAME 字段(需存在于 @JSONField)
        String text = JSON.toJSONString(user, filter);
        System.out.println(text);  // {"AGE":18}
    }

    @DisplayName("排除指定类的属性字段")
    @Test
    public void testOneClassField() {
        ArrayList<User> users = new ArrayList<>();
        users.add(new User("xiaodan", 18));
        users.add(new User("xiaoxue", 19));
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter(User.class);  // 指定User类
        filter.getExcludes().addAll(Arrays.asList("AGE"));
        String text = JSON.toJSONString(users, filter);
        System.out.println(text);  // [{"AGE":18},{"AGE":19}]
    }

}

3.  反序列化:parseObject() / parseArray()

1)反序列化对象

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.annotation.JSONField;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.*;

class User {

    /**
     * @JSONField 作用:自定义对象属性所对应的 JSON 键名
     * @JSONField 的作用对象:
     * 1. Field
     * 2. Setter 和 Getter 方法
     * 注意:
     * 1. 若属性是私有的,必须要有 set 方法,否则反序列化会失败。
     * 2. 若没有 @JSONField 注解,则直接使用属性名。
     */
    // @JSONField(name="name")
    private String name;
    // @JSONField(name="age")
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class ObjectTest {

    @DisplayName("反序列化对象")
    @Test
    public void testJsonToObject() {
        String text = "{\"age\":18,\"name\":\"xiaoming\"}";
        User user = JSON.parseObject(text, User.class);
        System.out.println(user);  // User{name='xiaoming', age=18}
    }

    @DisplayName("反序列化数组")
    @Test
    public void testJsonToArray() {
        String text = "[{\"age\":18,\"name\":\"xiaoming\"}, {\"age\":19,\"name\":\"xiaowa\"}]";
        User[] users = JSON.parseObject(text, User[].class);
        System.out.println(Arrays.toString(users));  // [User{name='xiaoming', age=18}, User{name='xiaowa', age=19}]
    }

    @DisplayName("反序列化集合")
    @Test
    public void testJsonToCollection() {
        String text = "[{\"age\":18,\"name\":\"xiaoming\"}, {\"age\":19,\"name\":\"xiaowa\"}]";
        // List 集合
        List<User> userList = JSON.parseArray(text, User.class);
        System.out.println(Arrays.toString(userList.toArray()));  // [User{name='xiaoming', age=18}, User{name='xiaowa', age=19}]
        // Set 集合
        Set<User> userSet = JSON.parseObject(text, new TypeReference<Set<User>>() {});
        System.out.println(Arrays.toString(userSet.toArray()));  // [User{name='xiaowa', age=19}, User{name='xiaoming', age=18}]
    }

    @DisplayName("反序列化映射")
    @Test
    public void testJsonToMap() {
        String text = "{1:{\"age\":18,\"name\":\"xiaoming\"}, 2:{\"age\":19,\"name\":\"xiaowa\"}}";
        Map<Integer, User> userList = JSON.parseObject(text, new TypeReference<Map<Integer, User>>() {});
        for (Integer i : userList.keySet()) {
            System.out.println(userList.get(i));
        }
        /*
            User{name='xiaoming', age=18}
            User{name='xiaowa', age=19}
         */
    }

}

2)反序列化非公有字段

由于某些属性字段没有公有设置方法,或者没有以字段名称作为公有设置方法,那么当需要反序列化这些属性字段时,需要指定 SupportNonPublicField(支持非公有字段)反序列化参数。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.*;

class Person {

    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private String getName() {
        return name;
    }

    private void setName(String name) {
        this.name = name;
    }

    private int getAge() {
        return age;
    }

    private void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class PrivateTest {

    @DisplayName("反序列化非公有字段")
    @Test
    public void testJsonToObject() {
        String text = "{\"age\":18,\"name\":\"xiaoming\"}";
        Person person = JSON.parseObject(text, Person.class, Feature.SupportNonPublicField);
        System.out.println(person.toString());  // Person{name='xiaoming', age=18}
    }

}

四、YAML 序列化和反序列化

1、YAML简介

  • YAML(YAML Ain't Markup Language,即 YAML 不是一种标记语言),也可以叫做 YML 。YAML 是一种直观的、能够被电脑识别的数据序列化格式,容易被人类阅读,容易和脚本语言交互,可以被支持 YAML 库的不同编程语言程序所导入(如 C/C++、Ruby、Python、Java、Perl、C#、PHP 等)。

  • YML 文件是以数据为核心的,相比 JSON、XML 等方式更加简洁。

  • YAML 文件的扩展名可以使用 .yml 或者 .yaml 。

YAML 官网

YAML 语法:

  • 大小写敏感。
  • 数据值前边必须要有空格(大于等于 1 个)作为分隔符。
  • 使用缩进表示层级关系。
  • 缩进时不允许使用 Tab 键,只允许使用空格(各个系统 Tab对应的 空格数目可能不同,导致层次混乱)。
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略。

YAML 数据格式:

  • 对象(map):键值对的集合
# 行内写法
address: {province: 山东, city: 济南}

# 多行写法
address:
  province: 山东
  city: 济南
  • 数组:一组按次序排列的值
# 行内写法
hobbyList: [游泳, 跑步]

# 多行写法
hobbyList:
  - 游泳
  - 跑步
  • 纯量:单个的、不可再分的值
# 字符串默认不用加引号,但包含空格或特殊字符必须加引号,单引号或双引号都可以
# 单引号:不识别转移字符,即原样输出
# 双引号:识别转移字符,如 \r、\n 等
userId: S123
username: "lisi"
password: '123456'
province: 山东
city: "济南 : ss"

# 布尔值
success: true

# 整数
age: 13

# 浮点数
weight: 75.5

# Null
gender: ~

# 时间:使用 ISO8601 标准
createDate: 2001-12-14T21:59:43.10+05
  • 参数引用
name: lisi

person:
  name: ${name}  # 引用上边定义的name值

2、YAML 序列化和反序列化

1. yaml文件与Bean类

示例:yaml 文件。

userId: 1
username: lisi
password: 123456
address: {province: 山东, city: 济南}
hobbyList: [游泳, 跑步]

或:

userId: 1
username: "lisi"
password: '123456'
address:
  province: 山东
  city: "济南 : ss"
hobbyList:
  - 游泳
  - 跑步

示例:Bean 实体类。

  • Maven 依赖:
<!-- Bean类的GETTER、SETTER注解 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>RELEASE</version>
    <scope>compile</scope>
</dependency>
  • User 实体类:
import lombok.*;

import java.security.Timestamp;
import java.util.List;

@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private String userId;
    private String username;
    private String password;
    private Timestamp createDate;
    private Address address;
    private List<String> hobbyList;
}
  • Address 实体类:
import lombok.*;

@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Address {

    private String province;
    private String city;
}

2. snakeyaml 库

Maven 依赖:

<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.21</version>
</dependency>

1)yaml、map 互转

使用 yaml 对象中的 load 方法会返回一个 map 对象,然后遍历这个 map 即可得到自己想要的数据。

import org.yaml.snakeyaml.Yaml;

import java.io.InputStream;
import java.util.Map;

public class YamlDemo {

    public static void main(String[] args) {
        // yaml 读取
        InputStream in = YamlDemo.class.getClassLoader().getResourceAsStream("test.yaml");
        Yaml yaml = new Yaml();
        Map<String, Object> map = yaml.loadAs(in, Map.class);
        map.forEach(
                (String key, Object value) -> {
                    System.out.println("key: "+key+" value: "+value);
                }
        );
        /* 执行结果:
            key: userId value: 1
            key: username value: lisi
            key: password value: 123456
            key: address value: {province=山东, city=济南}
            key: hobbyList value: [游泳, 跑步]
         */

        // yaml 写入
        map.put("username", "zhangsan");  // 修改读取的yaml内容
        try {
            // 将修改后的内容写入new_user.yaml
            yaml.dump(map, new OutputStreamWriter(new FileOutputStream(new File("new_user.yaml"))));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

key: userId value: 1
key: username value: lisi
key: password value: 123456
key: address value: {province=山东, city=济南}
key: hobbyList value: [游泳, 跑步]

2)yaml 转 Bean

import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.util.Objects;

public class YamlDemo {

    public static void main(String[] args) {
        InputStream resource = YamlDemo.class.getClassLoader().getResourceAsStream("test.yaml");
        if (Objects.nonNull(resource)) {
            Yaml yaml = new Yaml();
            User user = yaml.loadAs(resource, User.class);
            System.out.println(user.getClass());  // class User
            System.out.println(user);  // User(userId=1, username=lisi, password=123456, createDate=null, address=Address(province=山东, city=济南), hobbyList=[游泳, 跑步])
        }
    }
}

3)Bean 转 yaml

import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.util.Arrays;

public class YamlDemo {

    public static void main(String[] args) {
        User user = new User();
        user.setUserId("1");
        user.setUsername("lisi");
        user.setPassword("123456");
        user.setAddress(new Address("山东", "济南"));
        user.setHobbyList(Arrays.asList("游泳", "跑步"));
        Yaml yaml = new Yaml();
        String userString = yaml.dump(user);  // 输出字符串
        try {
            yaml.dump(user, new FileWriter("Bean.yaml"));  // 输出文件
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(userString);
        System.out.println(yaml.loadAs(userString, User.class));
    }
}

输出结果:

!!User  # 首行为:!!+全类名
address: {city: 济南, province: 山东}
createDate: null
hobbyList: [游泳, 跑步]
password: '123456'
userId: '1'
username: lisi

上面的对象和数组是显示在一行的,我们也可以通过自定义序列化显示为多行。

import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.util.Arrays;

public class YamlDemo {

    public static void main(String[] args) {
        User user = new User();
        user.setUserId("1");
        user.setUsername("lisi");
        user.setPassword("123456");
        user.setAddress(new Address("山东", "济南"));
        user.setHobbyList(Arrays.asList("游泳", "跑步"));
        DumperOptions dumperOptions = new DumperOptions();
        dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        Yaml yaml = new Yaml(dumperOptions);
        String userString = yaml.dump(user);  // 输出字符串
        try {
            yaml.dump(user, new FileWriter("Bean.yaml"));  // 输出文件
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(userString);
        System.out.println(yaml.loadAs(userString, User.class));
    }
}

执行结果:

!!User
address:
  city: 济南
  province: 山东
createDate: null
hobbyList:
- 游泳
- 跑步
password: '123456'
userId: '1'
username: lisi

3. jackson 库

jackson-dataformat-yaml 是在 snakeyaml 的基础上又封装了一层。

Maven 依赖:

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-yaml</artifactId>
  <version>2.12.0</version>
</dependency>

1)yaml 转 Bean

import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;

import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;

public class YamlDemo {

    public static void main(String[] args) {
        InputStream resource = YamlDemo.class.getClassLoader().getResourceAsStream("test.yaml");
        if (Objects.nonNull(resource)) {
            YAMLMapper yamlMapper = new YAMLMapper();
            User user = null;
            try {
                user = yamlMapper.readValue(resource, User.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(user.getClass());  // class User
            System.out.println(user);  // User(userId=1, username=lisi, password=123456, createDate=null, address=Address(province=山东, city=济南), hobbyList=[游泳, 跑步])
        }
    }
}

2)Bean 转 yaml

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;

import java.io.FileWriter;
import java.util.Arrays;

public class YamlDemo {

    public static void main(String[] args) {
        User user = new User();
        user.setUserId("1");
        user.setUsername("lisi");
        user.setPassword("123456");
        user.setAddress(new Address("山东", "济南"));
        user.setHobbyList(Arrays.asList("游泳", "跑步"));
        YAMLMapper yamlMapper = new YAMLMapper();
        try {
            System.out.println(yamlMapper.writeValueAsString(user));
            yamlMapper.writeValue(new FileWriter("Bean.yaml"), user);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 执行结果:

---
userId: "1"
username: "lisi"
password: "123456"
createDate: null
address:
  province: "山东"
  city: "济南"
hobbyList:
- "游泳"
- "跑步"
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java 对象的序列化是将对象的状态转换为字节流,以便将其存储在文件中或通过网络进行传输。而反序列化则是将字节流重新转换为对象,以便在程序中重新使用。 对象的序列化主要涉及到两个接口,即 Serializable 和 Externalizable。Serializable 接口是 Java 标准序列化机制的简单版本,所有需要序列化的类都需要实现这个接口。而 Externalizable 接口则需要自己实现序列化和反序列化的方法。 在进行对象序列化时,可以使用 ObjectOutputStream 类来实现。通过这个类的 writeObject() 方法,可以将对象写入到输出流中。而在进行反序列化时,可以使用 ObjectInputStream 类来实现。通过这个类的 readObject() 方法,可以将字节流重新转换为对象。 对象序列化的主要用途包括: 1. 对象的持久化:通过将对象序列化后存储在文件中,可以实现对象的持久化,当程序再次启动时,可以反序列化读取文件并重新获取对象的状态。 2. 对象的传输:通过将对象序列化后通过网络传输,可以实现在不同计算机之间的对象传递。 在进行对象序列化时,需要注意以下几点: 1. 需要被序列化的对象和其引用的对象,都需要实现 Serializable 接口。 2. 对于不希望被序列化的属性,可以使用 transient 关键字进行标记。 3. 如果序列化的是一个对象的成员变量,而不是整个对象,那么成员变量对应的类也需要实现 Serializable 接口。 总之,Java 对象序列化和反序列化是一种非常有用的机制,它可以将对象的状态转换为字节流进行存储或传输,以便在需要时重新获取对象。通过使用序列化机制,我们可以实现对象的持久化和传输,使得编程更加灵活和便捷。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值