Черга з обмеженням розміру, яка містить останні N елементів на Java


198

Дуже просте та швидке запитання щодо бібліотек Java: чи є готовий клас, який реалізує Queueфіксований максимальний розмір - тобто він завжди дозволяє додавати елементи, але він мовчки видалить головні елементи, щоб вмістити місце для нових доданих елементів.

Звичайно, реально це реалізувати вручну:

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;
    }
}

Наскільки я бачу, у Java stdlibs немає стандартної реалізації, але може бути така, що в Apache Commons чи щось подібне?



9
@Kevin: Ти така дражниця.
Марк Петерс

5
Personnaly Я б не представив іншу бібліотеку, якби це було єдиним використанням цієї бібліотеки ...
Nicolas Bousquet

2
@Override public boolean add (PropagationTask t) {boolean added = super.add (t); while (додано && size ()> межа) {super.remove (); } повернення додано; }
Рено

6
Попередження: питання, про який йде мова, хоч і, мабуть, працює, але може мати зворотну реакцію. Існують додаткові методи, які можуть додавати більше елементів до черги (наприклад, addAll ()), які ігнорують перевірку цього розміру. Докладніше див. Ефективне видання Java 2 - Пункт 16: Композиція улюбленого над спадщиною
Дієго

Відповіді:


171

Колекція Apache commons 4 має CircularFifoQueue <>, що саме ви шукаєте. Цитуючи javadoc:

CircularFifoQueue - це черга, що перебуває на першому місці, із фіксованим розміром, яка замінює її найстаріший елемент, якщо він заповнений.

    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]

Якщо ви використовуєте старішу версію колекцій Apache commons (3.x), ви можете використовувати CircularFifoBuffer, який в основному те саме, без дженериків.

Оновлення : оновлена ​​відповідь після випуску колекцій спільноти версії 4, яка підтримує дженерики.


1
Це хороший кандидат, але, на жаль, він не використовує дженерики :(
GreyCat

Дякую! Здається, це найбільш життєздатна альтернатива на сьогодні :)
GreyCat

3
Дивіться цю іншу відповідь за посиланням на EvictingQueueдодане до Google Guava версії 15 близько 2013-10.
Василь Бурк

Чи є виклик зворотного дзвінка, коли елемент виселяється з черги через додавання до повної черги?
ed22

"Кругова черга" - це лише одна реалізація, яка задовольняє питання. Але питання безпосередньо не виграє основних відмінностей кругової черги, тобто не потрібно звільняти / перерозподіляти кожне відро під час кожного додавання / видалення.
simpleuser

90

Тепер у Guava є EvictingQueue , що не блокує чергу, яка автоматично вилучає елементи з голови черги при спробі додати нові елементи до черги, і вона заповнена.

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]

Це має бути цікавим для використання, коли воно вийде офіційно.
Асаф

Ось джерело: code.google.com/p/guava-libraries/source/browse/guava/src/com/… - схоже, було б легко скопіювати та компілювати з поточними випусками Guava
Tom Carchrae

1
Оновлення: Цей клас був офіційно випущений разом із Google Guava у версії 15 , приблизно у 2013-10 роках.
Василь Бурк

1
@MaciejMiklas Питання про FIFO, EvictingQueueце FIFO. Якщо є сумніви, спробуйте цю програму: Queue<Integer> fifo = EvictingQueue.create(2); fifo.add(1); fifo.add(2); fifo.add(3); System.out.println(fifo); Поспостерігайте за результатом:[2, 3]
kostmo

2
Це правильна відповідь. Це трохи незрозуміло з документації, але EvictingQueue є FIFO.
Майкл Бёклінг

11

Мені подобається рішення @FractalizeR. Але я б додатково зберігав і повертав значення з 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;
    }
}

1
Наскільки я бачу, FractalizeR не запропонував жодного рішення, лише відредагував питання. "Рішення" всередині питання не є рішенням, оскільки питання стосувалося використання якогось класу в стандартній або напівстандартній бібліотеці, а не прокатки власного.
GreyCat

3
Слід зазначити, що це рішення не є безпечним для ниток
Конрад Моравський

7
@KonradMorawski весь клас LinkedList все одно не є безпечним для потоків, тому ваш коментар є безглуздим у цьому контексті!
Рено

Безпека нитки @RenaudBlue є важливою проблемою (якщо її часто не помічають), тому я не думаю, що коментар є безглуздим. і нагадування про те, що LinkedListне безпечно для потоків, також не було б безглуздим. в контексті цього питання особлива вимога ОП робить особливо важливим, щоб додавання елемента виконувались як атомна операція. Іншими словами, ризик не забезпечити атомність був би більшим, ніж у випадку звичайного LinkedList.
Конрад Моравський

4
Робиться, як тільки хтось add(int,E)натомість дзвонить . І чи буде addAllробота за призначенням, залежить від не визначених деталей реалізації. Ось чому вам слід віддати перевагу делегуванню над спадщиною…
Holger

6

Використовувати склад не розширюється (так, я маю на увазі розширення, як у посиланні на ключове слово extends в java, і так, це спадкування). Склад є суперерним, оскільки він повністю захищає вашу реалізацію, дозволяючи змінювати реалізацію, не впливаючи на користувачів вашого класу.

Я рекомендую спробувати щось подібне (я набираю безпосередньо в це вікно, тому покупець остерігайтесь синтаксичних помилок):

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

Кращим варіантом (на основі відповіді Asaf) може бути обшивка колекцій Apache CircularFifoBuffer загальним класом. Наприклад:

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
}

2
+1, якщо ви поясните, чому композиція є кращим вибором (окрім "віддайте перевагу композиції над спадщиною) ... і є дуже вагома причина
kdgregory

1
Склад - це поганий вибір мого завдання тут: він означає щонайменше вдвічі більше об’єктів => принаймні вдвічі частіше збирання сміття. Я використовую великі кількості (десятки мільйонів) цих черг з обмеженим розміром, наприклад: Map <Long, LimitedSizeQueue <String>>.
GreyCat

@GreyCat - я вважаю, ви тоді не дивилися на те, як LinkedListце реалізовано. Додатковий об’єкт, створений як обгортка навколо списку, буде досить незначним, навіть із "десятками мільйонів" екземплярів.
kdgregory

Я збирався "зменшити розмір інтерфейсу", але "екранує реалізацію" - це майже те саме. Або відповіді на скарги Марка Петра щодо підходу ОП.
kdgregory

4

Єдине, що я знаю, що має обмежений простір - це інтерфейс BlockingQueue (який, наприклад, реалізований класом ArrayBlockingQueue), - але вони не видаляють перший елемент, якщо заповнені, а замість цього блокують операцію поставлення, поки простір не вільний (видалено іншим потоком ).

Наскільки мені відомо, ваша тривіальна реалізація - це найпростіший спосіб отримати таку поведінку.


Я вже переглянув класи stdlib Java, і, на жаль, BlockingQueueце не відповідь. Я думав про інші поширені бібліотеки, такі як Apache Commons, бібліотеки Eclipse, Spring, додатки Google тощо?
GreyCat

3

Ви можете використовувати MinMaxPriorityQueue від Google Guava , з javadoc:

Мінімальна пріоритетна черга може бути налаштована з максимальним розміром. Якщо так, щоразу, коли розмір черги перевищує це значення, черга автоматично видаляє свій найбільший елемент відповідно до свого компаратора (який може бути елементом, який щойно додали). Це відрізняється від звичайних обмежених черг, які або блокують, або відхиляють нові елементи, коли вони заповнені.


3
Чи розумієте ви, що таке черга пріоритетів, і чим вона відрізняється від прикладу ОП?
kdgregory

2
@Mark Peters - я просто не знаю, що сказати. Звичайно, ви можете змусити чергу пріоритетів вести себе як черга фіфо. Ви також можете Mapповодити себе так, як List. Але обидві ідеї показують повне розуміння алгоритмів та розробки програмного забезпечення.
kdgregory

2
@Mark Peters - це не кожен питання про SO про хорошому способі зробити що - то?
jtahlborn

3
@jtahlborn: Ясно, що не (код гольфу), але навіть якби вони були, добро не є чорно-білим критерієм. Для певного проекту хороший може означати "найефективніший", для іншого він може означати "найпростіший в обслуговуванні", а для іншого - "найменший обсяг коду з існуючими бібліотеками". Все , що не має ніякого значення , так як я ніколи не говорив , що це був хороший відповідь. Я щойно сказав, що це може бути рішення без особливих зусиль. Перетворення MinMaxPriorityQueueна те, що хоче ОП, є більш дрібницею, ніж зміна LinkedList(код ОП навіть не наближається).
Марк Петерс

3
Можливо, ви, хлопці, вивчаєте мій вибір слів "на практиці цього майже напевно буде достатньо". Я не мав на увазі, що це рішення майже напевно буде достатнім для проблеми ОП або взагалі. Я мав на увазі вибір низхідного longтипу курсору в рамках власної пропозиції, кажучи, що це буде досить широким на практиці, хоча теоретично ви можете додати до цієї черги більше 2 ^ 64 об'єктів, в який момент рішення розбивається. .
Марк Петерс


-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;
    }
}

3
Питання стосувалося занять у популярних бібліотеках класів колекцій, а не прокатування власних - мінімалістичне «рішення» для домашньої мови вже було поставлено під питання.
GreyCat

2
що не має значення Google знайти цю сторінку також в інших запитах =)
user590444

1
Ця відповідь з'явилася в черзі на огляд низької якості, імовірно, тому що ви не надаєте жодного пояснення коду. Якщо цей код відповідає на питання, спробуйте додати текст, що пояснює код у вашій відповіді. Таким чином, ви набагато більше шансів отримати більше грошей - і допомогти запитувачу дізнатися щось нове.
lmo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.