Hàng đợi giới hạn kích thước chứa N phần tử cuối cùng trong Java


197

Một câu hỏi rất đơn giản và nhanh chóng trên các thư viện Java: có một lớp được tạo sẵn thực hiện a Queuevới kích thước tối đa cố định - tức là nó luôn cho phép thêm các phần tử, nhưng nó sẽ âm thầm loại bỏ các phần tử đầu để chứa không gian cho các phần tử mới được thêm vào.

Tất nhiên, thật tầm thường khi thực hiện nó một cách thủ công:

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

Theo như tôi thấy, không có triển khai tiêu chuẩn nào trong các stdlib Java, nhưng có thể có một trong Apache Commons hay đại loại như thế?



9
@Kevin: Bạn thật là trêu chọc.
Mark Peters

5
Cá nhân tôi sẽ không giới thiệu một thư viện khác nếu đây là lần sử dụng duy nhất của thư viện này ...
Nicolas Bousquet

2
@Override boolean công cộng thêm (PropagationTask t) {boolean thêm = super.add (t); while (đã thêm && size ()> giới hạn) {super.remove (); } trả lại được thêm vào; }
Đổi mới

6
Cảnh báo: mã được đề cập, mặc dù nó rõ ràng hoạt động, nhưng nó có thể phản tác dụng. Có các phương thức bổ sung có thể thêm nhiều phần tử vào hàng đợi (chẳng hạn như addAll ()) mà bỏ qua kiểm tra kích thước này. Để biết thêm chi tiết, hãy xem Phiên bản Java hiệu quả thứ 2 - Mục 16: Thành phần ưu tiên so với kế thừa
Diego

Câu trả lời:


171

Bộ sưu tập Apache commons 4 có một tròn trònFifoQueue <> đó là những gì bạn đang tìm kiếm. Trích dẫn javadoc:

ThongFifoQueue là hàng đợi vào trước xuất trước với kích thước cố định thay thế phần tử cũ nhất của nó nếu đầy đủ.

    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]

Nếu bạn đang sử dụng một phiên bản cũ hơn của các bộ sưu tập commons Apache (3.x), bạn có thể sử dụng Thông tư tròn , về cơ bản là điều tương tự mà không có chung chung.

Cập nhật : cập nhật câu trả lời sau khi phát hành bộ sưu tập commons phiên bản 4 có hỗ trợ chung.


1
Đó là một ứng cử viên tốt, nhưng, than ôi, nó không sử dụng thuốc generic :(
GreyCat

Cảm ơn! Có vẻ như đó là sự thay thế khả thi nhất hiện nay :)
GreyCat

3
Xem câu trả lời khác này cho liên kết để EvictingQueuethêm vào Google Guava phiên bản 15 vào khoảng 2013-10.
Basil Bourque

Có cuộc gọi lại nào được gọi khi phần tử bị đuổi khỏi hàng đợi do thêm vào hàng đợi đầy đủ không?
ed22

một "Hàng đợi tròn" chỉ là một cách thực hiện thỏa mãn câu hỏi. Nhưng câu hỏi không được hưởng lợi trực tiếp từ sự khác biệt chính của hàng đợi tròn, tức là không phải giải phóng / phân bổ lại mỗi nhóm ở mỗi lần thêm / xóa.
đơn giản

90

Giờ đây, ổi có EvictingQueue , hàng đợi không chặn tự động lấy ra các phần tử từ đầu hàng đợi khi cố gắng thêm các phần tử mới vào hàng đợi và nó đã đầy.

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]

Điều này sẽ rất thú vị để sử dụng một khi nó được phát hành chính thức.
Asaf

Đây là nguồn: code.google.com/p/guava-lologists/source/browse/guava/src/com/ Kẻ - có vẻ như sẽ dễ dàng sao chép và biên dịch với các bản phát hành hiện tại của Guava
Tom Carchrae

1
Cập nhật: Lớp này được phát hành chính thức với Google Guava trong phiên bản 15 , khoảng 2013-10.
Basil Bourque

1
@MaciejMiklas Câu hỏi yêu cầu cho một FIFO, EvictingQueuelà một FIFO. Trong trường hợp có bất kỳ nghi ngờ nào, hãy thử chương trình này: Queue<Integer> fifo = EvictingQueue.create(2); fifo.add(1); fifo.add(2); fifo.add(3); System.out.println(fifo); Quan sát kết quả:[2, 3]
kostmo

2
Đây là câu trả lời chính xác. Có một chút không rõ ràng từ tài liệu này, nhưng EvictingQueue là một FIFO.
Michael Böckling

11

Tôi thích giải pháp @FractalizeR. Nhưng tôi cũng sẽ giữ và trả lại giá trị từ 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
Theo như tôi có thể thấy, FractalizeR chưa cung cấp bất kỳ giải pháp nào, chỉ chỉnh sửa câu hỏi. "Giải pháp" trong câu hỏi không phải là một giải pháp, bởi vì câu hỏi là về việc sử dụng một số lớp trong thư viện tiêu chuẩn hoặc bán chuẩn, không phải là của riêng bạn.
GreyCat

3
Cần chỉ ra rằng giải pháp này không an toàn cho luồng
Konrad Morawski

7
@KonradMorawski dù sao thì toàn bộ lớp LinkedList không an toàn cho chủ đề, vì vậy nhận xét của bạn là vô nghĩa trong ngữ cảnh này!
Đổi mới

@RenaudBlue an toàn chủ đề là một mối quan tâm hợp lệ (nếu thường bị bỏ qua), vì vậy tôi không nghĩ nhận xét là vô nghĩa. và nhắc nhở rằng LinkedListkhông phải là chủ đề an toàn cũng sẽ là vô nghĩa. trong bối cảnh của câu hỏi này, yêu cầu cụ thể của OP làm cho điều đặc biệt quan trọng là việc thêm một mục được thực hiện như một hoạt động nguyên tử. nói cách khác, rủi ro không đảm bảo tính nguyên tử sẽ lớn hơn trong trường hợp của LinkedList thông thường.
Konrad Morawski

4
add(int,E)Thay thế ngay khi có người gọi . Và liệu addAllhoạt động như dự định, phụ thuộc vào chi tiết thực hiện không xác định. Đó là lý do tại sao bạn nên ưu tiên ủy quyền hơn thừa kế
Holger

6

Sử dụng thành phần không mở rộng (vâng tôi có nghĩa là mở rộng, như trong một tham chiếu đến từ khóa mở rộng trong java và có, đây là sự kế thừa). Thành phần là tuyệt vời hơn vì nó hoàn toàn che chắn việc thực hiện của bạn, cho phép bạn thay đổi việc thực hiện mà không ảnh hưởng đến người dùng của lớp.

Tôi khuyên bạn nên thử một cái gì đó như thế này (Tôi đang gõ trực tiếp vào cửa sổ này, vì vậy người mua hãy cẩn thận với các lỗi cú pháp):

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

Một lựa chọn tốt hơn (dựa trên câu trả lời của Asaf) có thể là bọc Bộ sưu tập ApacheFifoBuffer của Apache bằng một lớp chung. Ví dụ:

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 nếu bạn giải thích lý do tại sao sáng tác là lựa chọn tốt hơn (ngoài "thích sáng tác hơn thừa kế) ... và có một lý do rất chính đáng
kdgregory

1
Thành phần là một lựa chọn kém cho nhiệm vụ của tôi ở đây: nó có nghĩa là ít nhất gấp đôi số lượng đối tượng => ít nhất hai lần thu gom rác thường xuyên hơn. Tôi sử dụng số lượng lớn (hàng chục triệu) trong số các hàng đợi có kích thước giới hạn này, như thế: Bản đồ <Long, LimitedSizeQueue <String >>.
GreyCat

@GreyCat - Tôi hiểu rằng bạn đã không nhìn vào cách thức LinkedListtriển khai. Đối tượng bổ sung được tạo như một trình bao bọc xung quanh danh sách sẽ khá nhỏ, thậm chí với "hàng chục triệu" trường hợp.
kdgregory

Tôi đã định "giảm kích thước của giao diện", nhưng "che chắn việc thực hiện" là điều tương tự. Hoặc là trả lời những phàn nàn của Mark Peter về cách tiếp cận của OP.
kdgregory

4

Điều duy nhất tôi biết có không gian hạn chế là giao diện BlockingQueue (được thực hiện bởi lớp ArrayBlockingQueue) - nhưng chúng không xóa phần tử đầu tiên nếu được lấp đầy, mà thay vào đó, chặn hoạt động put cho đến khi không gian trống (bị xóa bởi luồng khác ).

Theo hiểu biết của tôi, việc thực hiện tầm thường của bạn là cách dễ nhất để có được một hành vi như vậy.


Tôi đã duyệt qua các lớp stdlib Java và thật đáng buồn, BlockingQueuekhông phải là một câu trả lời. Tôi đã nghĩ đến các thư viện phổ biến khác, chẳng hạn như Apache Commons, thư viện của Eclipse, Spring, bổ sung của Google, v.v?
GreyCat

3

Bạn có thể sử dụng MinMaxP WarriorityQueue từ Google Guava , từ javadoc:

Một hàng đợi ưu tiên tối thiểu có thể được cấu hình với kích thước tối đa. Nếu vậy, mỗi lần kích thước của hàng đợi vượt quá giá trị đó, hàng đợi sẽ tự động loại bỏ phần tử lớn nhất của nó theo bộ so sánh của nó (có thể là phần tử vừa được thêm vào). Điều này khác với các hàng đợi giới hạn thông thường, có thể chặn hoặc từ chối các phần tử mới khi đầy.


3
Bạn có hiểu hàng đợi ưu tiên là gì và nó khác với ví dụ của OP như thế nào không?
kdgregory

2
@Mark Peters - Tôi không biết phải nói gì. Chắc chắn, bạn có thể làm cho một hàng đợi ưu tiên hoạt động giống như một hàng đợi fifo. Bạn cũng có thể làm cho một Maphành vi như một List. Nhưng cả hai ý tưởng đều cho thấy sự hiểu biết hoàn toàn về thuật toán và thiết kế phần mềm.
kdgregory

2
@Mark Peters - không phải mọi câu hỏi trên SO về một cách tốt để làm gì đó sao?
jtahlborn

3
@jtahlborn: Rõ ràng không phải (mã golf), nhưng ngay cả khi chúng là, tốt không phải là một tiêu chí đen trắng. Đối với một dự án nhất định, tốt có thể có nghĩa là "hiệu quả nhất", đối với một dự án khác, nó có thể có nghĩa là "dễ bảo trì nhất" và đối với một dự án khác, nó có thể có nghĩa là "số lượng mã ít nhất với các thư viện hiện có". Tất cả điều đó là không liên quan vì tôi không bao giờ nói đây một câu trả lời tốt . Tôi chỉ nói nó có thể là một giải pháp mà không cần quá nhiều nỗ lực. Biến một MinMaxPriorityQueuethứ mà OP muốn là tầm thường hơn là sửa đổi một LinkedList(mã của OP thậm chí không đến gần).
Mark Peters

3
Có lẽ các bạn đang kiểm tra lựa chọn từ ngữ của tôi "trong thực tế sẽ gần như chắc chắn là đủ". Tôi không có nghĩa là giải pháp này gần như chắc chắn là đủ cho vấn đề của OP hay nói chung. Tôi đã đề cập đến việc lựa chọn loại giảm dần longnhư một loại con trỏ trong đề xuất của riêng tôi, nói rằng nó sẽ đủ rộng trong thực tế mặc dù về mặt lý thuyết bạn có thể thêm hơn 2 ^ 64 đối tượng vào hàng đợi này tại đó giải pháp sẽ bị phá vỡ .
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;
    }
}

3
Câu hỏi là về các lớp học trong các thư viện lớp sưu tập phổ biến, không phải là "giải pháp" homebrew homebrew tối giản đã được cung cấp trong câu hỏi.
GreyCat

2
điều đó không quan trọng google tìm trang này cũng trên các truy vấn khác =)
user590444

1
Câu trả lời này xuất hiện trong hàng đánh giá chất lượng thấp, có lẽ vì bạn không cung cấp bất kỳ lời giải thích nào về mã. Nếu mã này trả lời câu hỏi, hãy xem xét thêm một số văn bản giải thích mã trong câu trả lời của bạn. Bằng cách này, bạn có nhiều khả năng nhận được nhiều upvote hơn - và giúp người hỏi tìm hiểu điều gì đó mới.
LMO
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.