-1

I will declare an ArrayList at class level. I'll use a 'set' method to fill the arrayList with values. This 'set' method will be called from an ActionEvent method. Events will happen regularly in the program, so this 'set' method will be called 100 times or more. Each time 'set' is called it will pass a String variable to the set method. The String variable will be added to the (Class level) ArrayList. I want this ArrayList to "trim" itself so that it only ever contains 5 values. ie: I need the value at index 4 to be eliminated and what was at index 3 shifts to index 4 and the "newest" variable which is passed in becomes index 0. What I don't know how to do is make the ArrayList "trim" itself in this way. some guidance would be much apprecieated. Thank you xxx

user3814983
  • 39
  • 2
  • 8

3 Answers3

3

An ArrayList is not really a suitable class for what you need to do. You essentially need a limited-capacity circular buffer - ArrayDeque would be much closer. You have to extend it, though, in order to have it drop elements implicitly when its capacity has been reached:

public static class LimitedArrayDeque<T> extends ArrayDeque<T> {
    int threshold;

    public LimitedArrayDeque(int capacity) {
        super(capacity);

        this.threshold = capacity - 1;
    }

    @Override
    public boolean add(T element) {
        while (this.size() > this.threshold) {
            this.removeFirst();
        }

        return super.add(element);
    }

    /* ... */
}

Note that you should probably override any method that adds elements to the queue in the same manner as add() in my example.

thkala
  • 84,049
  • 23
  • 157
  • 201
  • You want to perform the `add` before the `removeFirst`, in case the `add` fails. – chiastic-security Sep 06 '14 at 07:11
  • @chiastic-security: it's a matter of semantics - if you do that then for a short time the capacity of the queue would have been exceeded... – thkala Sep 06 '14 at 07:12
  • Indeed so. On the other hand, if you do it in your direction then for a while it's under capacity and doesn't meet its spec of having the most recent five elements in it! So really you'd want it to be `synchronized` so that this is an atomic operation. Still you need to add before remove, though, even when it's synchronized, because if you remove and then the add fails, you've got no way of backing out. – chiastic-security Sep 06 '14 at 07:14
  • Thanks ^^ I'm trying ArrayDeque now. – user3814983 Sep 06 '14 at 07:17
  • @chiastic-security: I'd rather let the OP figure out the thorny details themselves :-) BTW, if `add()` fails on an `ArrayDeque` you probably have a real mess in your hands no matter what you do. All that `ArrayDeque` does is fiddle with a couple of array indexes - no memory allocations or anything "risky" apart from the array allocation itself... – thkala Sep 06 '14 at 07:22
  • 1
    Inheritance is really not the right tool for this. You should wrap and delegate to an ArrayDeque instead of extending it. – JB Nizet Sep 06 '14 at 07:35
  • @JBNizet: agreed, a decorator class that limits the size of any queue would be better in general... – thkala Sep 06 '14 at 07:42
1

From Size-limited queue that holds last N elements in Java


Apache commons collections 4 has a CircularFifoQueue which is what you are looking for. Quoting the javadoc:

CircularFifoQueue is a first-in first-out queue with a fixed size that replaces its oldest element if full.

If you are using an older version of the Apache commons collections (3.x), you can use the CircularFifoBuffer which is basically the same thing without generics.

Update: updated answer following release of commons collections version 4

Community
  • 1
  • 1
Benny Bottema
  • 11,111
  • 10
  • 71
  • 96
0

What I don't know how to do is make the ArrayList "trim" itself in this way. some guidance would be much appreciated.

From How to design a Least Recently Used (LRU) Cache in Java. It does not use an ArrayList, though.

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class LRUCache<K, V> {

    //Maximum capacity for the LRU cache.
    private final int capacity;
    //Queue to store the recently used keys.
    private ConcurrentLinkedQueue<K> queue;
    //Key-Value store to maintain the actual object.
    private ConcurrentHashMap<K, V> map;

    /**
     * Initial capacity for the LRU Cache.
     * @param capacity
     */
    public LRUCache(final int capacity) {
        this.capacity = capacity;
        this.queue  = new ConcurrentLinkedQueue<K>();
        this.map    = new ConcurrentHashMap<K, V>(capacity);
    }

    /**
     * Check whether the items exists in the cache. Returns null if key doesn't exists in the cache.
     * @param key
     * @return 
     */
    public V get(final K key) {
        return map.get(key);
    }

    /**
     * Add new value to the LRU Cache. If the key already exists, 
     * the key will be promoted to the front of the cache.
     * Neither the key nor the value can be null.
     * @param key
     * @param value
     * @throws NullPointerException
     */
    public synchronized void put(final K key, final V value) {
        if(key == null || value == null) {
            throw new NullPointerException();
        }
        if (map.containsKey(key)) {
            queue.remove(key);
        }
        while (queue.size() >= capacity) {
            K expiredKey = queue.poll();
            if (expiredKey != null) {
                map.remove(expiredKey);
            }
        }
        queue.add(key);
        map.put(key, value);
    }
}

You can also use a LinkedHashMap. But again, its not an ArrayList.

Also see Pro Android Apps Performance Optimization. Chapter 1, "Optimizing Java Code"; the section on "Caching Results" and LruCache<K, V>; and Chapter 4, "Using Memory Efficiently".

jww
  • 97,681
  • 90
  • 411
  • 885