Improve writing XML elements to socket

Instead of allocating one big continuous memory block before "writing"
the XMPP stream element to the socket, write the single CharSequences of
LazyStringBuilder/XmlStringBuilder.

Also change Obserable writer to only notify the listeners after a
flush *or* if a certain limit has been reached since the last
notification. Otherwise the debugger would "print" every single XML part
as result of this change.
This commit is contained in:
Florian Schmaus 2015-05-21 20:10:31 +02:00
parent 3c2ba0ec43
commit 8db0403138
4 changed files with 62 additions and 6 deletions

View File

@ -17,6 +17,7 @@
package org.jivesoftware.smack.util; package org.jivesoftware.smack.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class LazyStringBuilder implements Appendable, CharSequence { public class LazyStringBuilder implements Appendable, CharSequence {
@ -105,4 +106,17 @@ public class LazyStringBuilder implements Appendable, CharSequence {
} }
return cache; return cache;
} }
/**
* Get the List of CharSequences representation of this instance. The list is unmodifiable. If
* the resulting String was already cached, a list with a single String entry will be returned.
*
* @return a List of CharSequences representing this instance.
*/
public List<CharSequence> getAsList() {
if (cache != null) {
return Collections.singletonList((CharSequence) cache);
}
return Collections.unmodifiableList(list);
}
} }

View File

@ -28,9 +28,11 @@ import java.util.List;
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class ObservableWriter extends Writer { public class ObservableWriter extends Writer {
private static final int MAX_STRING_BUILDER_SIZE = 4096;
Writer wrappedWriter = null; Writer wrappedWriter = null;
List<WriterListener> listeners = new ArrayList<WriterListener>(); List<WriterListener> listeners = new ArrayList<WriterListener>();
private final StringBuilder stringBuilder = new StringBuilder(MAX_STRING_BUILDER_SIZE);
public ObservableWriter(Writer wrappedWriter) { public ObservableWriter(Writer wrappedWriter) {
this.wrappedWriter = wrappedWriter; this.wrappedWriter = wrappedWriter;
@ -39,10 +41,11 @@ public class ObservableWriter extends Writer {
public void write(char[] cbuf, int off, int len) throws IOException { public void write(char[] cbuf, int off, int len) throws IOException {
wrappedWriter.write(cbuf, off, len); wrappedWriter.write(cbuf, off, len);
String str = new String(cbuf, off, len); String str = new String(cbuf, off, len);
notifyListeners(str); maybeNotifyListeners(str);
} }
public void flush() throws IOException { public void flush() throws IOException {
notifyListeners();
wrappedWriter.flush(); wrappedWriter.flush();
} }
@ -57,18 +60,25 @@ public class ObservableWriter extends Writer {
public void write(char[] cbuf) throws IOException { public void write(char[] cbuf) throws IOException {
wrappedWriter.write(cbuf); wrappedWriter.write(cbuf);
String str = new String(cbuf); String str = new String(cbuf);
notifyListeners(str); maybeNotifyListeners(str);
} }
public void write(String str) throws IOException { public void write(String str) throws IOException {
wrappedWriter.write(str); wrappedWriter.write(str);
notifyListeners(str); maybeNotifyListeners(str);
} }
public void write(String str, int off, int len) throws IOException { public void write(String str, int off, int len) throws IOException {
wrappedWriter.write(str, off, len); wrappedWriter.write(str, off, len);
str = str.substring(off, off + len); str = str.substring(off, off + len);
notifyListeners(str); maybeNotifyListeners(str);
}
private void maybeNotifyListeners(String s) {
stringBuilder.append(s);
if (stringBuilder.length() > MAX_STRING_BUILDER_SIZE) {
notifyListeners();
}
} }
/** /**
@ -76,12 +86,14 @@ public class ObservableWriter extends Writer {
* *
* @param str the written String to notify * @param str the written String to notify
*/ */
private void notifyListeners(String str) { private void notifyListeners() {
WriterListener[] writerListeners = null; WriterListener[] writerListeners = null;
synchronized (listeners) { synchronized (listeners) {
writerListeners = new WriterListener[listeners.size()]; writerListeners = new WriterListener[listeners.size()];
listeners.toArray(writerListeners); listeners.toArray(writerListeners);
} }
String str = stringBuilder.toString();
stringBuilder.setLength(0);
for (int i = 0; i < writerListeners.length; i++) { for (int i = 0; i < writerListeners.length; i++) {
writerListeners[i].write(str); writerListeners[i].write(str);
} }

View File

@ -16,6 +16,8 @@
*/ */
package org.jivesoftware.smack.util; package org.jivesoftware.smack.util;
import java.io.IOException;
import java.io.Writer;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
@ -459,4 +461,23 @@ public class XmlStringBuilder implements Appendable, CharSequence {
public int hashCode() { public int hashCode() {
return toString().hashCode(); return toString().hashCode();
} }
/**
* Write the contents of this <code>XmlStringBuilder</code> to a {@link Writer}. This will write
* the single parts one-by-one, avoiding allocation of a big continuous memory block holding the
* XmlStringBuilder contents.
*
* @param writer
* @throws IOException
*/
public void write(Writer writer) throws IOException {
for (CharSequence csq : sb.getAsList()) {
if (csq instanceof XmlStringBuilder) {
((XmlStringBuilder) csq).write(writer);
}
else {
writer.write(csq.toString());
}
}
}
} }

View File

@ -73,6 +73,7 @@ import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TLSUtils; import org.jivesoftware.smack.util.TLSUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smack.util.dns.HostAddress; import org.jivesoftware.smack.util.dns.HostAddress;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.jid.parts.Resourcepart;
@ -1316,7 +1317,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
} }
writer.write(element.toXML().toString());
CharSequence elementXml = element.toXML();
if (elementXml instanceof XmlStringBuilder) {
((XmlStringBuilder) elementXml).write(writer);
}
else {
writer.write(elementXml.toString());
}
if (queue.isEmpty()) { if (queue.isEmpty()) {
writer.flush(); writer.flush();
} }