Fila de tamanho limitado que contém os últimos N elementos em Java

197

Uma pergunta muito simples e rápida nas bibliotecas Java: existe uma classe pronta que implementa uma Queuecom um tamanho máximo fixo - isto é, sempre permite a adição de elementos, mas remove silenciosamente os elementos principais para acomodar espaço para os elementos adicionados recentemente.

Obviamente, é trivial implementá-lo manualmente:

import java.util.LinkedList;

public class LimitedQueue<E> extends LinkedList<E> {
    private int limit;

    public LimitedQueue(int limit) {
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        super.add(o);
        while (size() > limit) { super.remove(); }
        return true;
    }
}

Até onde eu vejo, não há implementação padrão no stdlibs Java, mas pode haver no Apache Commons ou algo assim?

GreyCat
fonte
1
Relacionados stackoverflow.com/questions/590069/...
andersoj
9
@ Kevin: Você é uma provocação.
Mark Peters
5
Personnaly eu não iria introduzir uma outra biblioteca se esta seria a única utilizar desta biblioteca ...
Nicolas Bousquet
2
@Override public boolean add (PropagationTask t) {boolean adicionado = super.add (t); while (adicionado && size ()> limite) {super.remove (); } retorno adicionado; }
Renaud 14/01
6
Aviso: o código em questão, embora aparentemente funcione, pode sair pela culatra. Existem métodos adicionais que podem adicionar mais elementos à fila (como addAll ()) que ignoram essa verificação de tamanho. Para obter mais detalhes, consulte Effective Java 2nd Edition - Item 16: Favorecer a composição sobre herança
Diego

Respostas:

171

O Apache commons collections 4 possui um CircularFifoQueue <> que é o que você está procurando. Citando o javadoc:

CircularFifoQueue é uma fila primeiro a entrar, primeiro a sair, com um tamanho fixo que substitui o elemento mais antigo se estiver cheio.

    import java.util.Queue;
    import org.apache.commons.collections4.queue.CircularFifoQueue;

    Queue<Integer> fifo = new CircularFifoQueue<Integer>(2);
    fifo.add(1);
    fifo.add(2);
    fifo.add(3);
    System.out.println(fifo);

    // Observe the result: 
    // [2, 3]

Se você estiver usando uma versão mais antiga das coleções comuns do Apache (3.x), poderá usar o CircularFifoBuffer, que é basicamente a mesma coisa sem genéricos.

Atualização : resposta atualizada após o lançamento do commons collections versão 4 que suporta genéricos.

Asaf
fonte
1
Esse é um bom candidato, mas, infelizmente, ele não usa genéricos :(
GreyCat
Obrigado! Looks que é a alternativa mais viável para agora :)
Greycat
3
Veja esta outra resposta para o link EvictingQueueadicionado à versão 15 do Google Guava por volta de 2013-10.
Basil Bourque
Existe algum retorno de chamada chamado quando o elemento é despejado da fila devido à adição à fila completa?
ed22
uma "fila circular" é apenas uma implementação que satisfaz a pergunta. Mas a pergunta não se beneficia diretamente das principais diferenças de uma fila circular, ou seja, não é necessário liberar / realocar cada bloco a cada adição / exclusão.
Simples # 17/17
90

O Goiaba agora tem um EvictingQueue , uma fila sem bloqueio que remove automaticamente os elementos do início da fila ao tentar adicionar novos elementos à fila e ela está cheia.

import java.util.Queue;
import com.google.common.collect.EvictingQueue;

Queue<Integer> fifo = EvictingQueue.create(2); 
fifo.add(1); 
fifo.add(2); 
fifo.add(3); 
System.out.println(fifo); 

// Observe the result: 
// [2, 3]
Miguel
fonte
Isso deve ser interessante de usar quando sair oficialmente.
Asaf
Aqui está a fonte: code.google.com/p/guava-libraries/source/browse/guava/src/com/… - parece que seria fácil copiar e compilar com as versões atuais do Guava
Tom Carchrae
1
Atualização: esta classe foi lançada oficialmente com o Google Guava na versão 15 , por volta de 2013-10.
Basil Bourque
1
@MaciejMiklas A pergunta pede um FIFO, EvictingQueueé um FIFO. Em caso de dúvida, tente este programa: Queue<Integer> fifo = EvictingQueue.create(2); fifo.add(1); fifo.add(2); fifo.add(3); System.out.println(fifo); Observe o resultado:[2, 3]
kostmo
2
Esta é agora a resposta correta. Não está claro na documentação, mas EvictingQueue é um FIFO.
Michael Böckling
11

Eu gosto da solução @FractalizeR. Além disso, eu manteria e retornaria o valor de super.add (o)!

public class LimitedQueue<E> extends LinkedList<E> {

    private int limit;

    public LimitedQueue(int limit) {
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        boolean added = super.add(o);
        while (added && size() > limit) {
           super.remove();
        }
        return added;
    }
}
Renaud
fonte
1
Tanto quanto posso ver, o FractalizeR não forneceu nenhuma solução, apenas editou a pergunta. "Solução" dentro da pergunta não é uma solução, porque a pergunta era sobre o uso de alguma classe na biblioteca padrão ou semi-padrão, e não a sua própria.
precisa saber é
3
Deve-se salientar que esta solução não é thread-safe
Konrad Morawski
7
@KonradMorawski, toda a classe LinkedList não é segura para threads, então seu comentário não faz sentido nesse contexto!
Renaud
A segurança de threads do @RenaudBlue é uma preocupação válida (se muitas vezes esquecida), então não acho que o comentário seja inútil. e lembrar que LinkedListnão é seguro para threads também não faria sentido. no contexto desta pergunta, o requisito específico do OP torna particularmente importante que a adição de um item seja realizada como uma operação atômica. em outras palavras, o risco de não garantir a atomicidade seria maior do que no caso de uma LinkedList regular.
precisa
4
Quebra assim que alguém liga add(int,E). E se addAllfunciona como pretendido, depende de detalhes de implementação não especificados. É por isso que você deve preferir delegação sobre a herança ...
Holger
6

Use composição not extends (sim, quero dizer extends, como em uma referência à palavra-chave extends em java e sim, isso é herança). A composição é mais alta porque protege completamente sua implementação, permitindo alterar a implementação sem afetar os usuários de sua classe.

Eu recomendo tentar algo assim (estou digitando diretamente nesta janela, portanto, o comprador deve ficar atento aos erros de sintaxe):

public LimitedSizeQueue implements Queue
{
  private int maxSize;
  private LinkedList storageArea;

  public LimitedSizeQueue(final int maxSize)
  {
    this.maxSize = maxSize;
    storageArea = new LinkedList();
  }

  public boolean offer(ElementType element)
  {
    if (storageArea.size() < maxSize)
    {
      storageArea.addFirst(element);
    }
    else
    {
      ... remove last element;
      storageArea.addFirst(element);
    }
  }

  ... the rest of this class

Uma opção melhor (com base na resposta de Asaf) pode ser agrupar o CircularFifoBuffer do Apache Collections com uma classe genérica. Por exemplo:

public LimitedSizeQueue<ElementType> implements Queue<ElementType>
{
    private int maxSize;
    private CircularFifoBuffer storageArea;

    public LimitedSizeQueue(final int maxSize)
    {
        if (maxSize > 0)
        {
            this.maxSize = maxSize;
            storateArea = new CircularFifoBuffer(maxSize);
        }
        else
        {
            throw new IllegalArgumentException("blah blah blah");
        }
    }

    ... implement the Queue interface using the CircularFifoBuffer class
}
DwB
fonte
2
+1 se você explicar por que a composição é uma escolha melhor (que não seja "prefira composição sobre herança") ... e há uma boa razão
#
1
A composição é uma má escolha para minha tarefa aqui: significa pelo menos duas vezes o número de objetos => pelo menos duas vezes mais vezes a coleta de lixo. Uso grandes quantidades (dezenas de milhões) dessas filas de tamanho limitado, assim: Map <Long, LimitedSizeQueue <>>.
precisa saber é o seguinte
@ GreyCat - suponho que você não tenha visto como LinkedListé implementado, então. O objeto extra criado como um invólucro em torno da lista será bem menor, mesmo com "dezenas de milhões" de instâncias.
kdgregory
Eu estava indo para "reduz o tamanho da interface", mas "protege a implementação" é praticamente a mesma coisa. Qualquer um deles responde às reclamações de Mark Peter sobre a abordagem do OP.
kdgregory
4

A única coisa que sei que tem espaço limitado é a interface BlockingQueue (que é implementada pela classe ArrayBlockingQueue) - mas eles não removem o primeiro elemento se forem preenchidos, mas bloqueiam a operação de colocação até que o espaço esteja livre (removido por outro encadeamento )

Que eu saiba, sua implementação trivial é a maneira mais fácil de obter esse comportamento.

flolo
fonte
Eu já naveguei pelas classes Java stdlib e, infelizmente, BlockingQueuenão é uma resposta. Já pensei em outras bibliotecas comuns, como o Apache Commons, as bibliotecas do Eclipse, as da Spring, as adições do Google, etc.?
precisa saber é o seguinte
3

Você pode usar um MinMaxPriorityQueue do Google Guava , no javadoc:

Uma fila de prioridade min-max pode ser configurada com um tamanho máximo. Nesse caso, sempre que o tamanho da fila exceder esse valor, a fila remove automaticamente seu maior elemento de acordo com seu comparador (que pode ser o elemento que acabou de ser adicionado). Isso é diferente das filas limitadas convencionais, que bloqueiam ou rejeitam novos elementos quando cheios.

Moritz
fonte
3
Você entende o que é uma fila de prioridade e como ela difere do exemplo do OP?
kdgregory
2
Mark Peters - Eu simplesmente não sei o que dizer. Claro, você pode fazer com que uma fila de prioridade se comporte como uma fila de quinze. Você também pode se Mapcomportar como um List. Mas ambas as idéias mostram uma incompreensão completa de algoritmos e design de software.
Kdgregory
2
@ Mark Peters - nem todas as perguntas sobre SO são uma boa maneira de fazer alguma coisa?
precisa saber é o seguinte
3
@jtahlborn: Claramente não (código de golfe), mas mesmo se fossem, o bom não é um critério em preto e branco. Para um determinado projeto, bom pode significar "mais eficiente", para outro pode significar "mais fácil de manter" e, para outro, pode significar "menor quantidade de código com bibliotecas existentes". Tudo isso é irrelevante, pois nunca disse que essa era uma boa resposta. Acabei de dizer que pode ser uma solução sem muito esforço. Transformar um MinMaxPriorityQueueno que o OP deseja é mais trivial do que modificar um LinkedList(o código do OP nem chega perto).
Mark Peters
3
Talvez vocês estejam examinando minha escolha de palavras "na prática, quase certamente será suficiente". Não quis dizer que essa solução quase certamente seria suficiente para o problema do OP ou em geral. Eu estava me referindo à escolha de um descendente longcomo um tipo de cursor dentro da minha própria sugestão, dizendo que seria suficientemente amplo na prática, embora teoricamente você possa adicionar mais de 2 ^ 64 objetos a essa fila, momento em que a solução quebraria. .
Mark Peters
-2
    public class ArrayLimitedQueue<E> extends ArrayDeque<E> {

    private int limit;

    public ArrayLimitedQueue(int limit) {
        super(limit + 1);
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        boolean added = super.add(o);
        while (added && size() > limit) {
            super.remove();
        }
        return added;
    }

    @Override
    public void addLast(E e) {
        super.addLast(e);
        while (size() > limit) {
            super.removeLast();
        }
    }

    @Override
    public boolean offerLast(E e) {
        boolean added = super.offerLast(e);
        while (added && size() > limit) {
            super.pollLast();
        }
        return added;
    }
}
user590444
fonte
3
A questão era sobre as classes nas bibliotecas populares de classes de coleções, e não a própria - a "solução" minimalista do homebrew já era fornecida em questão.
precisa saber é o seguinte
2
isso não importa google encontre esta página também em outras consultas =)
user590444
1
Essa resposta apareceu na fila de revisão de baixa qualidade, presumivelmente porque você não fornece nenhuma explicação do código. Se esse código responder à pergunta, considere adicionar um texto explicando o código em sua resposta. Dessa forma, é muito mais provável que você receba mais votos - e ajude o interlocutor a aprender algo novo.
lmo