2009-04-27 4 views
1

私の問題は次のようなものです。"buckets"でファイルを整理する

私はクライアントサイドのHTTPキャッシュを作成しています。何とかファイルシステムにHTTPペイロードを保存する必要があります。私は不要なファイルでファイルシステムを混乱させたくありません。

私はこのクラスを書いた:

 

/* 
* Copyright (c) 2008, The Codehaus. All Rights Reserved. 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* http://www.apache.org/licenses/LICENSE-2.0 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
* 
*/ 

package org.codehaus.httpcache4j.cache; 

import org.apache.commons.lang.Validate; 
import org.apache.commons.io.filefilter.AndFileFilter; 
import org.apache.commons.io.filefilter.DirectoryFileFilter; 
import org.apache.commons.io.filefilter.RegexFileFilter; 

import org.codehaus.httpcache4j.util.DeletingFileFilter; 

import java.io.File; 
import java.io.FileFilter; 
import java.io.Serializable; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.List; 

/** 
* This class is internal and should never be used by clients. 
* 
* Responsible for creating and maintaining a "Pool" of file generations. 
* The files are promoted when they are accessed, so we can figure out which files that are OK to delete.
* Known Gotchas: This needs to be in sync with the size of the storage engine.
* If you have too few generations when you have many items in the cache, you might * be missing some files when you try to access them. * * Note from Despot: I am looking into another way of storing files, so this class might go away at some point, * or change to a different form. * */ class FileGenerationManager implements Serializable{ private static final long serialVersionUID = -1558644426181861334L; private final File baseDirectory; private final int generationSize; private final int numberOfGenerations; private final FileFilter generationFilter; public FileGenerationManager(final File baseDirectory, final int numberOfGenerations) { this(baseDirectory, numberOfGenerations, 100); } public FileGenerationManager(final File baseDirectory, final int numberOfGenerations, final int generationSize) { Validate.isTrue(numberOfGenerations > 0, "You may not create 0 generations"); Validate.notNull(baseDirectory, "You may not have a null base directory"); if (!baseDirectory.exists()) { Validate.isTrue(baseDirectory.mkdirs(), "Could not create base directory: " + baseDirectory); } this.baseDirectory = baseDirectory; this.generationSize = generationSize; this.numberOfGenerations = numberOfGenerations; generationFilter = new AndFileFilter(DirectoryFileFilter.DIRECTORY, new RegexFileFilter("[0-9]*")); getGenerations(); } /** * Creates generations of the directories in the base directory. * * @return the created generations. */ //TODO: Is this heavy? //TODO: Maybe we should do this when we miss in getFile() ? public synchronized List getGenerations() { final List generations = new ArrayList(); //handle existing generations... File[] directories = baseDirectory.listFiles(generationFilter); if (directories.length > 0) { for (File directory : directories) { generations.add(new Generation(baseDirectory, Integer.parseInt(directory.getName()))); } } else { generations.add(new Generation(baseDirectory, 1)); } Collections.sort(generations); Generation currentGeneration = generations.get(0); if (currentGeneration.getGenerationDirectory().list().length > generationSize) { generations.add(0, new Generation(baseDirectory, currentGeneration.getSequence() + 1)); removeLastGeneration(generations); } while (generations.size() > numberOfGenerations) { removeLastGeneration(generations); } return Collections.unmodifiableList(generations); } private void removeLastGeneration(List generations) { if (generations.size() > numberOfGenerations) { Generation generation = generations.remove(generations.size() - 1); generation.delete(); } } /** * Returns the most recent created generation * * @return the generation with the highest sequence number */ synchronized Generation getCurrentGeneration() { return getGenerations().get(0); } public synchronized File getFile(String fileName) { File target = new File(getCurrentGeneration().getGenerationDirectory(), fileName); for (Generation generation : getGenerations()) { File candidate = new File(generation.getGenerationDirectory(), fileName); if (candidate.exists()) { if (!target.equals(candidate)) { //because of; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4017593 target.delete(); if (!candidate.renameTo(target)) { return candidate; } else { break; } } } } return target; } static class Generation implements Comparable { private File generationDirectory; private int sequence; public Generation(final File baseDir, final int generationNumber) { Validate.notNull(baseDir, "Generation directory may not be null"); File genFile = new File(baseDir, String.valueOf(generationNumber)); genFile.mkdirs(); this.generationDirectory = genFile; this.sequence = generationNumber; } public synchronized void delete() { File[] undeleteableFiles = generationDirectory.listFiles(new DeletingFileFilter()); if (undeleteableFiles == null || undeleteableFiles.length == 0) { generationDirectory.delete(); } else { System.err.println("Unable to delete these files: " + Arrays.toString(undeleteableFiles)); } } public File getGenerationDirectory() { return generationDirectory; } public int getSequence() { return sequence; } public int compareTo(Generation generation) { return 1 - (sequence - generation.sequence); } } }

問題は時々ファイルが正しいフォルダに移動さ​​れていないことである、と私はファイルディスクリプタをリークすることがあります。

これを改善する方法についてご意見はありますか?

これには標準的な解決策がありますか?言語に関係なく?

これもかなり遅いですが、スピードの向上が歓迎されています。

+0

「ファイルが正しいフォルダに移動さ​​れないことがある」とはどういう意味ですか?これはあなたのコードのバグですか?それともエラーが出ますか?これはどこが遅いのですか?コードを見て、どこの時間が費やされたのかを知ることは不可能です。 –

+0

Windowsでは、実際には失敗する唯一のオペレーティングシステムであるため、ターゲットファイルがロックされているように見えることがあります。これが私のコードのバグであれば、私はそれを見ていません。 時間はFile.listFiles()メソッドで費やされます。その方法では多くのオブジェクトが作成されます。 – ngarthl

答えて

3

パフォーマンスの問題(およびおそらくバグ)は、この情報をメモリに保存するのではなく、ファイルシステムを過度に使用して世代をマークすることによって発生していると考えられます。ファイルシステムへのアクセスはメモリアクセスよりもはるかに高価です。特にFile.listFiles()またはFile.list()は非常に遅い可能性があります。数千のファイルがある場合は、NTFSを使用するWindowsシステムでは、実行する時間がミリ秒ではなく、秒()になると予想されます。

可能であれば、すべての世代情報を同期コレクション内のオブジェクトとして保存および更新する必要があります。キャッシュされたデータファイルを実際に格納、検索、および削除するためにファイルシステムを使用するだけであれば、すべてのキャッシュファイルを1つのディレクトリに置き、必要なファイルを呼び出すことができます(ファイルに番号またはランダムな名前を付けるだけです)。

突発的なアプリケーションのシャットダウンに対して永続的かつ安全である必要がある場合は、シリアライズされたコレクションを使用してディスクに定期的に書き込むことができます(たとえば、30秒ごとに、またアプリケーションシャットダウン時に)。それは単なるキャッシュであるため、実際のファイルを使用せずにアプリケーションの起動とキャッシュエントリの削除をチェックし、キャッシュエントリなしでファイルを削除することができます。

また、組み込みデータベースを使用してキャッシュ全体を格納する方法もあります。 H2またはHSQLDBは​​純粋なJavaであり、非常に高速で軽量であり、より高速なインメモリデータベースと組み込みモードをサポートします。これにより、DBMSが頻繁に使用するアイテムをRAMにキャッシュすることができるため、より多くのキャッシュオブジェクトを格納できるようになり、速度が大幅に向上する可能性があります。

関連する問題