Java如何解析某个目录下xml文件,将XML文件转换为报表数据源?

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

Java开发的报表工具FineReport中,假如在目录下保存了几个XML文件,希望把XML文件转换为报表数据源,同时希望展示动态xml数据源的效果,这时可通过参数的方式,动态获取xml字段中的值再作为报表数据源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Northwind.xml记录数据格式如下:
<?xmlversion= "1.0"  encoding= "UTF-8" ?>
<Northwind>
     <Customers>
             <CustomerID>ALFKI</CustomerID>
                    <CompanyName>ALfredsFutterkiste</CompanyName>
                    <ContactName>MariaAnders</ContactName>
                    <ContactTitle>SalesRepresentative</ContactTitle>
                    <Address>ObereStr. 57 </Address>
                    <City>Berlin</City>
                    <PostalCode> 12209 </PostalCode>
                    <Country>Germany</Country>
                    <Phone> 030 - 0074321 </Phone>
                    <Fax> 030 - 0076545 </Fax>
          </Customers>
</Northwind>


最终用于制作报表的数据源形式如下:

wKioL1f_R1Lh8oAdAAAhI26URh8078.png

对于这样的情况我们如何来实现呢?FineReport中可以通过自定义程序数据集来对xml字段数据进行解析,最终返回所希望的数据表。实现步骤如下:

1、定义XMLColumnNameType4Demo封装类

首先定义参数nametype,供其他类直接调用,安全性比较高,详细代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
packagecom.fr.data;  
   
publicclass XMLColumnNameType4Demo {  
     private  int  type = - 1 ;  
     private  String name =  null ;   
     public  XMLColumnNameType4Demo(String name, int  type) {  
         this .name = name;  
         this .type = type;  
     }  
     public  String getName() {  
         return  name;  
     }  
     public  void  setName(String name) {  
         this .name = name;  
     }      
     public  int  getType() {  
         return  type;  
     }  
     public  void  setType( int  type) {  
         this .type = type;  
     }  
}


2、定义XMLParseDemoDataModel.java类文件

定义XMLParseDemoDataModel.java类继承AbstractDataModel接口,实现getColumnCountgetColumnNamegetRowCountgetValueAt四个方法,详细代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
packagecom.fr.data;  
   
importjava.io.File;  
importjava.util.ArrayList;  
importjava.util.List;  
importjavax.xml.parsers.SAXParser;  
importjavax.xml.parsers.SAXParserFactory;  
importorg.xml.sax.Attributes;  
importorg.xml.sax.SAXException;  
importorg.xml.sax.helpers.DefaultHandler;  
importcom.fr.base.FRContext; 
importcom.fr.data.AbstractDataModel;  
importcom.fr.general.ComparatorUtils;
importcom.fr.general.data.TableDataException;
   
/**
  * XMLParseDemoDataModel 
  *  
  * DataModel是获取数据的接口 
  *  
  * 这里通过init方法一次性取数后,构造一个二维表对象来实现DataModel的各个取数方法 
  */  
publicclass XMLParseDemoDataModel  extends  AbstractDataModel {  
     // 数据类型标识  
     public  static  final  int  COLUMN_TYPE_STRING=  0 ;  
     public  static  final  int  COLUMN_TYPE_INTEGER=  1 ;  
     public  static  final  int  COLUMN_TYPE_BOOLEAN=  2 ;  
   
     // 缓存取出来的数据  
     protected  List row_list =  null ;  
   
     // 数据对应的节点路径  
     private  String[] xPath;  
     // 节点路径下包含的需要取数的节点  
     private  XMLColumnNameType4Demo[]columns;  
   
     private  String filePath;  
   
     public  XMLParseDemoDataModel(Stringfilename, String[] xPath,  
             XMLColumnNameType4Demo[] columns){  
         this .filePath = filename;  
         this .xPath = xPath;  
         this .columns = columns;  
     }  
   
     /** 
      * 取出列的数量 
      */  
     public  int  getColumnCount() throwsTableDataException {  
         return  columns.length;  
     }  
   
     /** 
      * 取出相应的列的名称 
      */  
     public  String getColumnName(intcolumnIndex)  throws  TableDataException { 
         if  (columnIndex <  0  || columnIndex>= columns.length)  
             return  null ;  
         String columnName =columns[columnIndex] ==  null  null  
                 :columns[columnIndex].getName();  
   
         return  columnName;  
     }  
   
     /** 
      * 取出得到的结果集的总的行数 
      */  
     public  int  getRowCount() throwsTableDataException {  
         this .init();  
         return  row_list.size();  
     }  
   
     /** 
      * 取出相应位置的值 
      */  
     public  Object getValueAt( int  rowIndex, intcolumnIndex)  
             throws  TableDataException {  
         this .init();  
         if  (rowIndex <  0  || rowIndex >=row_list.size() || columnIndex <  0  
                 || columnIndex >=columns.length)  
             return  null ;  
         return  ((Object[])row_list.get(rowIndex))[columnIndex];  
     }  
   
     /** 
      * 释放一些资源,取数结束后,调用此方法来释放资源 
      */  
     public  void  release()  throws  Exception{  
         if  ( this .row_list !=  null ) {  
             this .row_list.clear();  
             this .row_list =  null ;  
        
     }  
   
     /**************************************************** */  
     /** ***********以上是实现DataModel的方法*************** */  
     /**************************************************** */  
   
     /**************************************************** */  
     /** ************以下为解析XML文件的方法*****************/  
     /**************************************************** */  
   
     // 一次性将数据取出来  
     protected  void  init() throwsTableDataException {  
         if  ( this .row_list !=  null )  
             return ;  
   
         this .row_list =  new  ArrayList();  
         try 
             // 使用SAX解析XML文件,使用方法请参见JAVA SAX解析  
             SAXParserFactory f =SAXParserFactory.newInstance();  
             SAXParser parser =f.newSAXParser();  
   
             parser.parse(newFile(XMLParseDemoDataModel. this .filePath), 
                     new  DemoHandler());  
         catch  (Exception e) {  
             e.printStackTrace();  
            FRContext.getLogger().error(e.getMessage(), e);  
        
     }  
   
     /** 
      * 基本原理就是解析器在遍历文件时发现节点开始标记时,调用startElement方法读取节点内部内容时,调用characters方法 
      * 发现节点结束标记时,调用endElement 
      */  
     private  class  DemoHandler extendsDefaultHandler {  
         private  List levelList = newArrayList();  // 记录当前节点的路径 
         private  Object[] values;  // 缓存一条记录  
         private  int  recordIndex = - 1 // 当前记录所对应的列的序号,-1表示不需要记录  
   
         public  void  startElement(String uri,String localName, String qName,  
                 Attributes attributes) throwsSAXException {  
             // 记录下  
             levelList.add(qName);  
   
             if  (isRecordWrapTag()) {  
                 // 开始一条新数据的记录  
                 values = newObject[XMLParseDemoDataModel. this .columns.length];  
             else  if  (needReadRecord()) {  
                 // 看看其对应的列序号,下面的characters之后执行时,根据这个列序号来设置值存放的位置。  
                 recordIndex =getColumnIndex(qName);  
             }  
        
   
         public  void  characters( char [] ch, intstart,  int  length)  
                 throws  SAXException {  
             if  (recordIndex > - 1 ) {  
                 // 读取值  
                 String text =  new  String(ch,start, length);  
                XMLColumnNameType4Demo type =XMLParseDemoDataModel. this .columns[recordIndex];  
                 Object value =  null ;  
                 if  (type.getType() ==COLUMN_TYPE_STRING) {  
                     value = text;  
                 }  
                 if  (type.getType() ==COLUMN_TYPE_INTEGER) {  
                     value = newInteger(text);  
                 else  if  (type.getType() ==COLUMN_TYPE_BOOLEAN) {  
                     value = newBoolean(text);  
                 }  
   
                 values[recordIndex] = value;  
            
        
   
         public  void  endElement(String uri,String localName, String qName)  
                 throws  SAXException {  
             try  {  
                 if  (isRecordWrapTag()) {  
                     // 一条记录结束,就add进list中  
                    XMLParseDemoDataModel. this .row_list.add(values);  
                     values =  null ;  
                 else  if  (needReadRecord()){  
                     recordIndex = - 1 ;  
                 }  
             finally  {  
                levelList.remove(levelList.size() -  1 ); 
            
        
   
         // 正好匹配路径,确定是记录外部的Tag  
         private  boolean  isRecordWrapTag(){  
             if  (levelList.size() ==XMLParseDemoDataModel. this .xPath.length  
                     && compareXPath()){  
                 return  true ;  
            
   
             return  false ;  
        
   
         // 需要记录一条记录  
         private  boolean  needReadRecord() {  
             if  (levelList.size() ==(XMLParseDemoDataModel. this .xPath.length +  1
                     && compareXPath()){  
                 return  true ;  
            
   
             return  false ;  
        
   
         // 是否匹配设定的XPath路径  
         private  boolean  compareXPath() {  
             String[] xPath =XMLParseDemoDataModel. this .xPath;  
             for  ( int  i =  0 ; i <xPath.length; i++) {  
                 if (!ComparatorUtils.equals(xPath[i], levelList.get(i))) {  
                     return  false ;  
                 }  
            
   
             return  true ;  
        
   
         // 获取该字段的序号  
         private  int  getColumnIndex(StringcolumnName) {  
             XMLColumnNameType4Demo[] nts =XMLParseDemoDataModel. this .columns;  
             for  ( int  i =  0 ; i < nts.length;i++) {  
                 if (ComparatorUtils.equals(nts[i].getName(), columnName)) {  
                     return  i;  
                 }  
            
   
             return  - 1 ;  
        
     }  
}


3、定义程序数据集XMLDemoTableData

通过参数filename,动态显示xml文件内容,首先xml文件需要放到某个目录下,如下代码是放到D盘,并且定义需要解析的数据列,这边定义的数据列名称,根xml内字段名称是一一对用的。详细代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
packagecom.fr.data;    
  
importjava.io.BufferedInputStream;  
importjava.io.ByteArrayInputStream;  
importjava.io.File;  
importjava.io.FileInputStream;  
importjava.io.FileNotFoundException;  
importjava.io.FileReader;  
importjava.io.InputStream;  
importjava.io.Reader;  
importjava.util.*; 
  
importjavax.xml.stream.XMLEventReader;  
importjavax.xml.stream.XMLInputFactory;  
importjavax.xml.stream.XMLStreamException;  
importjavax.xml.stream.events.XMLEvent; 
  
importcom.fr.base.Parameter;  
importcom.fr.data.AbstractParameterTableData;
importcom.fr.general.data.DataModel;
importcom.fr.script.Calculator;    
importcom.fr.stable.ParameterProvider;
importcom.fr.stable.StringUtils;
     
/**  
  * XMLDemoTableData  
  *   
  *  这是一个按参数来解析不同地址XML文件的demo  
  *   
  * AbstractParameterTableData 包装了有参数数据集的基本实现  
  */    
publicclass XMLDemoTableData  extends  AbstractParameterTableData {    
         
     // 构造函数    
     public  XMLDemoTableData() {    
         // 定义需要的参数,这里定义一个参数,参数名为filename,给其一个默认值"Northwind.xml"    
         this .parameters = newParameter[ 1 ];    
         this .parameters[ 0 ] = newParameter( "filename" "Northwind" );    
     }    
     
     /**  
      * 返回获取数据的执行对象  
      * 系统取数时,调用此方法来返回一个获取数据的执行对象  
      * 注意!当数据集需要根据不同参数来多次取数时,此方法在一个计算过程中会被多次调用。  
      */   
    @SuppressWarnings ( "unchecked"
     public  DataModel createDataModel(Calculatorcalculator) {    
         // 获取传进来的参数    
         ParameterProvider[] params = super .processParameters(calculator);    
             
         // 根据传进来的参数,等到文件的路径    
         String filename =  null ;    
         for  ( int  i =  0 ; i < params.length;i++) {    
             if  (params[i] ==  null ) continue ;    
                 
             if ( "filename" .equals(params[i].getName())) {    
                 filename =(String)params[i].getValue();    
             }   
         }   
             
         String filePath;    
         if  (StringUtils.isBlank(filename)){    
             filePath = "D://DefaultFile.xml" ;    
         else  {    
             filePath =  "D://"  +filename +  ".xml" ;    
         }   
             
         // 定义需要解析的数据列,机器    
//        XMLColumnNameType4Demo[] columns = newXMLColumnNameType4Demo[7];    
//        columns[0] = newXMLColumnNameType4Demo("CustomerID",XMLParseDemoDataModel.COLUMN_TYPE_STRING);   
//        columns[1] = newXMLColumnNameType4Demo("CompanyName",XMLParseDemoDataModel.COLUMN_TYPE_STRING);   
//        columns[2] = newXMLColumnNameType4Demo("ContactName",XMLParseDemoDataModel.COLUMN_TYPE_STRING);   
//        columns[3] = newXMLColumnNameType4Demo("ContactTitle",XMLParseDemoDataModel.COLUMN_TYPE_STRING);   
//        columns[4] = newXMLColumnNameType4Demo("Address",XMLParseDemoDataModel.COLUMN_TYPE_STRING);   
//        columns[5] = newXMLColumnNameType4Demo("City",XMLParseDemoDataModel.COLUMN_TYPE_STRING);   
//        columns[6] = new XMLColumnNameType4Demo("Phone",XMLParseDemoDataModel.COLUMN_TYPE_STRING);   
             
         List list= new  ArrayList();  
         XMLInputFactory inputFactory =XMLInputFactory.newInstance();  
         InputStream in;  
         try 
             in =  new  BufferedInputStream(newFileInputStream( new  File(filePath)));  
             XMLEventReader reader =inputFactory.createXMLEventReader(in);  
             readCol(reader,list);  
             in.close();  
         catch  (Exception e) {  
             // TODO Auto-generated catchblock  
             e.printStackTrace();  
        
         XMLColumnNameType4Demo[]columns=(XMLColumnNameType4Demo[])list.toArray(newXMLColumnNameType4Demo[ 0 ]);  
           
           
         // 定义解析的数据在xml文件结构中的位置    
         String[] xpath =  new  String[ 2 ];    
         xpath[ 0 ] =  "Northwind" ;    
         xpath[ 1 ] =  "Customers" ;    
         /* 
          * 说明  
          * 提供的样例xml文件的格式是:  
          * <Notrhwind>  
          *    <Customers>  
          *         <CustomerID>ALFKI</CustomerID>  
          *         <CompanyName>AlfredsFutterkiste</CompanyName>  
          *         <ContactName>MariaAnders</ContactName>  
          *         <ContactTitle>SalesRepresentative</ContactTitle>  
          *         <Address>Obere Str. 57</Address>  
          *         <City>Berlin</City>  
          *        <PostalCode>12209</PostalCode>  
          *        <Country>Germany</Country> 
          *        <Phone>030-0074321</Phone> 
          *        <Fax>030-0076545</Fax> 
          *    </Customers>  
          * </Northwind>  
          *  
          * 上面定义的意义就是  
          * /Northwind/Customers路径所表示的一个Customers节点为一条数据,它包含的节点中的CustomerID...等等是需要获取的列值  
          */   
             
         // 构造一个实际去取值的执行对象    
         return  new  XMLParseDemoDataModel(filePath,xpath, columns);    
     }  
     private  int  deep= 0 ;  
     private  static  final  int  COL_DEEP= 3 ;  
     private  boolean  flag= false ;  
     private  void  readCol(XMLEventReader reader,List list)  
             throws  XMLStreamException {  
         while  (reader.hasNext()) {  
             XMLEvent event =reader.nextEvent();  
             if  (event.isStartElement()) {  
                 //deep是控制层数的,只把xml中对应的层的加入到列名中  
                 deep++;  
                 //表示已经进入到了列名那一层  
                 if (deep==COL_DEEP){  
                     flag= true ;  
                 }  
                 //如果在高层,并且已经进入到了col层,则退出  
                if (deep<COL_DEEP&&flag){ 
                     return ;  
                 }  
                 if (deep!=COL_DEEP){  
                     continue ;  
                 }  
//              println("name: " +event.asStartElement().getName());  
                 XMLColumnNameType4Democolumn= new  XMLColumnNameType4Demo(event.asStartElement().getName().toString(),XMLParseDemoDataModel.COLUMN_TYPE_STRING); 
                 list.add(column);  
                 readCol(reader,list);  
             else  if  (event.isCharacters()){  
                 //对数据值不做处理  
             else  if  (event.isEndElement()){  
                 deep--; 
                 return ;  
            
        
     }  
       
     private  void  readCol0(XMLEventReader reader) 
             throws  XMLStreamException {  
         while  (reader.hasNext()) {  
             XMLEvent event = reader.nextEvent();  
             if  (event.isStartElement()) {  
                 //deep是控制层数的,只把xml中对应的层的加入到列名中  
                 deep++;  
                 //表示已经进入到了列名那一层  
                 if (deep==COL_DEEP){  
                     flag= true ;  
                 }  
                 //如果在高层,并且已经进入到了col层,则退出  
                if (deep<COL_DEEP&&flag){ 
                     return ;  
                 }  
                 if (deep!=COL_DEEP){  
                     continue ;  
                 }  
                 System.out.println( "name:"  + event.asStartElement().getName()); 
                 readCol0(reader);  
             else  if  (event.isCharacters()){  
                 //对数据值不做处理  
             else  if  (event.isEndElement()){  
                 deep--;  
                 return
            
        
     }  
     public  static  void  main(String[]args){  
         XMLInputFactory inputFactory =XMLInputFactory.newInstance();  
//      in = new FileReader(newFile(filePath));  
//      XMLEventReader reader = inputFactory.createXMLEventReader(in);  
//      readCol(reader,list);  
         BufferedInputStream in;  
         try 
             in =  new  BufferedInputStream(newFileInputStream( new  File( "D:/tmp/f.xml" )));  
             byte [] ba= new  byte [ 3 ];  
             in.read(ba, 0 , 3 );  
//      System.out.println(in)  
         XMLEventReader reader =inputFactory.createXMLEventReader(in);  
         newXMLDemoTableData().readCol0(reader);  
         catch  (Exception e) {  
                 // TODO Auto-generated catchblock  
                 e.printStackTrace();  
            
     }  
}
注:如果xml文件的格式上问题描述处所展示的xml格式不一致,则需要修改类中的deep变量,把列名所在的节点层数改成相对应的数值。
注:XMLDemoTableData里面的filename并不是文件名,而是xml里面某个标签名。


4、编译程序数据源

分别编译XMLColumnNameType4Demo.javaXMLParseDemoDataModel.javaXMLDemoTableData.java三个类文件,将生成的class文件放于%FR_HOME%\WebReport\WEB-INF\classes\com\fr\data下。

 

配置程序数据源

新建报表,模板数据集>程序数据集,选择我们定义好的程序数据集XMLDemoTableData.class文件,名字可以自定义,如程序1

wKiom1f_R7SykfFwAAAor28fmhc521.png

6、使用程序数据源

在模板数据集窗口,点击预览按钮,弹出参数对话框,输入要显示的xml文件名称,点击确定则可以把Northwind.xml文件里面的数据读取出来转换报表数据源了,如下图:

wKiom1f_R9KjXkjoAACLLIQ8keA237.png



本文转自 雄霸天下啦 51CTO博客,原文链接:http://blog.51cto.com/10549520/1861568,如需转载请自行联系原作者
相关文章
|
28天前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
68 9
|
29天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
67 2
|
8天前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
68 34
|
26天前
|
消息中间件 存储 Java
RocketMQ文件刷盘机制深度解析与Java模拟实现
【11月更文挑战第22天】在现代分布式系统中,消息队列(Message Queue, MQ)作为一种重要的中间件,扮演着连接不同服务、实现异步通信和消息解耦的关键角色。Apache RocketMQ作为一款高性能的分布式消息中间件,广泛应用于实时数据流处理、日志流处理等场景。为了保证消息的可靠性,RocketMQ引入了一种称为“刷盘”的机制,将消息从内存写入到磁盘中,确保消息持久化。本文将从底层原理、业务场景、概念、功能点等方面深入解析RocketMQ的文件刷盘机制,并使用Java模拟实现类似的功能。
40 3
|
29天前
|
Java 测试技术 Maven
Maven clean 提示文件 java.io.IOException
在使用Maven进行项目打包时,遇到了`Failed to delete`错误,尝试手动删除目标文件也失败,提示`java.io.IOException`。经过分析,发现问题是由于`sys-info.log`文件被其他进程占用。解决方法是关闭IDEA和相关Java进程,清理隐藏的Java进程后重新尝试Maven clean操作。最终问题得以解决。总结:遇到此类问题时,可以通过任务管理器清理相关进程或重启电脑来解决。
|
28天前
|
XML Android开发 数据格式
Eclipse 创建 XML 文件
Eclipse 创建 XML 文件
25 2
|
1月前
|
Java Maven
maven项目的pom.xml文件常用标签使用介绍
第四届人文,智慧教育与服务管理国际学术会议(HWESM 2025) 2025 4th International Conference on Humanities, Wisdom Education and Service Management
112 8
|
1月前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
61 2
|
7月前
|
XML JavaScript Java
详解Java解析XML的四种方法
详解Java解析XML的四种方法
359 1

推荐镜像

更多