Skip to Content.
Sympa Menu

xom-interest - [XOM-interest] QName flyweights

xom-interest AT lists.ibiblio.org

Subject: XOM API for Processing XML with Java

List archive

Chronological Thread  
  • From: Wolfgang Hoschek <whoschek AT lbl.gov>
  • To: xom-interest AT lists.ibiblio.org
  • Subject: [XOM-interest] QName flyweights
  • Date: Thu, 9 Jun 2005 04:00:37 -0700

The current xom-1.1 CVS code is generally rather inefficient due to heavy usage of String.intern() in Element and Attribute and has less than optimal memory footprint. So, I'd like to float some ideas centered around more efficient handling of (prefix, localName, URI) tuples, both in space and time.

What could be done to reduce memory footprint and simultaneously improve performance, both when assembling a document in memory and when building it via Builder? Below is a commented prototype idea in this directionl, based on the "flyweight" pattern.

Comments, other ideas, anyone?

Wolfgang.

package nu.xom;

/**
* Qualified XML name with associated namespace URI; Immutable and thread-safe.
* Does not perform XML verification; verification should be performed
* elsewhere. Primarily used in "flyweight" patterns where the same QName
* object is efficiently shared by a large number of elements and/or attributes.
* For some background see http://www.dofactory.com/Patterns/ PatternFlyweight.aspx
* Typically used in combination with a fast cache such as a bounded
* LinkedHashMap with LRU policy or similar. Designed to improve efficiency in <i>both </i>
* space and time, for the following reasons:
*
* <ol>
* <li>The same QName object can be shared and reused many times.</li>
* <li>Classes Element and Attribute need less instance vars.</li>
* <li>The prefix, localName and URI of a QName that has already been seen
* before need not be XML verified and String.intern()ed repeatedly. [A HashMap
* is *much* faster than String.intern(). String.intern() is a performance
* desaster rather than a magic bullet.]</li>
* <li>The same flyweight mechanism can be used inside the normal Element and Attribute
* constructors and setters, as well as in the [NonVerifying] XOMHandler,
* simplifing the XOM code base.</li>
* </ol>
*
* Pseudo code for private methods in Element/Attribute.java (public API remains unchanged):
* <pre>
* private static final Map cache = new LRUHashMap(maxCapacity=256);
*
* qname = new QName(name, uri);
* synchronized: cachedQName = cache.get(qname);
* if (cachedQName == null) { // cache miss
* qname = qname.intern(); // reduce memory footprint
* verify qname according to XML spec (element or attribute)
* synchronized: cache.put(qname, qname);
* cachedQName = qname;
* }
*
* some qname verification in current element/attribute/additional namespace context
* this.qname = cachedQName;
* </pre>
*
* @author whoschek.AT.lbl.DOT.gov
*/
final class QName { // better termed QNameUri or similar ???, package only access; XOM internal
/*
* This implementation is thread-safe without any synchronization even in
* the presence of internal mutation. Slightly subtle implementation ensures
* that it doesn't matter which thread wins the race, if any.
*/

private final String uri;
private final String name; // prefix:localName
private String prefix;
private String localName;

public QName(String name, String uri) {
if (name == null) throw new NullPointerException(); // preserves existing XOM semantics
if (uri == null) uri = "";
this.name = name;
this.uri = uri;
}

// public QName(String prefix, String localName, String uri) { // needed?
// this(prefix + ":" + localName, uri);
// // if name == null || localName == null throw ...
// }

public String getLocalName() {
if (localName == null) calc();
return localName;
}

public String getNamespacePrefix() {
if (prefix == null) calc();
return prefix;
}

public String getNamespaceURI() {
return uri;
}

public String getQualifiedName() {
// avoids repeated string concatenation, preserves string identity and
// String's hashCode cache
return name;
}

/**
* Lazy on demand calculation of prefix and localName. These values are not
* needed for hashCode() and equals()
*/
private void calc() {
int i = name.indexOf(':');
if (i < 0) {
prefix = "";
localName = name;
} else {
prefix = name.substring(0, i); // substring memory overlay
// prefix = prefix.intern(); // no real need for this
localName = name.substring(i+1); // substring memory overlay
// localName = localName.intern(); // no real need for this
}
}

public QName intern() {
String iName = name.intern();
String iUri = uri.length() == 0 ? "" : uri.intern(); // might be unnecessary overkill
return name == iName && uri == iUri ? this : new QName (iName, iUri);
}

public boolean equals(Object o2) {
// if (this == o2) return true;
if (o2 instanceof QName) {
QName q2 = (QName) o2;
return (name == q2.name || name.equals(q2.name))
&& (uri == q2.uri || uri.equals(q2.uri));
}
return false;
}

public int hashCode() {
return uri.hashCode() ^ name.hashCode();
}

}






Archive powered by MHonArc 2.6.24.

Top of Page