2013-01-06 97 views
1

我目前正在開發一個需要隨機訪問許多(60K-100K)相對較大的文件的應用程序。 由於開閉流是一個相當昂貴的操作,我更願意保持開放的最大文件FileChannels,直到他們不再需要。有沒有辦法讓FileChannel自動關閉?

的問題是,由於這種行爲是不包括在Java 7中的嘗試-with語句,我需要手動關閉所有FileChannels。 但是,這變得越來越複雜,因爲可以在整個軟件中同時訪問相同的文件。

我實現了一個ChannelPool類,它可以保持打開FileChannel情況的跟蹤爲每個註冊的路徑。然後可以發佈ChannelPool來關閉這些通道,這些通道僅在某些時間間隔內由池本身弱引用。 我更喜歡事件監聽器方法,但我寧願不必聽GC。

從阿帕奇百科全書的FileChannelPool沒有解決我的問題,因爲渠道仍需要手動關閉。

有沒有更優雅的解決方案來解決這個問題?如果不是,我的實施如何改進?

import java.io.IOException; 
import java.lang.ref.WeakReference; 
import java.nio.channels.FileChannel; 
import java.nio.file.Path; 
import java.nio.file.StandardOpenOption; 
import java.util.Map; 
import java.util.Timer; 
import java.util.TimerTask; 
import java.util.concurrent.ConcurrentHashMap; 

public class ChannelPool { 

    private static final ChannelPool defaultInstance = new ChannelPool(); 

    private final ConcurrentHashMap<String, ChannelRef> channels; 
    private final Timer timer; 

    private ChannelPool(){ 
     channels = new ConcurrentHashMap<>(); 
     timer = new Timer(); 
    } 

    public static ChannelPool getDefault(){ 
     return defaultInstance; 
    } 

    public void initCleanUp(){ 
     // wait 2 seconds then repeat clean-up every 10 seconds. 
     timer.schedule(new CleanUpTask(this), 2000, 10000); 
    } 

    public void shutDown(){ 
     // must be called manually. 
     timer.cancel(); 
     closeAll(); 
    } 

    public FileChannel getChannel(Path path){ 
     ChannelRef cref = channels.get(path.toString()); 
     System.out.println("getChannel called " + channels.size()); 

     if (cref == null){ 
      cref = ChannelRef.newInstance(path); 
      if (cref == null){ 
       // failed to open channel 
       return null; 
      } 
      ChannelRef oldRef = channels.putIfAbsent(path.toString(), cref); 
      if (oldRef != null){ 
       try{ 
        // close new channel and let GC dispose of it 
        cref.channel().close(); 
        System.out.println("redundant channel closed"); 
       } 
       catch (IOException ex) {} 
       cref = oldRef; 
      } 
     } 
     return cref.channel(); 
    } 

    private void remove(String str) { 
     ChannelRef ref = channels.remove(str); 
     if (ref != null){ 
      try { 
       ref.channel().close(); 
       System.out.println("old channel closed"); 
      } 
      catch (IOException ex) {} 
     } 
    } 

    private void closeAll() { 
     for (Map.Entry<String, ChannelRef> e : channels.entrySet()){ 
      remove(e.getKey()); 
     } 
    } 

    private void maintain() { 
     // close channels for derefenced paths 
     for (Map.Entry<String, ChannelRef> e : channels.entrySet()){ 
      ChannelRef ref = e.getValue(); 
      if (ref != null){ 
       Path p = ref.pathRef().get(); 
       if (p == null){ 
        // gc'd 
        remove(e.getKey()); 
       } 
      } 
     } 
    } 

    private static class ChannelRef{ 

     private FileChannel channel; 
     private WeakReference<Path> ref; 

     private ChannelRef(FileChannel channel, WeakReference<Path> ref) { 
      this.channel = channel; 
      this.ref = ref; 
     } 

     private static ChannelRef newInstance(Path path) { 
      FileChannel fc; 
      try { 
       fc = FileChannel.open(path, StandardOpenOption.READ); 
      } 
      catch (IOException ex) { 
       return null; 
      } 
      return new ChannelRef(fc, new WeakReference<>(path)); 

     } 

     private FileChannel channel() { 
      return channel; 
     } 

     private WeakReference<Path> pathRef() { 
      return ref; 
     } 
    } 

    private static class CleanUpTask extends TimerTask { 

     private ChannelPool pool; 

     private CleanUpTask(ChannelPool pool){ 
      super(); 
      this.pool = pool; 
     } 

     @Override 
     public void run() { 
      pool.maintain(); 
      pool.printState(); 
     } 
    } 

    private void printState(){ 
     System.out.println("Clean up performed. " + channels.size() + " channels remain. -- " + System.currentTimeMillis()); 
     for (Map.Entry<String, ChannelRef> e : channels.entrySet()){ 
      ChannelRef cref = e.getValue(); 
      String out = "open: " + cref.channel().isOpen() + " - " + cref.channel().toString(); 
      System.out.println(out); 
     } 
    } 

} 

編輯: 由於fge的答案,我有我現在需要什麼。謝謝!

import com.google.common.cache.CacheBuilder; 
import com.google.common.cache.CacheLoader; 
import com.google.common.cache.LoadingCache; 
import com.google.common.cache.RemovalListener; 
import com.google.common.cache.RemovalNotification; 
import java.io.IOException; 
import java.nio.channels.FileChannel; 
import java.nio.file.Path; 
import java.nio.file.StandardOpenOption; 
import java.util.concurrent.ExecutionException; 

public class Channels { 

    private static final LoadingCache<Path, FileChannel> channelCache = 
      CacheBuilder.newBuilder() 
      .weakKeys() 
      .removalListener(
       new RemovalListener<Path, FileChannel>(){ 
        @Override 
        public void onRemoval(RemovalNotification<Path, FileChannel> removal) { 
         FileChannel fc = removal.getValue(); 
         try { 
          fc.close(); 
         } 
         catch (IOException ex) {} 
        } 
       } 
      ) 
      .build(
       new CacheLoader<Path, FileChannel>() { 
        @Override 
        public FileChannel load(Path path) throws IOException { 
         return FileChannel.open(path, StandardOpenOption.READ); 
        } 
       } 
      ); 

    public static FileChannel get(Path path){ 
     try { 
      return channelCache.get(path); 
     } 
     catch (ExecutionException ex){} 
     return null; 
    } 
} 
+0

也許你可以使用來自Guava的'LoadingCache'?它具有刪除監聽器,寫/訪問後到期等 – fge

+0

我不會有多個線程同時訪問文件在第一個地方。我會有一個線程從他們那裏提取你需要的信息。只需加載一次100K文件就足夠耗時。如果你的硬盤速度很慢,這對性能來說是一場噩夢。 –

+2

注意:除非你有一個非常快的驅動器,多線程不會讓它跑得更快,所以你可能也只使用一個線程,讀取一個文件的時間和避免所有這些問題。 –

回答

相關問題