簡體   English   中英

從Java中的多個線程寫入FileOutputStream

[英]Write to FileOutputStream from multiple threads in Java

從多個線程的Java FileOutputStream對象調用write是否安全? 輸出是否會正確序列化?

澄清:

在我的例子中,類記錄器包含一個FileOutputStream引用,多個線程可以調用logger write,它格式化輸出並調用FileOutputStream寫入。

我是否應該同步我的記錄器寫入方法以保證來自多個線程的消息不會混合?

以下是使用java nio FileChannel的同步記錄器的簡單實現。 在此示例中,日志消息限制為1024個字節。 您可以通過更改BUFFER_SIZE值來調整日志消息長度。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;

/**
 *  The MyLogger class abstracts the writing of log messages to a file.
 *  This is a synchronized implementation due to the usage of java.nio.channels.FileChannel 
 *  which is used to write log messages to the log file.
 *  
 *  The MyLogger class maintains a HashMap of MyLogger instances per log file.  
 *  The Key is the MD5 hash of the log file path and the Value is the MyLogger instance for that log file.
 *
 */
public final class MyLogger {
    private static final int BUFFER_SIZE = 1024;
    private static final int DIGEST_BASE_RADIX = 16;
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    private static HashMap<String, MyLogger> sLoggerMap;

    private FileChannel mLogOutputChannel;
    private ByteBuffer mByteBuffer;
    private String mLogDir;
    private String mLogFileName;

    /**
     * Private constructor which creates our log dir and log file if they do not already already exist. 
     * If the log file exists, then it is opened in append mode.
     * 
     * @param logDir
     *            The dir where the log file resides
     * @param logFileName
     *            The file name of the log file
     * @throws IOException
     *             Thrown if the file could not be created or opened for writing.
     */
    private MyLogger(String logDir, String logFileName) throws IOException {
        mLogDir = logDir;
        mLogFileName = logFileName;

        // create the log dir and log file if they do not exist
        FileOutputStream logFile;
        new File(mLogDir).mkdirs();

        final String logFilePath = mLogDir + File.separatorChar + mLogFileName;
        final File f = new File(logFilePath);
        if(!f.exists()) {
            f.createNewFile();
        }
        logFile = new FileOutputStream(logFilePath, true);

        // set up our output channel and byte buffer  
        mLogOutputChannel = logFile.getChannel();
        mByteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
    }

    /**
     * Writes the given log message to the log file that is represented by this MyLogger instance. 
     * If the log message could not be written to the log file an error is logged in the System log.
     * 
     * @param logMessage
     *            The log message to write to the log file.
     */
    public void log(String logMessage) {

        // write the log message to the log file
        if (mLogOutputChannel != null) {
            mByteBuffer.put(logMessage.getBytes());
            mByteBuffer.put(LINE_SEPARATOR.getBytes());
            mByteBuffer.flip();
            try {
                mLogOutputChannel.write(mByteBuffer);
                // ensure that the data we just wrote to the log file is pushed to the disk right away
                mLogOutputChannel.force(true);
            } catch (IOException e) {
                // Could not write to log file output channel
                e.printStackTrace();
                return;
            }
        }

        if(mByteBuffer != null) {
            mByteBuffer.clear();
        }
    }

    /**
     * Get an instance of the MyLogger for the given log file. Passing in the same logDir and logFileName will result in the same MyLogger instance being returned.
     * 
     * @param logDir
     *            The directory path where the log file resides. Cannot be empty or null.
     * @param logFileName
     *            The name of the log file Cannot be empty or null.
     * @return The instance of the MyLogger representing the given log file. Null is returned if either logDir or logFilename is null or empty string.
     * @throws IOException
     *             Thrown if the file could not be created or opened for writing.
     */
    public static MyLogger getLog(String logDir, String logFileName) throws IOException {
        if(logDir == null || logFileName == null || logDir.isEmpty() || logFileName.isEmpty()) {
            return null;
        }

        if(sLoggerMap == null) {
            sLoggerMap = new HashMap<String, MyLogger>();
        }

        final String logFilePathHash = getHash(logDir + File.separatorChar + logFileName);
        if(!sLoggerMap.containsKey(logFilePathHash)) {
            sLoggerMap.put(logFilePathHash, new MyLogger(logDir, logFileName));
        }

        return sLoggerMap.get(logFilePathHash);
    }

    /**
     * Utility method for generating an MD5 hash from the given string.
     * 
     * @param path
     *            The file path to our log file
     * @return An MD5 hash of the log file path. If an MD5 hash could not be generated, the path string is returned.
     */
    private static String getHash(String path) {
        try {
            final MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(path.getBytes());
            return new BigInteger(digest.digest()).toString(DIGEST_BASE_RADIX);
        } catch (NoSuchAlgorithmException ex) {
            // this should never happen, but just to make sure return the path string
            return path;
        }
    }
}

這是你如何使用它:

MyLogger aLogger = MyLogger.getLog("/path/to/log/dir", "logFilename");
aLogger.log("my log message");

write-mode ,文件不能多次打開,所以答案是否定的。

看到您的編輯后,是的,您應該在記錄器中引入同步,以確保一次只能訪問一個流。 只是一個建議,你為什么不去Log4J 它已經處理了你的用例。

不,Java不支持從多個線程流式傳輸到同一個流。

如果您想使用線程流,請查看此站點: http//lifeinide.com/post/2011-05-25-threaded-iostreams-in-java/

他解釋得很好,並為ThreadedOutputStream提供了一些示例代碼, ThreadedOutputStream您的需求。

如果要保持排序(即輸出流中的消息1出現在消息2之前),則必須鎖定流。 這反過來又降低了並發性。 (所有線程將在鎖的/信號量隊列中排隊並等待流可供他們使用)

如果您只對同時寫入流並且不關心排序感興趣,則可以為每個線程提供緩沖區。 每個線程都寫入自己的緩沖區。 當緩沖區已滿時,它會獲取流上的鎖(可能涉及等待鎖)並將其內容清空到流中。

編輯:我剛剛意識到,如果你關心訂購並仍然想要多線程,如果你也在unix格式的輸出流中寫出時間(作為一個長)。 將流刷新到其他容器后,可以根據時間對內容進行排序,您應該有一個有序文件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM