 | 级别: 初级 齐克科 (qikeke@cn.ibm.com), 软件工程师, IBM Elaine Zhan (ezhan@cn.ibm.com), 软件咨询工程师, IBM 陈荔龙 (chenlil@cn.ibm.com), 软件工程师, IBM
2008 年 3 月 13 日 DB2 V9 中非常重要的一个新特性就是对 XML 的支持,我们可以将结构良好的 XML 文档以分层结构形式进行存储和访问。在全球化软件产品中,我们需要在 XML 中存储一些多字节字符,并以正确的编码信息处理它们。本文将从实例来分析如何处理 DB2 中 XML 类型编码,并学习 XML 内部编码,外部编码,BOM 等概念以及操作 DB2 中 XML 数据的一些基本方法。
XML 文档编码规范
通常处理 XML 的数据源来自文件,那么如何以正确编码读取文件成为问题的关键。如果开发人员已经事先确定了文件的编码类型,那么这个问题就不存在了,但是很多情况下需要根据文件本身来判断这个文件的编码。我们来看如何获取一个XML文件的编码信息。
一个XML文件中可能包含编码信息的地方有三处:
- BOM(Byte Order Mark)。如果这个文件是 unicode 编码,那么 BOM 就可能出现在 XML 文件的开始部分。在 XML 中,BOM 不仅用于显示输入文本流的字节顺序,而且可以帮助检测字符编码方式。BOM 与编码的对应关系如表
|
BOM 值
|
编码
| | X'EFBBBF' | UTF-8 | | X'FEFF' | UTF-16BE | | X'FFFE' | UTF-16LE | | X'0000FEFF' | UTF-32BE | | X'FFFE0000' | UTF-32LE |
- XML 修饰行编码:XML 修饰行所用的编码。如果 XML 文件中包含修饰行,比如
<?xml version="1.0"?>,那么我们可以根据前两个字符的编码编码来判断文档采用的编码信息。是 ASCII 码,UTF-8,UTF-16BE, UTF-16LE, 还是UTF-32?
- XML 修饰行中编码属性:XML 修饰行中的 encoding 的属性值。通常在 XML decoration 中会带有编码信息,比如
<?xml version="1.0" encoding="UTF-8"?>。
那么我们对XML文件处理之前,就可以从这三处得到文件的编码。如果得到的编码彼此矛盾,比如 BOM 显示文件是 UTF-8 编码,而 XML 修饰行中显示<?xml version="1.0" encoding="UTF-8"?>, 那么我们就报错;如果从这几个地方都得不到编码信息,那么按照缺省值 UTF-8 来处理。Java 代码如清单 1.
清单 1. Get XML encoding from file
try{
//step 1: read file to byte array
File file = new File(filename);
int size = (int)file.length();
FileInputStream fis = new FileInputStream(file);
byte[] buff = new byte[size];
int readed = 0;
while (readed < size)
readed += fis.read(buff, readed, size-readed);
String BOM=null;
String Dec=null;
String Enc=null;
String Encoding = null;
boolean hasdec = false;
//step 2: get encoding from BOM if exist
int dec_start = 0;
int b0 = buff[0] & 0xFF;
int b1 = buff[1] & 0xFF;
int b2 = buff[2] & 0xFF;
if (b0 == 0xFE && b1 == 0xFF){
BOM = "UTF-16BE";
Encoding = BOM;
dec_start = 2;
}
else if (b0 == 0xFF && b1 == 0xFE){
BOM = "UTF-16LE";
Encoding = BOM;
dec_start = 2;
}
else if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF){
BOM = "UTF-8";
Encoding = BOM;
dec_start = 3;
}
//step 3: get encoding from xml decoration
int d0 = buff[dec_start] & 0xFF;
int d1 = buff[dec_start+1] & 0xFF;
int d2 = buff[dec_start+2] & 0xFF;
int d3 = buff[dec_start+3] & 0xFF;
if (d0 == 0x3C && d1 == 0x3F){ //ascii encoding of "<?"
Dec = "ASCII";
if (Encoding!=null && Encoding.compareTo("UTF-8")!=0)
System.out.print("Conflict");
if (Encoding == null)
Encoding = Dec;
hasdec = true;
}
else if (d0 == 0x3C && d1 == 0x00 && d2== 0x3F &&
d3 == 0x00){ //UTF-16 Little Ending encoding of "<?"
Dec = "UTF-16LE";
if (Encoding!=null && Encoding.compareTo("UTF-16LE")!=0)
System.out.print("Conflict");
if (Encoding == null)
Encoding = Dec;
hasdec = true;
}
else if (d0 == 0x00 && d1 == 0x3C && d2== 0x00 &&
d3 == 0x3F){ //UTF-16 Big Ending encoding of "<?"
Dec = "UTF-16BE";
if (Encoding!=null && Encoding.compareTo("UTF-16BE")!=0)
System.out.print("Conflict");
if (Encoding == null)
Encoding = Dec;
hasdec = true;
}
//step 4: get encoding from decoration encoding
if (hasdec){
int i = dec_start;
while ((buff[i]&0xFF) != 0x3E && i<buff.length)
i++;
if (i==buff.length){
System.out.print("bad file.");
return "";
}
String decoration = new String(buff, dec_start, i-dec_start, Dec);
int pos = decoration.indexOf("encoding");
if (pos!=-1){
while (pos<decoration.length() && decoration.charAt(pos)!
='"' && decoration.charAt(pos)!='\'')
pos++;
if (pos == decoration.length()){
System.out.print("bad file.");
return "";
}
char quot = decoration.charAt(pos);
int encstart = ++pos;
while (pos<decoration.length() && decoration.charAt(pos)!=quot)
pos++;
if (pos == decoration.length()){
System.out.print("bad file.");
return "";
}
Enc = decoration.substring(encstart,pos);
if (Encoding.compareTo(Enc)!=0 && Encoding.compareTo("ASCII")!=0){
System.out.print("bad file.");
return "";
}
Encoding = Enc;
}
if (Encoding.compareTo("ASCII")==0)
Encoding = "UTF-8";
}
System.out.println("Encoding:"+Encoding);
return new String(buff, dec_start, buff.length-dec_start, Encoding);
}catch(Exception e){
e.printStackTrace();
}
|

 |

|
将 XML 数据存储到 DB2 中
本文只以 Java 程序为例。当从程序中向 DB2 XML 列中存储数据时,有两种方式,这两种方式采用的编码处理方式有所不同。以字符数据类型对 XML 列赋值,比如setString(),这种情况下,传给 DBMS 的数据所使用的编码是由 Java 语言本身所决定的 (UTF-16),这种方式称之为外部编码。在这种情况下,XML 数据本身所包含的内部编码将被忽略。我们使用一些例子来验证一下,首先来看使用外部编码来存储两个使用不同编码的 XML 文件。
清单 2. 以外部编码方式存储
String url = "jdbc:db2://localhost:50000/testdb";
Connection con;
PreparedStatement stmt;
ResultSet rs;
try
{
// Load the IBM DB2 Driver for JDBC and SQLJ
Class.forName("com.ibm.db2.jcc.DB2Driver");
// Create the connection using the IBM DB2 Driver for JDBC and SQLJ
con = DriverManager.getConnection (url,
"db2admin","db2admin");
con.setAutoCommit(true);
// Create the Statement
stmt = con.prepareStatement("insert into T1 values(?)");
//insert a xml data, readfile from a.xml with UTF-8 encoding
String xml = readfile("c:/temp/a.xml");
stmt.setString(1, xml);
stmt.executeUpdate();
//insert a xml data, readfile from b.xml with GBK encoding
stmt = con.prepareStatement("insert into T1 values(?)");
xml = readfile("c:/temp/tmp/b.xml");
stmt.setString(1, xml);
stmt.executeUpdate();
// Close the Statement
stmt.close();
con.close();
}
catch (Exception e){
e.printStackTrace();
}
|
在这一段程序中,分别读取 a.xml 和 b.xml ,并且按照正确的编码转换为字符串,然后插入到数据库中。其中,文件 a.xml 是 UTF-8 编码的文件,b.xml 是 GBK 编码的文件。
清单 3. a.xml(UTF-8 编码的文件)
<?xml version="1.0" encoding="UTF-8"?>
<node>
你好
</node>
|
清单 4. b.xml(GBK 编码的文件)
<?xml version="1.0" encoding="GBK"?>
<node>
你好
</node>
|
从代码中可以看出,读取完文件以后,xml 文档(字符串类型)中仍然带有 xml 修饰行,从两个文件中读取到的 XML 文档分别有不同的编码,但是由于使用了方法setString(),这样 DB2 就只会关注程序使用的外部编码,xml 文档中所带有的 encoding 信息被忽略。存储时,将外部编码 UTF-16 转换为 DB2 使用的编码 UTF-8。在 DB2 CLP 中用select * from T1 来查看一下插入的数据,就会发现,插入两条数据是一样的,而且 xml decoration 被去掉了。
以二进制数据类型对 XML 赋值,比如setBytes()。这种情况下,数据库将检查并使用内部编码。用内部编码时,在我们的 readfile 中所做的跟编码相关的工作就交给了 DBMS 去做了。如果在程序中不关心 xml 文档内容,只是简单存储到数据库,那么这种方式应该是被推荐的方式。
清单 5. 以内部编码方式存储
File file = new File("c:/temp/tmp/c.xml");
int size = (int)file.length();
FileInputStream fis = new FileInputStream(file);
byte[] buff = new byte[size];
int readed = 0;
while (readed < size)
readed += fis.read(buff, readed, size-readed);
stmt.setBytes(1, buff);
stmt.executeUpdate();
|
我们还是通过例子来验证一下,在使用内部编码时,DB2 是如何处理的。首先准备一个正确的 xml 文件
清单 6. 一个正确的XML 文件
<?xml version="1.0" encoding="GBK"?>
<node>
你好
</node>
|
保证这个文件是 GBK 编码的,然后运行上面一段代码。可以验证,这个文档会被正常插入数据库。现在我们改变一下文件 c.xml。用 notepad 打开它,然后另存为 UTF-8 编码,再运行一次代码,会发现有 SQLException 产生,插入数据库失败。这是因为 DBMS 在检查内部代码时发现 BOM 所表示的编码 (UTF-8) 跟 XML 修饰行里面的 encoding 属性值encoding="GBK"?>冲突。再进一步,我们用其他编辑工具打开 c.xml,把 BOM 去掉,然后再试一次,会发现插入成功,但是查询存入的数据,会发现数据变成了
清单 7. 改变编码后的 XML 文件
<?xml version="1.0" encoding="GBK"?>
<node>
浣犲ソ
</node>
|
这是因为原本按照三字节 UTF-8 编码的“你(0xE4BDA0) 好(0xE5A5BD)”,被当成双字节的 GBK 编码,就成了“浣(0xE4BD)犲(0xA0E5)ソ(0xA5BD)”
从 DB2 中读取 XML 数据
从数据库读取数据时,也有两种方式,一种是通过隐式的序列化操作来获取,另一种是显式的序列化操作来获取。
隐式序列化:在 java 程序中,使用 select c1 from qkk.t2,根据赋值时程序所用的数据类型,决定序列化的目标数据编码。如果赋值给字节流,比如rs.getBytes() 那么不存在编码转换的问题,因为在数据库中是 UTF-8 编码,因此得到的是 xml 的 UTF-8 的 byte 串。输出如下
3C6E6F64653E0AE4BDA0E5A5BD0A3C2F6E6F64653E
如果赋值给字符串类型,比如rs.getString(), 那么系列化过程中将进行 UTF-8 到 UTF-16 的编码转换,得到在 java 程序中的字符串。结果如下
清单 8. 隐式序列化 XML 文件
显式序列化:SELECT XMLSERIALIZE(XMLCOL AS BLOB(100) EXCLUDING XMLDECLARATION) FROM T1
不进行字符转换,得到的字节数组依然是
3C6E6F64653E0AE4BDA0E5A5BD0A3C2F6E6F64653E
使用SELECT XMLSERIALIZE(XMLCOL AS CLOB(100) INCLUDING XMLDECLARATION) FROM T1
则会进行字符转换,系列化过程中将进行 UTF-8 到 UTF-16 的编码转换,同时添加 xml 修饰行,得到结果如下
清单 9. 显式序列化 XML 文件
<?xml version="1.0" encoding="GBK"?>
<node>
你好
</node>
|
总结
XML 数据的编码可以从数据本身派生(称为内部编码数据),也可以从外部源派生(称为外部编码数据)。用来在应用程序与 XML 列之间交换 XML 数据的应用程序数据类型确定了编码的派生方式。
参考资料 学习
获得产品和技术
- 下载
IBM 软件试用版,体验强大的 DB2®,Lotus®,Rational®,Tivoli® 和
WebSphere® 软件。
讨论
作者简介  | |  | 齐克科 2001年加入IBM,目前就职于中国开发中心,从事MBPS(Managed Business Process Services)相关的工作。 |
 | |  | Elaine Zhan 中国开发中心 MBPS develope lead,负责CSDP common service 和 provisioning 的开发工作。 |
 | |  | 陈荔龙 2004年加入IBM,目前就职于中国开发中心,从事MBPS(Managed Business Process Services)相关的工作。 |
对本文的评价
|  |