2011-09-12 25 views
3

我正在尋找從紀元毫秒長的日期格式化程序的Java實現。我不想使用SimpleDateFormatter,因爲它會向GC產生垃圾。我正在尋找一種快速且無垃圾的Java實現。有沒有人見過這個地方?將數據格式化爲毫秒String WITHOUT SimpleDateFormatter(產生垃圾)

StringBuilder sb = getReusableStringBuilder(); 

parse(sb, System.currentTimeMillis()); 

編輯:這是一個日誌庫,所以它必須包括時間以及。

+3

如果您不想產生垃圾,請勿使用垃圾收集語言。你爲什麼關心SimpleDateFormat產生的垃圾? –

+0

@JB Nizet:我沒有這個選項。我必須使用Java,並且必須儘量減少GC延遲。這對於低延遲行業來說並不罕見。 – TraderJoeChicago

+0

@JB Nizet,如果您的應用程序少於1%需要不使用GC,您是否會更改其他99%以滿足此要求? –

回答

-1

該解決方案比我想象的要簡單得多:bootstrap FieldPosition,因此它不會泄漏內存。

+0

實現格式化邏輯是一個痛苦。 – TraderJoeChicago

+0

因爲沒有人指示我執行格式化邏輯的代碼,所以如果沒有其他內容出現,我將選擇此答案。 – TraderJoeChicago

3

這是一個背景記錄器,它記錄時間/日期和一個StringBuilder完全記錄在後臺。典型的延遲低於每次通話1微秒。它回收所有東西,所以不會產生GC。

這比使用隊列在兩個線程之間傳遞工作要高效得多。不幸的是,所有隊列實現都會創建垃圾。 :(

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. 
    } 
} 

我寫了一個。

如果允許的垃圾一點點,你可以簡化你的問題。你可以使用SimpleDateFormatter每次改變時間格式的日期(即一旦注意:通過創建一個字符串,你仍然在生產垃圾(一個String和一個char [],即使你沒有使用一個StringBuilder,這是非常棘手的)。

我會附加到一個循環的ByteBuffer以避免任何GC。 (除了在午夜)


作爲@Joachim碟表明格式化可用於產生較少的垃圾。我懷疑,除非你也放棄生產一個字符串,它不會有太大的區別。

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"); 
} 

打印

SDF.format used an average of 24 bytes 
SDF.format with a String used an average of 120 bytes 
+0

我會使用StringBuilder來產生字符串,所以格式化邏輯會寫入到StringBuilder中。我正在改變我的問題來說清楚。 – TraderJoeChicago

+1

你的方法很酷,彼得。我不介意緩存日期,只計算每個日誌上的時間。但時間邏輯可能是一種痛苦,特別是日間儲蓄,時區等。在開始自己編寫代碼之前,我想花一些時間來看看能否找到已經完成的東西。 – TraderJoeChicago

+0

重複使用FieldPosition可將平均內存使用量減少到24個字節(少32字節!)。它可能會保存,因爲你已經共享了'SimpleDateFormat'和'StringBuffer',所以它需要以某種方式同步。 –

-3

當你需要處理任何日期和時間相關的始終使用Joda Time。它還包含格式化程序。

+4

也許你可以解釋如何使用它,因此它會產生最小的GC。 –

0

我爲此創建了帶DateTimes實用程序類的庫。它在內部從1970/01/01 00:00:00.000獲得毫秒的「長值」,並計算年,月,日,小時,分鐘,秒和毫秒值。然後它將此信息作爲ASCII字符串放入提供的字節數組中,GC沒有新對象。可以使用System.out.write()方法將此字節數組打印到控制檯而不創建新的String對象。

您可以從我的網站here獲得庫文件作爲jar文件。文章描述了使用情況並比較了性能。