2011-12-07 29 views
8

Có an toàn không khi gọi write trên Java FileOutputStream đối tượng tạo thành nhiều chuỗi? Đầu ra sẽ được tuần tự đúng không?Ghi vào FileOutputStream từ nhiều chủ đề trong Java

làm rõ:

Trong trường hợp của tôi logger lớp giữ một tham chiếu FileOutputStream, và nhiều chủ đề có thể gọi logger ghi, mà định dạng đầu ra và gọi FileOutputStream ghi.

Tôi có nên đồng bộ hóa phương pháp ghi nhật ký của mình để đảm bảo rằng các thư từ nhiều chuỗi không được trộn lẫn không?

+2

bạn có thể muốn xem xét [FileChannel] (http://docs.oracle.com/javase/6/docs/api/java /nio/channels/FileChannel.html) – Nerdtron

+0

Câu trả lời thứ hai của Nerdtron. Giải pháp FileChannel nio của Java là cách đơn giản nhất để thực hiện. –

Trả lời

3

Không thể mở tệp nhiều lần trong write-mode, vì vậy, câu trả lời là không.

Sau khi xem chỉnh sửa của bạn, có bạn nên giới thiệu đồng bộ hóa vào trình ghi nhật ký của mình để đảm bảo luồng chỉ được truy cập bởi một chuỗi tại một thời điểm. Chỉ là một gợi ý, tại sao bạn không đi cho Log4J? Nó đã xử lý trường hợp sử dụng của bạn.

+0

Trong trường hợp của tôi trình ghi log lớp chứa tham chiếu FileOutputStream và nhiều luồng có thể gọi logger write, định dạng đầu ra và gọi FileOutputStream ghi –

+0

@ José Chỉ cần chỉnh sửa câu trả lời của tôi, hãy xem nó – GETah

+0

Về log4j, đó là một phần của một thư viện, có một giao diện đăng nhập và thực hiện đơn giản chỉ cần ghi vào một tập tin, các ứng dụng vẫn có thể sử dụng log4J hoặc những người khác, nhưng tôi không muốn ép buộc sự phụ thuộc này cho các trường hợp đơn giản. –

2

No. Java không hỗ trợ phát trực tuyến tới cùng một luồng từ nhiều luồng.

Nếu bạn muốn làm sử dụng suối luồng, hãy kiểm tra trang web này: http://lifeinide.com/post/2011-05-25-threaded-iostreams-in-java/

Ông giải thích những điều tốt và có một số mẫu mã cho một ThreadedOutputStream, mà sẽ làm những gì bạn muốn.

1

Nếu bạn muốn tiếp tục đặt hàng (tức là tin nhắn 1 trong luồng đầu ra đến trước tin nhắn 2), bạn phải khóa luồng. Điều này lần lượt làm giảm đồng thời. (Tất cả các chủ đề sẽ được enqueued trong hàng đợi của khóa/semaphore và chờ đợi cho dòng để trở thành có sẵn cho họ)

Nếu bạn quan tâm chỉ bằng văn bản cho một dòng đồng thời và không quan tâm đến đặt hàng, bạn có thể có bộ đệm cho mỗi chuỗi. Mỗi luồng ghi vào bộ đệm riêng của nó. Khi bộ đệm đầy, nó sẽ lấy một khóa (có thể liên quan đến việc chờ khóa) trên luồng và làm trống nội dung của nó vào luồng.

Chỉnh sửa: Tôi chỉ nhận ra rằng, nếu bạn quan tâm đến việc đặt hàng và vẫn muốn đa luồng, nếu bạn cũng viết thời gian trong luồng đầu ra ở định dạng unix (như một thời gian dài). Sau khi dòng được flushed lên một số container khác, các nội dung có thể được sắp xếp dựa trên thời gian và bạn nên có một tập tin theo thứ tự.

4

Dưới đây là triển khai đơn giản của trình ghi nhật ký được đồng bộ hóa bằng cách sử dụng java nio FileChannel. Trong ví dụ này, các thông điệp tường trình được giới hạn ở 1024 byte. Bạn có thể điều chỉnh độ dài thông điệp tường trình bằng cách thay đổi giá trị 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; 
     } 
    } 
} 

Đây là cách bạn sẽ sử dụng nó:

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

Vâng đó là ngoài phạm vi cho câu hỏi, tôi chỉ hỏi nếu FileOutputStream.write được đồng bộ hóa. –

Các vấn đề liên quan