Formatting milliseconds to date String WITHOUT SimpleDateFormatter (which produces garbage)
I am looking for a Java implementation of a date formatter from an epoch milliseconds long. I do NOT want to use SimpleDateFormatter because it produces garbage to the GC. I am looking for a fast and garbage-free Java implementation. Has anyone seen that somewhere?
StringBuilder sb = getReusableStringBuilder();
parse(sb, System.currentTimeMillis());
EDIT: That's for a logging library so it must inclu开发者_JAVA百科de the time as well.
Here is a background logger which records time/dates and a StringBuilder to log entirely in the background. The typical latency is below one micro-second per call. It recycles everything so no GC is produced.
This is much more efficient than using a queue to pass work between two threads. All the queue implementations create garbage unfortunately. :(
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class BackgroundLogger implements Runnable {
static final int ENTRIES = 64;
static class LogEntry {
long time;
int level;
final StringBuilder text = new StringBuilder();
}
static class LogEntries {
final LogEntry[] lines = new LogEntry[ENTRIES];
int used = 0;
}
private final ExecutorService executor = Executors.newSingleThreadExecutor();
final Exchanger<LogEntries> logEntriesExchanger = new Exchanger<LogEntries>();
LogEntries entries = new LogEntries();
BackgroundLogger() {
executor.submit(this);
}
// put whatever you want in the StringBuilder, before the next call!
public StringBuilder log(int level) {
try {
if (entries.used == ENTRIES)
entries = logEntriesExchanger.exchange(entries);
LogEntry le = entries.lines[entries.used++];
le.time = System.currentTimeMillis();
le.level = level;
return le.text;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void flush() throws InterruptedException {
entries = logEntriesExchanger.exchange(entries);
entries = logEntriesExchanger.exchange(entries);
}
public void stop() {
try {
flush();
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdownNow();
}
@Override
public void run() {
LogEntries entries = new LogEntries();
try {
while(!Thread.interrupted()) {
entries = logEntriesExchanger.exchange(entries);
for (int i = 0; i < entries.used; i++) {
bgLog(entries.lines[i]);
entries.lines[i].text.delete(0, entries.lines[i].text.length());
}
entries.used = 0;
}
} catch (InterruptedException ignored) {
} finally {
System.out.println("logger stopping.");
}
}
private void bgLog(LogEntry line) {
// log the entry to a file.
}
}
I have written one.
If you allow a little bit of garbage you can simplify your problem. You can use SimpleDateFormatter to format the date each time it changes (i.e. once per day) and produce the time and seconds using division.
Note: by creating a String you are still producing garbage (a String, and a char[] even if you don't use a StringBuilder which is tricky).
I would append to a recycled ByteBuffer to avoid any GC. (except at around midnight)
As @Joachim Saucer suggests the formatter can be used to produce less garbage. I suspect that unless you also drop the production of a String, its not going to make much difference.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
StringBuffer sb = new StringBuffer();
Date tmpDate = new Date();
final FieldPosition pos = new FieldPosition(0);
{
long free1 = Runtime.getRuntime().freeMemory();
for (int i = 0; i < 1000; i++) {
tmpDate.setTime(System.currentTimeMillis());
sdf.format(tmpDate, sb, pos);
sb.delete(0, sb.length());
}
long free2 = Runtime.getRuntime().freeMemory();
if (free1 == free2) throw new Error("This must be run with -XX:-UseTLAB");
System.out.println("SDF.format used an average of " + (free1 - free2) / 1000 + " bytes");
}
{
long free1 = Runtime.getRuntime().freeMemory();
for (int i = 0; i < 1000; i++) {
tmpDate.setTime(System.currentTimeMillis());
sdf.format(tmpDate, sb, pos);
String str = sb.toString();
sb.delete(0, sb.length());
}
long free2 = Runtime.getRuntime().freeMemory();
if (free1 == free2) throw new Error("This must be run with -XX:-UseTLAB");
System.out.println("SDF.format with a String used an average of " + (free1 - free2) / 1000 + " bytes");
}
prints
SDF.format used an average of 24 bytes
SDF.format with a String used an average of 120 bytes
I created the library with DateTimes utility class for this purpose. Internally it takes the "long value" of milliseconds since 1970/01/01 00:00:00.000 and calculates the year, month, day, hour, minute, second and millisecond values. Then it puts this info as an ASCII string into provided byte array, no new objects for GC. This byte array can be printed to console with System.out.write() method without creating a new String object.
You can get the library as jar file from my website here. Article describes the usage and compares the performance.
The solution was much simpler than I thought: bootstrap FieldPosition so it does not leak memory.
Always use Joda Time when you need to handle anything date and time related. It also contains formatters.
精彩评论