Ukuran-antrian terbatas yang menampung elemen N terakhir di Jawa

197

Sebuah pertanyaan yang sangat sederhana & cepat di perpustakaan Java: apakah ada kelas yang sudah jadi yang mengimplementasikan a Queuedengan ukuran maksimum tetap - yaitu selalu memungkinkan penambahan elemen, tetapi akan secara diam-diam menghapus elemen kepala untuk mengakomodasi ruang untuk elemen yang baru ditambahkan.

Tentu saja, sangat mudah untuk mengimplementasikannya secara manual:

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

Sejauh yang saya lihat, tidak ada implementasi standar di stdlibs Java, tetapi mungkin ada satu di Apache Commons atau sesuatu seperti itu?

GreyCat
sumber
9
@Kevin: Kau sangat menggoda.
Mark Peters
5
Personnaly Saya tidak akan memperkenalkan perpustakaan lain jika ini akan menjadi satu-satunya penggunaan perpustakaan ini ...
Nicolas Bousquet
2
@Override add boolean publik (PropagationTask t) {boolean ditambahkan = super.add (t); while (ditambahkan && size ()> batas) {super.remove (); } kembali ditambahkan; }
Renaud
6
Peringatan: kode yang dipermasalahkan, meskipun tampaknya berfungsi, namun bisa menjadi bumerang. Ada metode tambahan yang dapat menambahkan lebih banyak elemen ke antrian (seperti addAll ()) yang mengabaikan pemeriksaan ukuran ini. Untuk lebih jelasnya lihat Efektif Java 2nd Edition - Butir 16: Komposisi nikmat atas warisan
Diego

Jawaban:

171

Koleksi Apache commons 4 memiliki CircularFifoQueue <> yang Anda cari. Mengutip javadoc:

CircularFifoQueue adalah antrian masuk pertama keluar dengan ukuran tetap yang menggantikan elemen tertua jika penuh.

    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]

Jika Anda menggunakan versi yang lebih lama dari koleksi Apache commons (3.x), Anda dapat menggunakan CircularFifoBuffer yang pada dasarnya adalah hal yang sama tanpa obat generik.

Pembaruan : jawaban yang diperbarui setelah rilis koleksi commons versi 4 yang mendukung obat generik.

Asaf
sumber
1
Itu kandidat yang bagus, tapi, sayangnya, itu tidak menggunakan obat generik :(
GreyCat
Terima kasih! Sepertinya itu alternatif yang paling layak untuk saat ini :)
GreyCat
3
Lihat jawaban lain ini untuk tautan yang EvictingQueueditambahkan ke Google Guava versi 15 sekitar 2013-10.
Basil Bourque
Apakah ada panggilan balik yang dipanggil ketika elemen diusir dari antrian karena penambahan ke antrian penuh?
ed22
"Circular Queue" hanyalah satu implementasi yang memuaskan pertanyaan. Tetapi pertanyaannya tidak secara langsung menguntungkan dari perbedaan utama antrian melingkar, yaitu tidak harus membebaskan / merealokasi setiap ember pada setiap penambahan / penghapusan.
simpleuser
90

Guava sekarang memiliki EvictingQueue , antrian non-pemblokiran yang secara otomatis mengusir elemen dari kepala antrian ketika mencoba untuk menambahkan elemen baru ke dalam antrian dan penuh.

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
sumber
Ini harus menarik untuk digunakan setelah keluar secara resmi.
Asaf
Berikut adalah sumbernya: code.google.com/p/guava-libraries/source/browse/guava/src/com/… - sepertinya akan mudah untuk menyalin dan mengkompilasi dengan rilis terbaru Guava
Tom Carchrae
1
Pembaruan: Kelas ini secara resmi dirilis dengan Google Guava di versi 15 , sekitar 2013-10.
Basil Bourque
1
@MaciejMiklas Pertanyaan meminta FIFO, EvictingQueueadalah FIFO. Jika ada keraguan, coba program ini: Queue<Integer> fifo = EvictingQueue.create(2); fifo.add(1); fifo.add(2); fifo.add(3); System.out.println(fifo); Amati hasilnya:[2, 3]
kostmo
2
Ini sekarang jawaban yang benar. Agak tidak jelas dari dokumentasi, tetapi EvictingQueue adalah FIFO.
Michael Böckling
11

Saya suka solusi @FractalizeR. Tapi saya juga akan menyimpan dan mengembalikan nilai dari 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
sumber
1
Sejauh yang saya bisa lihat, FractalizeR belum memberikan solusi apa pun, hanya mengedit pertanyaan. "Solusi" dalam pertanyaan itu bukanlah solusi, karena pertanyaannya adalah tentang menggunakan beberapa kelas di perpustakaan standar atau semi-standar, bukan menggulirkan Anda sendiri.
GreyCat
3
Harus ditunjukkan bahwa solusi ini tidak aman untuk benang
Konrad Morawski
7
@KonradMorawski seluruh kelas LinkedList tidak aman utas, jadi komentar Anda tidak ada gunanya dalam konteks ini!
Renaud
@RenaudBlue safety thread adalah masalah yang valid (jika sering diabaikan), jadi saya tidak berpikir komentar itu tidak ada gunanya. dan mengingatkan bahwa LinkedListtidak aman utas juga tidak akan ada gunanya. dalam konteks pertanyaan ini, persyaratan khusus OP membuatnya sangat penting bahwa menambahkan item dilakukan sebagai operasi atom. dengan kata lain, risiko tidak memastikan atomisitas akan lebih besar daripada dalam kasus LinkedList biasa.
Konrad Morawski
4
Istirahat segera setelah seseorang menelepon add(int,E). Dan apakah addAllberfungsi sebagaimana dimaksud, tergantung pada detail implementasi yang tidak ditentukan. Itu sebabnya Anda harus memilih delegasi daripada warisan ...
Holger
6

Gunakan komposisi tidak meluas (ya maksudku meluas, seperti dalam referensi ke kata kunci meluas di java dan ya ini adalah warisan). Komposisi lebih unggul karena sepenuhnya melindungi implementasi Anda, memungkinkan Anda untuk mengubah implementasi tanpa berdampak pada pengguna kelas Anda.

Saya sarankan mencoba sesuatu seperti ini (saya mengetik langsung ke jendela ini, jadi pembeli waspada terhadap kesalahan sintaksis):

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

Opsi yang lebih baik (berdasarkan jawaban oleh Asaf) mungkin untuk membungkus Apache Collections CircularFifoBuffer dengan kelas generik. Sebagai contoh:

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
sumber
2
+1 jika Anda menjelaskan mengapa komposisi adalah pilihan yang lebih baik (selain "lebih suka komposisi daripada warisan) ... dan ada alasan yang sangat bagus
kdgregory
1
Komposisi adalah pilihan yang buruk untuk tugas saya di sini: itu berarti setidaknya dua kali jumlah objek => setidaknya dua kali lebih sering pengumpulan sampah. Saya menggunakan jumlah besar (puluhan juta) dari antrian ukuran terbatas ini, seperti itu: Peta <Panjang, LimitedSizeQueue <String>>.
GreyCat
@GreyCat - Saya rasa Anda belum melihat bagaimana LinkedListdiimplementasikan, lalu. Objek tambahan yang dibuat sebagai pembungkus di sekitar daftar akan sangat kecil, bahkan dengan "puluhan juta" instance.
kdgregory
Saya akan "mengurangi ukuran antarmuka," tetapi "melindungi implementasi" adalah hal yang hampir sama. Keduanya menjawab keluhan Mark Peter tentang pendekatan OP.
kdgregory
4

Satu-satunya hal yang saya tahu yang memiliki ruang terbatas adalah antarmuka BlockingQueue (yang misalnya diimplementasikan oleh kelas ArrayBlockingQueue) - tetapi mereka tidak menghapus elemen pertama jika diisi, tetapi sebaliknya memblokir operasi put sampai ruang bebas (dihapus oleh utas lainnya) ).

Sepengetahuan saya implementasi sepele Anda adalah cara termudah untuk mendapatkan perilaku seperti itu.

flolo
sumber
Saya sudah melihat-lihat kelas stdlib Java, dan, sayangnya, BlockingQueuebukan jawaban. Saya sudah memikirkan perpustakaan umum lainnya, seperti Apache Commons, perpustakaan Eclipse, Spring, tambahan Google, dll?
GreyCat
3

Anda dapat menggunakan MinMaxPriorityQueue dari Google Guava , dari javadoc:

Antrian prioritas min-max dapat dikonfigurasi dengan ukuran maksimum. Jika demikian, setiap kali ukuran antrian melebihi nilai itu, antrian secara otomatis menghapus elemen terbesarnya sesuai dengan pembandingnya (yang mungkin merupakan elemen yang baru saja ditambahkan). Ini berbeda dari antrian terbatas konvensional, yang memblokir atau menolak elemen baru saat penuh.

moritz
sumber
3
Apakah Anda mengerti apa antrian prioritas itu, dan bagaimana antriannya berbeda dari contoh OP?
kdgregory
2
@ Mark Peters - Saya hanya tidak tahu harus berkata apa. Tentu, Anda dapat membuat antrian prioritas berperilaku seperti antrian fifo. Anda juga bisa Mapberperilaku seperti List. Namun kedua ide tersebut menunjukkan tidak lengkapnya algoritma dan desain perangkat lunak.
kdgregory
2
@ Mark Peters - bukankah setiap pertanyaan pada SO tentang cara yang baik untuk melakukan sesuatu?
jtahlborn
3
@ jtahlborn: Jelas bukan (golf kode), tetapi meskipun begitu, bagus bukan kriteria hitam putih. Untuk proyek tertentu, bagus bisa berarti "paling efisien", untuk yang lain itu mungkin berarti "paling mudah untuk mempertahankan" dan untuk yang lain, itu mungkin berarti "paling sedikit kode dengan perpustakaan yang ada". Semua itu tidak relevan karena saya tidak pernah mengatakan ini adalah sebuah baik jawaban. Saya hanya mengatakan itu bisa menjadi solusi tanpa terlalu banyak usaha. Mengubah MinMaxPriorityQueuemenjadi apa yang diinginkan OP lebih sepele daripada memodifikasi LinkedList(kode OP bahkan tidak mendekati).
Mark Peters
3
Mungkin kalian memeriksa pilihan kata-kata saya "dalam praktik yang hampir pasti akan cukup". Saya tidak bermaksud bahwa solusi ini hampir pasti cukup untuk masalah OP atau secara umum. Saya merujuk pada pilihan turun longsebagai jenis kursor dalam saran saya sendiri, mengatakan bahwa itu akan cukup luas dalam prakteknya meskipun secara teoritis Anda dapat menambahkan lebih dari 2 ^ 64 objek ke antrian ini di mana titik solusi akan rusak .
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;
    }
}
pengguna590444
sumber
3
Pertanyaannya adalah tentang kelas-kelas di perpustakaan kelas koleksi populer, bukan bergulir sendiri - "solusi" homebrew minimalis sudah disediakan dalam pertanyaan.
GreyCat
2
itu tidak masalah google menemukan halaman ini juga pada permintaan lain =)
user590444
1
Jawaban ini muncul dalam antrian ulasan berkualitas rendah, mungkin karena Anda tidak memberikan penjelasan kode apa pun. Jika kode ini menjawab pertanyaan, pertimbangkan untuk menambahkan beberapa teks yang menjelaskan kode dalam jawaban Anda. Dengan cara ini, Anda jauh lebih mungkin mendapatkan lebih banyak upvotes - dan membantu si penanya mempelajari sesuatu yang baru.
lmo