Java 中的 XML:数据绑定,第 2 部分:性能

Java 中的 XML:数据绑定,第 2 部分:性能 c.gif英文原文c.gif
c.gif
c.gif
c.gif
c.gif
c.gif
内容:
c.gif
性能测试
输入计时
输出计时
内存使用情况
启动时间
那么什么是 JiBX 呢?
结束语
参考资料
关于作者
对本文的评价
c.gif
相关内容:
c.gif
Data binding, part 1
Data binding with Castor
XML document model performance in Java
Understanding SAX
Understanding DOM
c.gif
在 XML 和 Web 服务专区还有:
c.gif
教学
工具与产品
所有的文章
c.gif
c.gif
经过了第 1 部分对数据绑定框架的介绍后,现在对其进行测试

级别:中级

Dennis M. Sosnoskidms@sosnoski.com
总裁,SosnoskiSoftware Solutions,Inc.
2003 年 6 月

企业 Java 专家 Dennis Sosnoski 研究了 Java 中用于 XML 数据绑定的几种框架的速度和内存使用情况。这些框架包含第 1 部分中讨论的所有代码生成方法、更早的一篇文章中讨论的 Castor 映射绑定方法和一种令人惊讶的有可能成功的新方法。如果您正在您的 Java 应用程序中使用 XML,那么您会希望了解如何将这些数据绑定方法结合在一起!

第 1 部分介绍了有关为什么您希望对 XML 使用数据绑定的背景知识,还概述了可用于数据绑定的 Java 框架。如果您尚未阅读第 1 部分,那么现在您也许至少应该浏览一下那篇文章。在本部分中,我将直接讨论性能问题,而不会进一步讨论原因和方法!

性能测试
为了对数据绑定框架进行性能测试,我生成了包含模拟的航班时刻表信息的文档。这些文档的结构与我在较早的有关利用 Castor 进行映射数据绑定的文章(请参阅参考资料)中定义的结构相同。下面是该结构的样本,之所以称其为紧凑格式是因为它主要对数据使用了属性:

清单 1. 紧凑的文档格式

<?xml version="1.0"?>
<timetable>
  <carrier ident="AR" rating="9">
    <URL>http://www.arcticairlines.com</URL>
    <name>Arctic Airlines</name>
  </carrier>
  <carrier ident="CA" rating="7">
    <URL>http://www.combinedlines.com</URL>
    <name>Combined Airlines</name>
  </carrier>
  <airport ident="SEA">
    <location>Seattle, WA</location>
    <name>Seattle-Tacoma International 
                Airport</name>
  </airport>
  <airport ident="LAX">
    <location>Los Angeles, CA</location>
    <name>Los Angeles International 
                Airport</name>
  </airport>
  <route from="SEA" to="LAX">
    <flight carrier="AR" depart="6:23a" 
                arrive="8:42a" number="426"/>
    <flight carrier="CA" depart="8:10a" 
                arrive="10:52a" number="833"/>
    <flight carrier="AR" depart="9:00a" 
                arrive="11:36a" number="433"/>
  </route>
  <route from="LAX" to="SEA">
    <flight carrier="CA" depart="7:45a" 
                arrive="10:20a" number="311"/>
    <flight carrier="AR" depart="9:27a" 
                arrive="12:04p" number="593"/>
    <flight carrier="AR" depart="12:30p" 
                arrive="3:07p" number="102"/>
  </route>
</timetable>

注:清单 1 中的机场名称信息通常是一行代码。为了适应列大小,一些代码行被拆开,出现在两行上。

除了紧凑格式外,我还尝试了一个变体,它的数据值更多地使用了子元素(只对 ID 和 IDREF 继续使用属性)。下面是用在此被称为完整格式的格式表示的同一个数据:

清单 2. 完整的文档格式

<?xml version="1.0"?>
<timetable>
    <carrier ident="AR">
        <rating>9</rating>
        <URL>http://www.arcticairlines.com</URL>
        <name>Arctic Airlines</name>
    </carrier>
    <carrier ident="CA">
         <rating>7</rating>
       <URL>http://www.combinedlines.com</URL>
        <name>Combined Airlines</name>
    </carrier>
    <airport ident="SEA">
        <location>Seattle, WA</location>
        <name>Seattle-Tacoma International Airport</name>
    </airport>
    <airport ident="LAX">
        <location>Los Angeles, CA</location>
        <name>Los Angeles International Airport</name>
    </airport>
    <route from="SEA" to="LAX">
        <flight carrier="AR">
          <number>426</number>
          <depart>6:23a</depart>
          <arrive>8:42a</arrive>
        </flight>
        <flight carrier="CA">
          <number>833</number>
          <depart>8:10a</depart>
          <arrive>10:52a</arrive>
        </flight>
        <flight carrier="AR">
          <number>433</number>
          <depart>9:00a</depart>
          <arrive>11:36a</arrive>
        </flight>
    </route>
    <route from="LAX" to="SEA">
        <flight carrier="CA">
          <number>311</number>
          <depart>7:45a</depart>
          <arrive>10:20a</arrive>
        </flight>
        <flight carrier="AR">
          <number>593</number>
          <depart>9:27a</depart>
          <arrive>12:04p</arrive>
        </flight>
        <flight carrier="AR">
          <number>102</number>
          <depart>12:30p</depart>
          <arrive>3:07p</arrive>
        </flight>
    </route>
</timetable>

通常,根据所用文档的大小不同,XML 框架的相对性能会有巨大差异,因此在这些性能测试中,我同时包含了大文档和小文档。大文档(time-comp.xmltime-full.xml)使用相同的数据值(分别以如上所示的两种不同格式表示)。因此大小明显不同(紧凑格式的为 106 KB,而完整格式的为 211 KB)。小文档都在集合中,每个集合包含 34 个文档,紧凑格式(ttcomp)的大小从 1.4-3.3 KB 不等,完整格式(ttfull)的大小从 2.2-5.8 KB 不等。与大文档一样,小文档集合中的相应文档包含相同的数据值。可以从下载页面(请参阅参考资料)获得测试中使用的完整文档集。

数据绑定字典

下面是我在本文中使用的一些术语的一个袖珍字典:

编组(Marshalling)是在内存中为对象生成 XML 表示的过程。与 Java 对象序列化一样,该表示需要包含所有从属对象:我们的主对象引用的那些对象,以及那些对象引用的对象等等。

数据分解(Unmarshalling)是编组的逆过程,它根据 XML 表示在内存中构建对象(可能还有一幅链接对象的图)。

映射(Mapping)是一组规则,用于显式地将对象编组到 XML 文档和根据 XML 文档分解对象。使用代码生成(基于文档的 DTD 或 W3C XML Schema 描述)的数据绑定方法通常包含隐式的映射,这些映射内置在已构造的对象中,因此在本文中,术语映射只用于将用户定义的 Java 对象与 XML 文档进行关联的方法。

我更希望这些结果能使用更多的文档变体而不只有两种格式来进行测试。但是,由于需要为代码生成提供 W3C XML Schema(Schema)和文档类型定义(Document Type Definition,DTD)描述,还要为映射版本提供映射文件和基类,因此为数据绑定测试添加更多文档所涉及的工作量是可观的。本文使用的两种格式(包含大文档和小文档变体)至少会相当具有代表性地说明,对于典型的业务文档,数据绑定备用方案是如何执行的。但是,由于这些文档中的大多数数据值可以转换成基本类型 ,所以它们可能会使映射绑定方法显示出,内存使用情况将好于典型的普通文档。这导致一种非常紧凑的内部表示。对于其中大多数数据值都需要保存为 String 的文档而言,映射绑定方法的内存优势就会被削弱。

所有测试结果都是使用 1.4GHz Athlon 系统(拥有 256MB DDR RAM,运行 RedHat Linux 7.2)获得的。在所有测试中,我都使用了 Sun 的 JDK 1.4.1 for Linux。所测试的每个数据绑定框架的特定版本如下:JAXB Beta 1、Castor 0.9.4.1、JBind 1.0 Beta 12/07、Quick 4.3.1 和 Zeus Beta 3.5(JiBX 是一个特例 — 请参阅测试结果后面的那么什么是 JiBX?以获取详细信息)。除了 JBind 和 JiBX 之外,所有测试都使用了 Piccolo SAX2 解析器 V1.0.3。这是我知道的最快的 SAX2 解析器,它通常可以达到或超出用于 JiBX 测试的 XMLPull 解析器(XPP3 V1.1.2)的速度。JBind 无法使用 Piccolo 解析器,因此为测试 JBind,我使用了 Xerces Java 2 V2.2.0。

为了提供数据绑定和其它备用方法之间的性能比较,我还只使用 SAX2 解析器对相同文件运行了计时测试,并且使用 dom4j 文档模型(文档模型中的性能佼佼者,它允许使用不同的 SAX2 解析器解析输入文档)运行了计时和内存测试。对于这些测试,我使用了 dom4j V1.3。

在这些计时和内存使用量测试中,我使用的基本框架与以前的文档模型测试(请在参考资料中参阅作者有关文档模型性能的文章。)中所用的相同。这个基准测试框架首先将所有文档读入内存缓冲区,然后对针对文档的输入和输出操作的多次传递进行计时。输入计时输出计时中显示的测试结果是数次传递过程中的最佳计时。这应当代表了服务器类型的环境(其中重复执行相同的代码)中的长期性能。

输入计时
12 显示了使用 dom4j 文档模型和各种数据绑定方法读取 XML 文档(就数据绑定而言,就是对其进行数据分解)以及构造内存中表示的计时结果。在这些图表中,您可以将第一个 SAX2 计时值作为解析文档的基本时间。文档模型和数据绑定实现使用该解析结果来构建其在内存中的表示,因此它们决不会比解析器本身快。标有说明的两个数据绑定测试基于映射而不是代码生成。

图 1. 将大文档读到内存中
将大文档读到内存中

图 2. 将小文档读到内存中
将小文档读到内存中

dom4j 构造文档的内存表示所花费的时间不到单独使用解析器所花费时间的两倍。优于该性能的唯一数据绑定框架是 JiBX。与 dom4j 相比,JAXB、Quick 和 Zeus 都获得了不错的性能数字,但是所花费的时间整体来说都几乎是 JiBX 的两倍。比较起来,Castor 非常缓慢,使用映射绑定和生成代码都如此。

相对于这些测试中的大多数绑定框架,JBind 的执行速度慢了整整一个数量级。这样拙劣的性能一小部分原因是由于用于 JBind 测试的解析器比较慢(因为它无法使用其它测试所用的解析器)。更大的原因可能是由于 JBind 强制在输入时对照 Schema 进行文档验证,这样会增加大量开销。但是,导致这一拙劣性能的最主要原因可能是由于 JBind 框架本身,该框架使用非常间接的方法来进行绑定(在当前实现中,绑定建立在 DOM 文档模型之上)。

除了 JBind 以外的所有测试都是在不进行完全验证的情况下运行的。大多数数据绑定框架仅按照其设计包含某个固有的验证级别(例如,确保元素的内容模型是匹配的)。大多数框架还可以使用验证解析器(如 Xerces Java 2)在输入时对文档进行完全检查,并且有框架(包括 JAXB)可以在内存中执行绑定数据的完全验证。因为在这些测试中主要关心的是性能,所以我尽可能地禁用了可选验证(包括在 Castor 中使用属性文件和数据分组程序/编组程序设置)。

输出计时
34 显示了使用 dom4j 和各种数据绑定方法生成内存中表示的 XML 文本序列(就数据绑定而言,就是对其进行编组)的计时结果。这些图表使用的纵坐标与前两幅图表相同,以使比较变得简化,但是区别在于没有与 SAX2 解析器数字相对应的数字。

图 3. 从内存写大文档
从内存写大文档

图 4. 从内存写小文档
从内存写小文档

在该领域中,dom4j 提供的性能是所有数据绑定方法中最好的,比 JiBX 稍好一点,比 Zeus 更加好一点。其它数据绑定框架都花费了约两倍的时间,Quick 是所有框架中最慢的(当然,不是故意在说双关语)。尽管这里的结果与输入测试的几乎没有太大的变化,但是 dom4j 的确优于其它任何数据绑定框架的这一事实表明它们仍然有改进的余地。

内存使用情况
56 显示了性能情形的另一部分,研究了内存使用情况。当利用文档模型使用非常大的文档(通常有 5+ MB 大小)时,运行时内存的不足会成为一个问题。对数据绑定方法如何进行文档表示所使用的内存量的比较呢?

图 5. 大文档的内存使用情况
大文档的内存使用情况

图 6. 小文档的内存使用情况
小文档的内存使用情况

这里的差异比时间性能比较中的差异更大,并且表现出了一个非常不同的模式。尽管 dom4j 在时间测量中执行的很好,但是在内存使用方面,它比任何数据绑定框架(除了 JBind,它构建在与 dom4j 的表示相当的内部文档模型上)差远了。与该领域中最优秀的执行者相比,表示相同的数据,dom4j 所占用的内存是前者的 10 倍。

两种映射绑定方法为绑定数据使用了同一种内部结构,所以它们表现出了相同的内存使用情况。这让它们在内存效率的“竞技场”上并列第一,从而产生了比使用生成代码的数据绑定方法优越几倍的性能。部分原因是因为 映射绑定使用了数据值的紧凑表示。在这些测试中,映射绑定将大多数数据值转换成 int 值(在大多数 Java 虚拟机(Java Virtual Machine,JVM)中,String 即使只包含一个或两个字符,都将占用 20 个以上的字节,而 int 只占用 4 个字节)。该转换的开销增加了读写次数,但是除了只是内存大小减小了以外,它的确还有其它优点。当实际使用数据时,int 远比 String 更便利和有效。

映射绑定方法之所以能获得较高的内存效率,除了因为它更为广泛地使用了原语值外,另一个原因是生成代码方法通常会将控制信息添加到出现在每个绑定对象中的实际数据中去。该控制信息增加了对象的大小,因而数据绑定少了一个主要优点。

在这些测试中,使用生成代码的数据绑定框架消耗的内存至少是映射绑定的几倍,但是(除 JBind 外)仍然比 dom4j 的文档模型表示小很多。这一点不足为奇 — 诸如 dom4j 的文档模型需要构造一些对象以表示文档的每个组件(包括实际的数据文本以及诸如元素和属性之类的结构组件),而数据绑定只需要保存实际的数据。对于生成代码绑定而言,许多实际数据仍然是作为 String 存储的,但是一些值可以被转换成 int,而其它值可被转换成对象引用。

这里,Zeus 被认为是唯一直接将所有数据存储为 String 的数据绑定方法,这使它成为常用的数据绑定方法中占用内存最大的一种方法。到目前为止,JBind 的内存使用情况仍然较大。这有一部分是由于它在内部使用了文档模型,但是 JBind 使用的内存量要比单独使用文档模型(如 dom4j)所需的内存量大好几倍。从该内存使用情况判断,似乎 JBind 创建了许多其它对象,以建立绑定虚包(facade)和文档模型中实际数据之间的链接。

启动时间
16 说明了数据绑定框架在扩展的测试运行(代表了服务器环境)中执行结果如何。我认为,研究这些框架在仅执行一次(single-execution)环境(例如其中有一个应用程序正在使用数据绑定代码来读或写配置文件)中使用时的比较结果也很有趣。图 7 显示了结果。

图 7. 启动时间
启动时间

7 显示了启动一个短文档所花费的时间 — 从基准测试程序开始执行到整个操作返回为止(将数据分解成对象,然后将对象编组回文档)。同前面的计时数字不同:这里大多数的时间花费在了类装入,以及为获得数据绑定框架代码而由 JVM 进行的本机代码生成。通过将这些结果与前面的计时图表进行比较,可以看到这一启动时间通常比实际处理时间(即使是处理相当大的文档)要大好几倍。如果您的程序每次执行时将只使用一些文档,那么该启动时间将是比前面显示的最佳情形时间更重要的因素。

数据绑定框架使用的 jar 文件的大小是影响这一启动时间的一个主要因素。JiBX 是最小的,运行时和解析器的总大小不足 60KB。JAXB、Castor 和 JBind 是最大的,每个大小大约为 1MB。该时间还受每个框架所需的初始化影响。在使用映射绑定的 Castor 情形中,该时间包含处理映射定义文件,而对于 JBind 而言,它包含处理文档的 Schema 定义。

那么什么是 JiBX 呢?
既然我已经展示了性能结果,那么我可能应该介绍一下这个几乎在每项测试中都能占据小组中的第一名的框架。是的,事实上它是“作弊的参加者”— JiBX 是一种针对性能而设计的数据绑定框架,因此如果它满足了其设计需求,那么在这些测试中它应该是最佳的执行者。

JiBX 实际上源于本系列文章。当我开始研究可用的数据绑定框架时,我惊奇地看到:与文档模型(如 dom4j)相比,它们并不是执行得都那样好。这与我的期望相反,因为数据绑定方法实际上减少了保存在内存中的文档信息的数量 — 而文档模型在内存中保存所有事物,同时数据绑定只需要实际数据。我认为,数据用得少的方法通常应该比那些数据用得多的方法要快。

在研究现有数据绑定框架是如何操作的过程中,我发现从性能角度来说有两个方面看上去不是很好。第一个方面就是许多框架中广泛使用了反射。反射是在运行时访问有关 Java 语言类的信息的一种方法。可用它来访问类实例中的字段和方法,从而提供了一种在运行时将类动态地挂钩在一起,却无需类之间有任何源代码链接的方法。反射是一种功能非常强大的 Java 技术特性,但是将其与调用方法或直接访问已编译代码中的字段相比,它在性能上有些欠缺。

我质疑的第二个方面是使用 SAX2 解析器对文档进行数据分解。SAX2 是一种非常有用的解析 XML 的标准,但是其事件驱动方法并不非常适合数据绑定和类似的应用程序。这里的问题在于,处理 SAX2 事件的代码需要维护其处理的所有事情的状态信息,这既增加了复杂性又增加了开销。

我创建了形成 JiBX 的代码,以对一些方法(解决其它数据绑定框架中所存在的这些问题)进行测试,并实验扩展超出 Castor 支持范围的映射绑定方法。JiBX 使用字节代码增强而不是反射来在项目构建时将挂钩添加进应用程序代码。JiBX 基于拉(pull)解析器体系结构(当前是 XMLPull),而不是 SAX2。JiBX 不是根据 DTD 或 Schema 生成代码,而是使用绑定定义,该定义将用户提供的类与 XML 结构相关联。

这些技术并不是 JiBX 所特有的。许多 Java 数据对象(Java Data Object,JDO)实现都使用字节代码增强,基本上都是为了达到与 JiBX 相同的目的(将访问挂钩添加到现有的已编译代码中)。原始的 JAXB 代码(已被丢弃)基于类似 XMLPull 的拉解析器体系结构。Castor 和 Quick 都支持数据绑定的映射方法(尽管有一些限制)。即使个别技术不是很新,但是它们的组合仍然可以形成其它数据绑定框架非常有趣的备用方案。

在本系列文章的第 3 部分,我将完整地介绍有关 JiBX 的知识。JiBX 仍然处于初期开发阶段。为了性能测试,我手工编写了代码,通常通过字节代码增强添加该代码,并使用 JiBX 运行时的当时版本来运行它。到本文发表时,我仍在着手完成增强代码,有许多其它特性我希望添加到其中。如果您在第 3 部分发表前就希望了解更多有关 JiBX 的知识,请查阅参考资料以获取到 JiBX 站点的链接。您甚至可以为 JiBX 的未来开发献策献力,也可以在您自己的应用程序中使用 JiBX。

结束语
这篇对数据绑定性能的研究展示了一些有趣的结果,但是并未对第 1 部分中的推荐做根本上的更改。Castor 为使用代码生成(根据 W3C XML Schema 定义)的数据绑定提供了最佳的当前支持。与其它备用方案相比,它的数据分解性能比较差,但是它的确可以提供较好的内存利用率和相当快的启动时间。Castor 的开发人员说,在他们的 1.0 发布以前,他们计划专注于解决性能问题,所以到那个时候,您也可能会看到在数据分解性能方面的一些改进。

JAXB 看上去将来仍是代码生成方法的一个不错选择(测试版许可证只允许评估使用)。当前的参考实现测试版在 jar 大小方面非常庞大,并且在内存使用方面的效率也略嫌不足,但是这里再重申一次,将来您可能会看到更佳的性能。在撰写本文的时候,当前版本仍然是测试版,只有当它作为商业或开放源码项目发布以后,它的性能才可能优于参考实现。由于它将作为 J2EE 平台的标准部分,所以关于在 Java 中使用 XML 方面,JAXB 无疑会扮演重要的角色。

性能结果也证实:JBind、Quick 和 Zeus 最适用于有特殊需求的应用程序,而不应该用于一般用途。JBind 的 XML 代码方法可以为围绕 XML 文档处理而构建的应用程序提供重要基础,但是当前实现的性能容易导致问题。Quick 和 Zeus 提供根据 DTD 进行代码生成,但是正如我在第 1 部分中提到的那样,将 DTD 转换成 Schema 通常相当简单。缺点是,Quick 使用起来似乎过于复杂,而 Zeus 只支持 String 用于绑定数据值(没有原语或使用 ID-IDREF 或等价物的对象引用)。

对于数据绑定的映射方法,Castor 的优点是:它是一个相当稳定的实现,并可投入实际使用。Quick 也可以用于这类的绑定,但是似乎也难于设置。JiBX 是新事物,并且尚未完全投入使用,但是它提供了卓越的性能和高度的灵活性。

如果您还未阅读第 1 部分,您也许应该回头阅读一下那篇文章,以便了解更多有关这些数据绑定框架特性的知识。第 1 部分还讨论了数据绑定的代码生成和映射方法之间的权衡。在第 3 部分中,我将深入介绍新的 JiBX 框架。这包括 JiBX 如何将 Java 对象映射到 XML,以及 JiBX 为最小化运行时开销而在构建时使用的字节代码增强过程。请回来查看有关这个令人振奋的方法的完整信息,以提升框架性能!

参考资料

  • 参与有关本文的论坛。(您也可以通过单击文章顶部或底部的讨论来访问论坛。)

  • 有关数据绑定的本系列文章的第 1 部分提供了关于为什么您希望对 XML 使用数据绑定的背景知识,还概述了可用于数据绑定的 Java 框架(developerWorks,2003 年 1 月)。

  • 下载本文测试中使用的完整文档集。

  • 请查阅作者以前有关“Data Binding with Castor”的文章,它介绍了利用 Castor 进行的映射数据绑定技术(developerWorks,2002 年 4 月)。

  • 在作者有关 Java 中的 XML 的解析系列文章中,了解事件驱动方法(SAX2)与拉解析器方法在解析 XML 方面的差异。

  • 如果您需要了解有关 XML 的背景知识,请尝试查阅 developerWorksIntroduction to XML”教程(2002 年 8 月)。

  • 请回顾作者以前的 developerWorks 文章,它们介绍了各种 Java XML 文档模型在性能(2001 年 9 月)和使用情况(2002 年 2 月)方面所做的比较。

  • 请阅读 Brett McLaughlin 所写的“Converting between Java objects and XML with Quick”中有关 Quick 的概述,它向您展示了在不使用其它数据绑定框架必需的类生成语义的情况下,如何使用该框架快速而轻松地将您的 Java 数据转变成 XML 文档(developerWorks,2002 年 8 月)。

  • 有关对象-关系型数据绑定(本意与 JDO 标准类似,但不兼容)的基础知识简介,请阅读由 Bruce Snyder 撰写的“Getting started with Castor JDO”(developerWorks,2002 年 8 月)。

  • 获取有关用于 Java 语言对象持久性的 Java 数据对象(Java Data Objects,JDO)API 的详细信息。

数据绑定框架

  • 了解更多有关 Java Architecture for XML Binding(JAXB)的知识,这是一个正在发展的针对 Java 平台数据绑定的标准。

  • 进一步研究 Castor 框架,它支持映射绑定和生成绑定。

  • 了解 JBind,该框架对于允许 Java 语言应用程序方便地使用 XML 关注得较少,它更多地关注于如何围绕 XML 构建应用程序代码框架。

  • Quick 框架以一系列先于 Java 平台和 XML 的开发成果为基础。它提供了一个极其灵活的框架以便在 Java 平台上使用 XML。

  • 研究 Zeus 的详细信息,它(跟 Quick 一样)根据 XML 文档的 DTD 描述生成代码,但是比 Quick 更易于使用且限制更多。

  • 了解更多有关新的用于映射绑定的 JiBX 框架的知识。

其它链接

关于作者
Dennis Sosnoski 的照片Dennis Sosnoski(dms@sosnoski.com)是西雅图地区的 Java 咨询公司 Sosnoski Software Solutions, Inc. 的创始人和首席顾问,他是 J2EE、XML 和 Web 服务支持方面的专家。Dennis 有 30 多年的专业软件开发经验,最近几年致力于服务器端 Java 技术。他经常在全美范围的会议上就 Java 中的 XML 和 J2EE 技术发言,并主持 Seattle Java-XML SIG
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值