刚进公司第一个任务是写一个导出财务报表的功能:按照财务那边的需求,从数据库查询指定的字段导出到Excel表格,方便财务对账。公司以前的系统是用PHP写的,有些数据库字段是通过PHP的serialize()方法序列化之后存进数据库的,如下所示
a:2:{i:0;a:2:{s:7:"orderid";s:2:"62";s:5:"level";i:1;}i:1;a:2:{s:7:"orderid";s:2:"74";s:5:"level";i:1;}}
PHP可以通过unserialize()反序列化解析出需要的内容,上面的字符串反序列化之后其实是一个二维数组。那么问题来了,我不会写PHP,只能用Java的方法去解析这段字符串。
一看到这个字符串,我以为是JSON格式的字符串,或许可以通过JSON方法解析;后来认真看,这不是JSON格式,后来又想,能不能写个通用方法,把这种类型的字符串转换成JSON格式,再通过JSON方法解析,试了一下,挺麻烦的,不好实现。所以最后想,网上有没有现成Java的方法反序列化的工具类,然后解析出需要的内容。
如何用Java实现反序列化呢?我在网上查了很多资料,找了很久终于找到一个比较靠谱的方法。在项目中导入以下两个类:
PHPSerialize类:
import java.util.regex.*;
import java.util.*;
public class PHPSerialize {
/**
* To store partially decomposed serialized string
*/
private StringBuffer sb;
/** Creates a new instance of phpserialize */
public PHPSerialize() {
}
/**
* Unserializes a single value into a PHPValue
*
* @param String
* s String to unserialize
* @return PHPValue
*/
public PHPValue unserialize(String s) {
sb = new StringBuffer(s);
PHPValue phpvalue = getNext();
// println("value = " + phpvalue.toString());
return phpvalue;
}
/**
* Get the next token in the serialized string
*
*
*/
private PHPValue getNext() {
// println("getNext = "+ sb.toString());
PHPValue phpvalue;
Object value = new Object();
// Current character
String c;
// What type of value is this
String what = "";
// Consume & throw away characters
int consumeChars = 0;
// Length of the string/array
int len = -1;
// Buffer to read in string
StringBuffer tmp = new StringBuffer();
// length of sb is constantly changing.
while (sb.length() > 0) {
c = sb.substring(0, 1);
sb = sb.deleteCharAt(0);
// println(c +"\twhat="+what+"\ttmp="+tmp.toString());
// Ignore chars
if (consumeChars-- > 0) {
continue;
}
/**
* This is a hackish pseudo state machine 'what' is the state
*
*/
if (what.equals("array_length")) {
if (c.equals(":")) {
len = Integer.parseInt(tmp.toString());
tmp.delete(0, tmp.length());
// println("\tarray_length="+ len);
what = "array_value";
}
if (len == -1) {
tmp.append(c);
}
} else if (what.equals("array_value")) {
// value = new PHPValue( new HashMap() );
HashMap hash = new HashMap();
for (int i = 0; i < len; i++) {
PHPValue hashKey = getNext();
PHPValue hashValue = getNext();
// Force the keys of the HashMap to be type String
hash.put(hashKey.value, hashValue);
}
value = hash;
// return new PHPValue(value);
what = "set";
} else if (what.equals("string_length")) {
if (c.equals(":")) {
len = Integer.parseInt(tmp.toString());
tmp.delete(0, tmp.length());
// println("\tstring_length="+ len);
what = "string_value";
consumeChars = 1;
}
if (len == -1) {
tmp.append(c);
}
} else if (what.equals("string_value")) {
if (len-- > 0) {
tmp.append(c);
} else {
value = tmp.toString();
what = "set";
// println("\tstring_value="+ value);
}
} else if (what.equals("integer")) {
if (c.equals(";")) {
value = tmp.toString();
what = "set";
} else {
tmp.append(c);
}
} else if (what.equals("double")) {
if (c.equals(";")) {
value = tmp.toString();
what = "set";
} else {
tmp.append(c);
}
} else if (what.equals("null")) {
if (c.equals(";")) {
value = null;
what = "set";
} else {
// Bad
}
}
// What kind of variable is coming up next
else {
if (c.equals("a")) {
what = "array_length";
} else if (c.equals("s")) {
what = "string_length";
} else if (c.equals("i")) {
what = "integer";
} else if (c.equals("d")) {
what = "double";
} else if (c.equals("n")) {
what = "null";
// do NOT consume the next char ";"
continue;
} else {
continue;
}
// Consume the ":" after variable type declaration
consumeChars = 1;
continue;
}
if (what.equals("set")) {
return new PHPValue(value);
}
}
return new PHPValue(value);
}
}
PHPValue类:
import java.util.HashMap;
/**
* Since i have to declare the return type of a function, and i have no idea
* ahead of time what value is being unserialized, this helper class is a hack
* so that i can return any variable type as PHPValue and deal with it later.
*
* @author Scott Hurring [scott at hurring dot com]
* @version v0.01a ALPHA!!
* @licence GNU GPL
*/
public class PHPValue {
/**
* The raw object given to us by unserialize()
*/
private Object obj = null;
private HashMap hobj = new HashMap();
/**
* The toString() value of the Object passed into PHPValue
*/
public String value = "";
/**
* Is the PHPValue one of the following?
*/
public boolean isNull = false;
public boolean isInteger = false;
public boolean isDouble = false;
public boolean isString = false;
public boolean isHashMap = false;
/**
* Pass in an object, and PHPValue: 1. Attempts to try and figure out what
* it is 2. Stringifies it 3. Sets a boolean flag to let everyone know what
* we think it is.
*
*/
public PHPValue(Object o) {
obj = o;
if (obj == null) {
isNull = true;
value = "";
} else if (obj.getClass().toString().equals("class java.lang.Integer")) {
isInteger = true;
value = ((Integer) obj).toString();
} else if (obj.getClass().toString().equals("class java.lang.Double")) {
isDouble = true;
value = ((Double) obj).toString();
} else if (obj.getClass().toString().equals("class java.util.HashMap")) {
isHashMap = true;
value = obj.toString();
} else if (obj.getClass().toString().equals("class java.lang.String")) {
isString = true;
value = obj.toString();
} else {
value = obj.toString();
}
// debug
if (obj != null) {
// System.out.println("PHPValue: '" + obj.getClass() + "' = " +
// value);
}
}
/**
* Convert PHPValue into a double
*
* @return double
*/
public double toDouble() {
if (isDouble) {
return Double.parseDouble(value);
} else {
return 0.0;
}
}
/**
* Convert PHPValue into an integer
*
* @return int
*/
public int toInteger() {
if (isInteger) {
return Integer.parseInt(value);
} else {
return 0;
}
}
/**
* PHPValue's string-ified value
*
* @return String
*/
public String toString() {
if (!isNull) {
return value;
} else {
return "";
}
}
/**
* Cast PHPValue into HashMap
*
* @return HashMap
*/
public HashMap toHashMap() {
return (HashMap) obj;
}
/**
* Return the raw object
*
* @return double
*/
public Object toObject() {
return obj;
}
}
如何调用:
public static void main(String[] args) {
String str = "a:2:{i:0;a:2:{s:7:\"orderid\";s:2:\"77\";s:5:\"level\";i:1;}i:1;"
+ "a:2:{s:7:\"orderid\";s:2:\"78\";s:5:\"level\";i:1;}}";
PHPSerialize p = new PHPSerialize();
PHPValue c = p.unserialize(str);
System.out.println(c.toHashMap().toString());
}
反序列化出来的结果如下所示,然后你再利用字符串拆分出所需内容。
{0={orderid=77, level=1}, 1={orderid=78, level=1}}
不过上面的方法存在一个问题,就是要解析的内容存在中文时就会报错,如下:
a:2:{s:16:"carrier_realname";s:7:"测试2";s:14:"carrier_mobile";s:11:"18725195368";}
我的解决办法是直接拆分这个字符串,一步一步解析出所需的内容。因为这个字符串的格式的固定的,有一点的规律,所以就可以写一个方法:
//得到有关收货人的字符串后,根据特定规则解析字符串,得到所需内容
if (o_carrier.length() > 22) {
//收货人
String carrier_realname = o_carrier.substring(
o_carrier.indexOf(":\"carrier_realname\";s:") + 22,
o_carrier.indexOf(":\"carrier_mobile\";s:"));
//联系电话
String carrier_mobile = o_carrier.substring(o_carrier.indexOf(":\"carrier_mobile\";s:") + 20,
o_carrier.indexOf("\";}"));
//取出两个"号中间的字符内容
String regex = "\"(.*)\"";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(carrier_realname);
while (matcher.find()) {
//收货人
System.out.println(matcher.group(1));
}
//根据"号拆分字符串,存放到字符串数组
String mobile[] = carrier_mobile.split("\"");
//联系电话
System.out.println(mobile[1]);
}