Организация файлов в «ведра» - PullRequest
1 голос
/ 27 апреля 2009

Моя проблема такова:

У меня есть записанный 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. <br/>
 * The files are promoted when they are accessed, so we can figure out which files that are OK to delete. <br/>
 * Known Gotchas: This needs to be in sync with the size of the storage engine. <br/>
 * 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);
        }
    }

}

Проблема в том, что иногда файлы не перемещаются в правильную папку, и я могу пропустить дескрипторы файлов.

Есть ли у вас какие-либо предложения, как это улучшить?

Есть ли стандартное решение для этого, может быть? независимо от языка?

Это также довольно медленно, улучшения скорости приветствуются.

1 Ответ

3 голосов
/ 28 апреля 2009

Ваша проблема с производительностью (и, возможно, ошибки), вероятно, вызвана чрезмерным использованием файловой системы при маркировке поколений, а не хранением этой информации в памяти. Доступ к файловой системе намного дороже, чем доступ к памяти - в частности, File.listFiles () или File.list () может быть ОЧЕНЬ медленным. Если у вас есть несколько тысяч файлов, ожидайте, что это займет секунд вместо миллисекунд для выполнения в системе Windows с использованием NTFS.

Если возможно, вся информация о поколениях должна храниться и обновляться как объекты в синхронизированной коллекции. Если вы просто используете файловую систему для фактического хранения, извлечения и удаления файлов кэшированных данных, вы можете поместить все файлы кэша в один каталог и вызывать их как хотите (просто дайте файлу номер или случайное имя).

Если информация о кеше поколений должна быть постоянной и защищенной от внезапного завершения работы приложения, вы можете использовать сериализованные коллекции и периодически записывать их на диск (скажем, каждые 30 секунд и снова при завершении работы приложения). Поскольку это всего лишь кэш, вы можете проверить запуск приложения и удалить записи в кэше без реального файла, а также удалить файлы без записи в кэше.

С другой стороны, вы можете использовать встроенную базу данных для хранения всего кэша. H2 или HSQLDB - это чистый Java, очень быстрый и легкий, поддерживающий базы данных в памяти и встроенные режимы, которые работают еще быстрее. Это позволит вам хранить гораздо больше объектов кэша и может быть намного быстрее, поскольку СУБД может кэшировать часто используемые элементы в ОЗУ.

...