파일에 로그를 남긴다거나 데이타를 쓰기위해서 java.io.File 객체를 써도 되지만, 성능을 위해서 BufferedWriter를 많이 이용하게 됩니다. 특히 로그 라이브러리나 로깅 유틸리티 클래스에서 거의 대부분 사용할 것으로 생각이 드네요..
그래서, 생각한 방법이 BufferedWriter를 캐싱하는 것입니다.

아래 코드의 shutdown() 메쏘드는 ShudownHooking으로 등록된 쓰레드가 프로세스가 종료되면 호출을 해서 버퍼에 남아있는 내용을 기록하도록 되어 있습니다.

BufferedWriterCacheUtility.java
package net.sjava.io.test;

import java.util.Map;
import java.util.LinkedHashMap;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.util.Iterator;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BufferedWriterCacheUtility {
   
    /** max cache size */
    private static final int size = 10;
    /** lock */
    private static Lock lock = new ReentrantLock();
   
    /** initialized lru cache */
    private static Map<String, BufferedWriter> cache = new LinkedHashMap<String, BufferedWriter> (size, .75F, true) {
        private static final long serialVersionUID = 1L;
        protected boolean removeEldestEntry(Map.Entry<String, BufferedWriter> eldest) {
            if(size() > size) {
                try {
                    // flush and close before deleted
                    eldest.getValue().close();
                } catch(java.io.IOException e) {
                    // ignore
                }
            }
           
            return size() > size;  
        }
    };

   
    /**
     *
     * @param fileName
     * @return
     */
    public static BufferedWriter createBufferedWriter(String fileName) {
        lock.lock();
        try{
            if(cache.containsKey(fileName))
                return cache.get(fileName);
       
            return new BufferedWriter(new FileWriter(fileName, true), 1024);
        } catch(java.io.IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            lock.unlock();
        }
    }
   
    /**
     *
     * @param fileName
     * @param writer
     */
    public static void close(String fileName, BufferedWriter writer) {
        lock.lock();
        try {
            cache.put(fileName, writer);
        } finally {
            lock.unlock();
        }
    }
   
    /** shutdown hooking thread call */
    public static void shutdown() {
        // 아래의 코드를 통해서 종료가 되더라도 다 flush 하고 종료한다.     
        Iterator<BufferedWriter> iter = null;
        try {
            iter = cache.values().iterator();
            while (iter.hasNext()){
                iter.next().close();
                //iter.next().close();
            }
           
        }catch(java.io.IOException e) {
            e.printStackTrace();
        }
    }
}

아래 코드는 BufferedWriter를 사용하는 방법에 대한 3가지의 예입니다.
물론 2번째(bwriter02)를 사용하는 예가 가장 빠르겠지만, 3번째 예는 여러개의 fileName을 가지고 file write를 하기에 가장 좋은 방법으로 생각이 듭니다.

BufferedWriterTest.java
package net.sjava.io.test;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

public class BufferedWriterTest {

    private static SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS");

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //load();
               
        System.out.println("s - " + format.format(new Date()));
       
        BufferedWriter bwriter01 = null;
        try {
            for(int i=0; i < 100000; i++) {
                bwriter01 = new BufferedWriter(new FileWriter("c:\\test01.txt", true), 1024);
                bwriter01.write("aaaaaaaaaaaaccccccccccccccccccaaa");
                bwriter01.newLine();
                bwriter01.flush();
                bwriter01.close();
            }
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
        System.out.println("e - " + format.format(new Date()));
        
        System.out.println("s - " + format.format(new Date()));
        BufferedWriter bwriter02 = null;
        try {
            bwriter02 = new BufferedWriter(new FileWriter("c:\\test02.txt", true), 1024);
            for(int i=0; i < 100000; i++) {
                bwriter02.write("aaaaaaaaaaaaccccccccccccccccccaaa");
                bwriter02.newLine();
            }
            bwriter02.flush();
            bwriter02.close();
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
        System.out.println("e - " + format.format(new Date()));        
        
        // 날짜별 혹은 서비스별로 로깅을 해야 될경우
        System.out.println("s - " + format.format(new Date()));
        BufferedWriter bwriter03 = null;
        try {
            for(int i=0; i < 100000; i++) {
                bwriter03 = BufferedWriterCacheUtility.createBufferedWriter("c:\\test03.txt");   
                bwriter03.write("aaaaaaaaaaaaccccccccccccccccccaaa");
                bwriter03.newLine();
                BufferedWriterCacheUtility.close("c:\\test03.txt", bwriter03);
            }
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
       
        System.out.println("e - " + format.format(new Date()));
    }

}

여담이지만, java.io.BufferedWriter 소스를 보면, write시에 lock을 잡고 씁니다.
아래와 같은 코드를 많이 사용하지만요..
lock.lock();
try {
 bWriter.write("000000000000000");
}catch(java.io.IOException e) {
  e.printStackTrace();
} finally {
  lock.unlock();
}
위 코드보다는 아래코드를 사용하는게 맞겠습니다.
try {
 bWriter.write("000000000000000");
}catch(java.io.IOException e) {
  e.printStackTrace();
}

흠.. 로그 처리를 위한 파일에 스트링을 추가하는 클래스를 BufferedWriter로 구현을 하다가 갑자기 BufferedOutputStream으로 하는것과의 차이점을 알고 싶어서 아래의 코드로 차이를 살펴봤습니다.
아래의 BufferWriterTest, BufferOutputStream 클래스의 buffersize는 1024로 동일합니다.

BufferWriterTest.java
import java.util.*;
import java.text.*;
import java.io.*;

public class BufferWriterTest {
    //
    static DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
   
    static String txt = "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하마바사아자차타파하";
   
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Date start = new Date();   
        System.out.println("s - " + df.format(start));
       
        BufferedWriter bufferWriter;
       
        try {
           
            bufferWriter = new BufferedWriter(new FileWriter("c:\\BufferedWriter.txt", true), 1024);
           
            for(int i=0; i < 5000000; i++) {
                bufferWriter.write(BufferWriterTest.txt);
                bufferWriter.newLine();
            }
           
            bufferWriter.flush();
            bufferWriter.close();
           
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        Date end = new Date();   
        System.out.println("e - " + df.format(end));
    }
}

실행화면(3.15G 파일 쓰기)

s - 2009-06-19 15:55:00:968
e - 2009-06-19 15:55:48:280

BufferOutputStream.java
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;

public class BufferOutputStream {
    //
    static DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
   
    static String txt = "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하가나다라마바사아자차타파하" +
            "가나다라마바사아자차타파하마바사아자차타파하";
   
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Date start = new Date();   
        System.out.println("s - " + df.format(start));
               
        BufferedOutputStream bufferedOutput;
  
        try {
            bufferedOutput = new BufferedOutputStream(new FileOutputStream("c:\\BufferedStream.txt", true), 1024);
           
            for(int i=0; i < 5000000; i++) {
                bufferedOutput.write(txt.getBytes());
                bufferedOutput.write("\n".getBytes());
            }
           
            bufferedOutput.close();
           
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        Date end = new Date();   
        System.out.println("e - " + df.format(end));
    }

}

실행화면(3.10G 파일 쓰기)

s - 2009-06-19 15:56:41:889
e - 2009-06-19 15:58:19:825

위 결과로 보면, 비슷한 사이즈의 파일을 추가할 경우에 실행속도가 꽤 차이가 나네요.. ^^;;
그냥 느낌에는 비슷한 실행시간이 될 것으로 생각이 들었는데요..
아래의 구조때문에 그럴까요?? 혹시 아시는 분은 조언을.. 

BufferedWriter BufferedOutputStream
java.lang.Object
  - java.io.Writer
    - java.io.BufferedWriter
java.lang.Object
 - java.io.OutputStream
   - java.io.FilterOutputStream
     - java.io.BufferedOutputStream






File 내용 추가하기..

from Java 2008/09/24 15:34
간단하게 아래의 코드로 File의 내용을 추가합니다.

try
{
//ask user for file name to write to

FileWriter out = new FileWriter("out.txt", true);
BufferedWriter writer = new BufferedWriter(out, 64); // buffersize를 줘서 성능을 높이자..
writer.write("fooooooooooooooooooooooo~~");
writer.newLine();
writer.flush();
writer.close();
}

위의 FileWriter out = new FileWriter("out.txt", true); 에서 out.txt 파일이 없을경우 생성을 한다고 합니다. ^^
FileWriter의 생성자의 true 인자를 통해서 파일에 추가를 하시고, BufferedWriter는 buffersize를 줘서 성능을 높이시면 됩니다.  ^^