Reputation: 21003
Raised a bounty as the only answer doesn't provide a good implementation for Android. Is there a speedier implementation compatible with Android? Or is SimpleXML the best performance I'll get?
I'm fairly novice to Java and Android development so don't know the proper procedure for deserializing an xml string to an object. I found a method that works in:
public static Object deserializeXMLToObject(String xmlFile,Object objClass) throws Exception
{
try
{
InputStream stream = new ByteArrayInputStream(xmlFile.getBytes("UTF-8"));
Serializer serializer = new Persister();
objClass = serializer.read(objClass, stream);
return objClass;
}
catch (Exception e)
{
return e;
}
}
Where xmlFile
is the (misnamed) xml string, and objClass
is an empty class of the class I want to deserialize to. This is generally a list of other objects.
Example class:
@Root(name="DepartmentList")
public class DepartmentList {
@ElementList(entry="Department", inline=true)
public List<Department> DepartmentList =new ArrayList<Department>();
public boolean FinishedPopulating = false;
}
Department class:
public class Department {
@Element(name="DeptID")
private String _DeptID ="";
public String DeptID()
{
return _DeptID;
}
public void DeptID(String Value)
{
_DeptID = Value;
}
@Element(name="DeptDescription")
private String _DeptDescription ="";
public String DeptDescription()
{
return _DeptDescription;
}
public void DeptDescription(String Value)
{
_DeptDescription = Value;
}
}
Example XML:
<DepartmentList>
<Department>
<DeptID>525</DeptID>
<DeptDescription>Dept 1</DeptDescription>
</Department>
<Department>
<DeptID>382</DeptID>
<DeptDescription>Dept 2</DeptDescription>
</Department>
</DepartmentList>
This has been working fine throughout the app, but I have come to a point where it needs to deserialise >300 objects in the list. This only takes approximately 5 secs, or close to a minute when debugging, but users are not happy with that performance and time wasted when debugging isn't desirable. Is there any way to speed this up? Or is there another way I should be doing this? Preferably only by changing the deserializeXMLToObject
method.
Upvotes: 4
Views: 1996
Reputation: 6873
Got a similar issue with a SOAP Web Service times ago. In the end I've changed the format of the XML, transforming the nodes in attributes.
example:
<node>
<attr1>value1</attr1>
<attr2>value2</attr2>
<attr3>value3</attr3>
<attr4>value4</attr4>
</node>
was transformed in
<node attr1='value1'attr2='value2' attr3='value3' attr4='value4' />
Maybe not the best solution theorically wise, but performance improvement was really good. Ofc if your XML is a bit more complex (repeteable nodes at various levels or multi level lists) things may be a bit complex.
Upvotes: 0
Reputation: 618
For my research this is the best way to optimize:
"Simple will dynamically build your object graph, this means that it will need to load classes that have not already been loaded, and build a schema for each class based on its annotations using reflection. So first use will always be by far the most expensive. Repeated use of the same persister instance will be many times faster. So try to avoid multiple persister instances, just use one if possible."
So refactoring your code to use the same Persister should improve you performance.
This and other tips I got from this question. In that case, this refactoring improved the performance, as stated by the author (from 15s to 3-5s).
Hope it helps
Upvotes: 3
Reputation: 2520
I am sure someone will point to a better library that's out there, but according to one detailed answer, they are all slow on Android.
So here is my quick hack (yes I know its not very maintainable and is brittle to the XML not being formed exactly as specified) and some results:
private void doTest()
{
Thread t = new Thread()
{
public void run()
{
runOne(2000);
runOne(300);
runOne(20000);
}
private void runOne(int num)
{
String start = "<DepartmentList>";
String mid1 = "<Department>\n" +
"<DeptID>";
String mid2 = "</DeptID>\n" +
"<DeptDescription>Dept ";
String mid3 = "</DeptDescription></Department>";
String fin = "</DepartmentList>";
StringBuffer sb = new StringBuffer();
sb.append(start);
for (int i=0; i< num; i++)
{
sb.append(mid1);
sb.append(""+i);
sb.append(mid2);
sb.append(""+i);
sb.append(mid3);
}
sb.append(fin);
Pattern p = Pattern.compile(
"<Department\\s*>\\s*<DeptID\\s*>([^<]*)</DeptID>\\s*<DeptDescription\\s*>([^<]*)</DeptDescription>\\s*</Department>");
long startN = System.currentTimeMillis();
DepartmentList d = new DepartmentList();
List<Department> departments = d.DepartmentList;
Matcher m = p.matcher(sb);
while (m.find())
{
Department department = new Department();
department.DeptID(m.group(1));
department.DeptDescription(m.group(2));
departments.add(department);
}
long endN = System.currentTimeMillis();
Log.d("Departments", "parsed: " + departments.size() + " in " + (endN-startN) + " millis");
Log.d("Departments", "lastone: " + departments.get(departments.size() -1)._DeptID + " desc: " + departments.get(departments.size() -1)._DeptDescription);
}
};
t.start();
}
public class DepartmentList {
public List<Department> DepartmentList =new ArrayList<Department>();
public boolean FinishedPopulating = false;
}
public class Department {
private String _DeptID ="";
public String DeptID()
{
return _DeptID;
}
public void DeptID(String Value)
{
_DeptID = Value;
}
private String _DeptDescription ="";
public String DeptDescription()
{
return _DeptDescription;
}
public void DeptDescription(String Value)
{
_DeptDescription = Value;
}
}
I pasted this into an Android project and called it from the onCreate() method. Here is the results:
Platform num=300 num=2000 num=20000
=================================================
Nexus 7 5 38 355
Galaxy Y 29 430 1173
HTC Desire HD 19 189 539
Galaxy Nexus 14 75 379
All times are in milliseconds.
Upvotes: 6
Reputation: 18148
You can eliminate the intermediate (de)serialization steps by serializing directly to XML and deserializing directly from XML, using e.g. JAXB or XStream.
You may also be able to speed things up via multithreading. I'll assume that all of the XML strings you want to deserialize are in a ConcurrentLinkedQueue; alternatively, you can synchronize access to whatever non-threadsafe collection you're using. Use something like a ThreadPoolExecutor to minimize thread creation overhead.
public class DeserializeXML implements Runnable {
private final String xml;
private final ConcurrentLinkedQueue<Object> deserializedObjects;
public DeserializeXML(String xml, ConcurrentLinkedQueue deserializedObjects) {
this.xml = xml;
this.deserializedObjects = deserializedObjects;
}
public void run() {
deserializedObjects.offer(deserializeXMLToObject(xml, Object.class));
}
}
// ***
ConcurrentLinkedQueue<String> serializedObjects;
ConcurrentLinkedQueue<Object> deserializedObjects;
ThreadPoolExecutor executor = new ThreadPoolExecutor;
while(!serializedObjects.isEmpty()) {
executor.execute(new DeserializeXML(serializedObjects.poll(), deserializedObjects));
}
Upvotes: 2