1

現在、多数 (60k ~ 100k) の比較的大きなファイルへのランダム アクセスを必要とするアプリケーションを開発しています。ストリームを開いたり閉じたりするのはかなりコストのかかる操作であるため、必要がなくなるまで、最大のファイルの FileChannel を開いたままにしておくことをお勧めします。

問題は、この種の動作が Java 7 の try-with ステートメントでカバーされていないため、すべての FileChannel を手動で閉じる必要があることです。しかし、ソフトウェア全体で同じファイルに同時にアクセスできるため、これはますます複雑になりすぎています。

登録されたパスごとに開いている FileChannel インスタンスを追跡できる ChannelPool クラスを実装しました。次に ChannelPool を発行して、パスが特定の間隔でプール自体によって弱く参照されるだけのチャネルを閉じることができます。私はイベントリスナーのアプローチを好みますが、GC をリッスンする必要もありません。

チャネルを手動で閉じる必要があるため、Apache Commonsの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;
    }
}
4

1 に答える 1

1

ここを見てください:

http://code.google.com/p/guava-libraries/wiki/CachesExplained

LoadingCache有効期限が切れたときにチャネルを閉じる削除リスナーでを使用できます。また、アクセスまたは書き込み後に有効期限を指定できます。

于 2013-01-06T20:39:44.730 に答える