Arraylist Usage Examples

It is important to use array lists in a proper manner, otherwise you might degrade your code's performance. Both performance and memory usage can suffer greatly when you use a dynamic array in a way that is not appropriate for the particular data structure being used.

Note:

Inserting and removing in array lists are inefficient unless the target elements are at the end of the array.

Fast Multiple Value Lookups

Suppose you have a collection of values which doesn't charge often and you wish to invoke a number of find element operations on it. The following piece of code demonstrates how to do this by means of sorting and binarySearch. The results of the binarySearch method will be undefined if it operates on an array list that is not sorted in ascending order.

Example 16.2. Extremely fast array searching using binary Search

ArrayListLong l = new ArrayListLong ();
// insert your values into the arraylist here
        
l.sort ();
        
// test if the element 1021 is stored in the array (fast!)
if (l.binarySearch (102l) != -1) 
{
    // element was found, do something here
    // ..
}
	
int pos;
if ((pos = l.binarySearch (102l)) != -1) {
    // element was found, do something here with pos
    // ..
}

Remember that the array needs to be resorted when the data in it changes. Otherwise you can do as many lookup operations by means of binarySearch as you wish. The binarySearch method has an upper time limit of O(log n), which is very close to constant time.

ArrayList Size And Capacity

The difference between size and capacity is crucial for the array lists. The capacity denotes the number of elements that are currently allocated and is equal to or larger than the size of any array list. If a single element or a whole list of elements is added to a given ArrayList, storage is increased by a factor of 1.5 every time it becomes necessary. This way the number of allocations and copy operations is kept rather small and performance improves.

You can, however, hand-tune the capacity of an array list if you know beforehand how many elements you will store in it. Arrays never shrink in size, so if you can make a good assumption about the long-term capacity you will need for a particular array list, use the constructor that takes a single integer argument to specify the initial capacity. Doing so will prevent any further internal reallocations of the data storage and your array list will be as efficient as possible.

Example 16.3. Appending to an array efficiently

// arraylist that keeps 100 random integers;
ArrayListInt l = new ArrayListInt (100);
Random rnd = new Random ();
for (int i = 0; i < 100; ++i)
   l.add (rnd.nextInt());

The above code snippet allocates an internal data array with an initial capacity of 100 elements. Only one invocation of the new operator will occur and the efficiency of the code will be very good. If you need to add an array of integers to the above array list, you should write code like this:

Example 16.4. Appending to an array efficiently (again)

void addIntegerArrayToArrayList (ArrayListInt list, int arr[])
{
    // call that allows us to get away with just one internal allocation
    list.ensureCapacity (list.size() + arr.length);
    // now add the values
    for (int i = 0; i < arr.length; ++i)
        list.add (arr[i]);
}

As in the first example, the above code will only perform one single allocation for appending all of the array's elements.

Iterators

The array lists of the Tensegrity API provide iterators for convenient navigation through the lists. While these iterators are not thread-safe by default, attempting to change the sychronization would result in an unacceptable overhead, due to monitoring checks.

In the Tensegrity API, each iterator references a list and has a version number associated with it. In the event that an iterator's referenced list changes, the version number of the iterator is used to check multiple modifications of the list. When this is the case, an iterator will throw an exception to indicate that its use is no longer safe. The exception class is ArrayListModifiedException and an iterator which has thrown this exception is tagged to be useless from then on. As noted before, one can never rely on this mechanism to work in a multi-threaded Java application, as only the explicit and implicit entering and leaving of monitors trigger main memory updates in a Java virtual machine. In addition, every thread is allowed to have its own working copy of the main memory. The iterators of the array lists return the primitives of the specific underlying arraylist directly, so there is no way of implementing the standard API's java.util.Iterator interface, which is limited to returning subclasses of java.lang.Object.

Each concrete ArrayListTYPE class has two different kinds of iterators - one basic iterator of type ArrayList TYPE.Iterator that allows only forward iteration and a slightly more sophisticated iterator that allows accessing the current index and backward iteration as well. This advanced iterator has the type ArrayListTYPE.ListIterator and is also capable of starting at a user-defined position in the ArrayList. [8]

Example 16.5. Iterating through an arraylist

// compute the sum of the elements in an ArrayList
ArrayListLong l= new ArrayListLong(100);
for(int i= 0; i< 10; ++ i)
{
    l.add(100l+ i);
}
long sum;

// forward iteration
sum= 0;
ArrayListLong.ListIterator li= (ListIterator) l.iterator();
while(li.hasNext())
{
    long i= li.next();
    sum+= i;
}

// reverse iteration
sum= 0;
while(li.hasPrevious())
{
    long i= li.previous();
    sum+= i;
}



[8] The placeholder TYPE stands for the actual primitive type that is being used in the particular array list.