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.
Inserting and removing in array lists are inefficient unless the target elements are at the end of the array.
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.
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.
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.
© 2004, 2005 Tensegrity Software GmbH