网站菜单

JAVA(五)XML和JSON

一、XML

XML是可拓展标记语言的简称(eXtensible Markup Language),是一种数据的表现格式,通常用来保存和传输数据。例如,一本书的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文件为纯文本语言,并且结构上可以嵌套,经常被互联网用来传输数据。

二、XML格式规范

XML拥有一定的格式,其第一行一定是<?xml version="1.0"?>,此行声明xml的版本和编码。
第二行的声明DOCTYPE是可选的,这里定义了文件的类型,该内容是可选的。
一个XML文档有且仅有一个根元素,根元素可以包含任意个子元素,元素可以包含属性,例如,1234567包含一个属性lang="CN",且元素必须正确嵌套。如果是空元素,可以用表示。
如果内容中出现了尖括号这样的格式字段,则需要进行转义:

字符 表示
< <
> >
& &
" "
' '

类似<!DOCTYPE note SYSTEM "book.dtd">声明的是文档定义类型(DTD:Document Type Definition)
DTD文档可以指定一系列规则,例如:

  • 根元素必须是book
  • book元素必须包含name,author等指定元素
  • isbn元素必须包含属性lang

    注意:浏览器对HTML格式的文件有一定的容错率,但是对XML文件的格式是严格要求的
    而且XML文件的数据格式可以被DTD和XSD验证。
    XML是一个技术体系的简称,其不只含有我们所使用到的XML格式文件。

三、使用DOM

因为XML文件是一种树形结构,因此我们可以使用API来对XML文件进行解析。

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

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

  • Document:代表整个XML文档;
  • Element:代表一个XML元素;
  • Attribute:代表一个元素的某个属性。
    解析结构如下:

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

五、DOM和SAX的区别

  • DOM可在内存中完整表示XML数据结构;
  • DOM解析速度慢,内存占用大。
  • SAX是一种流式解析XML的API;
  • SAX通过事件触发,读取速度快,消耗内存少;

另外需要了解Jackson,官方文档:
https://github.com/FasterXML/jackson

六、JSON

JSON是JavaScript Object Notation的缩写,它去除了所有JavaScript执行代码,只保留JavaScript的对象格式。
例如以下这段代码:

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

JSON的优势:

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

现在问题来了:使用Java如何对JSON进行读写?
常用的用于解析JSON的第三方库有:

  • Jackson
  • Gson
  • Fastjson

就可以使用下面的代码解析一个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不存在该属性时解析不会报错。

把JSON解析为JavaBean的过程称为反序列化。如果把JavaBean变为JSON,那就是序列化。要实现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.10.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 = ...)即可。

显示评论 (325)

文章评论

相关推荐

面试题目

杭州蚂蚁金服一面面试题目 1.JAVA容器有哪些?哪些是同步容器,哪些是并发容器? 2.ArrayList和LinkedList的插入和访问的时间复杂度? 3.java反射原理,注解原理? 4.新生代…

try-catch放在循环体内还是循环体外?

结论:效率上没有差别。