|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 2 of 2
The previous pattern is suitable for read-only data whose usage is short-lived (e.g., scoped to a method). It trades efficiency
for a weaker form of immutability: changes in the underlying data remain visible to the client code. For stronger immutability,
I can do the following: create an immutable class with a toImmutable() method that locks in the data by returning a copy of it. Consider these two classes implementing a simplified version of
a sorted int vector:
package vector;
public class SortedVector
{
public SortedVector (int capacity)
{
m_data = new int [capacity];
}
// ACCESSORS: [all methods are final]
public final int size ()
{
return m_size;
}
public final int get (int index)
{
return m_data [index];
}
// package-private constructor:
SortedVector (MutableSortedVector item)
{
// Note: 'm_data' array is not cloned here and is rather shared
// by reference with the original MutableSortedVector until
// the next MutableSortedVector mutator method is invoked [if ever]
m_data = item.m_data;
m_size = item.m_size;
}
int [] m_data;
int m_size;
}
package vector;
public final class MutableSortedVector extends SortedVector
{
public MutableSortedVector (int capacity)
{
super (capacity);
}
public SortedVector toImmutable ()
{
m_shared = true; // set 'shared' flag
return new SortedVector (this);
}
// MUTATORS:
// Add a new value; keep them in increasing order:
public void add (int value)
{
// [This simple implementation is O(size) and is used for
// illustrative purposes only]
if (m_size == m_data.length)
throw new IllegalStateException ("maximum capacity reached");
// If the data array is shared, it must be cloned before we can
// proceed [copy-on-write]:
if (m_shared)
{
m_data = (int []) m_data.clone ();
m_shared = false; // reset 'shared' flag
}
// Insert the new value after a simple linear scan:
int position;
for (position = 0; position < m_size; ++ position)
{
if (m_data [position] > value) break;
}
if (position < m_size)
System.arraycopy (m_data, position,
m_data, position + 1, m_size - position);
m_data [position] = value;
++ m_size;
}
private boolean m_shared; // if 'true', next mutator call must clone data
}
Even though both classes are public, observe how toImmutable() relies on a special package-private constructor provided by SortedVector for MutableSortedVector and nobody else. Implementing such conversion methods usually implies extra data copying and extra performance hits. But
if I know that the mutable instance will always be discarded right after it has been converted, I can simply pass pieces of
the internal MutableSortedVector state to the SortedVector constructor by reference to reduce overhead. Being certain of this knowledge is generally difficult, however, so I handle
the issue using a traditional compromise technique called copy-on-write. Observe carefully how the mutator and toImmutable() methods use the m_shared flag to copy data only when necessary.
The implementation technique outlined above is much like the one used by String and StringBuffer companion classes in the standard Java implementation, except that the mutable StringBuffer is not an extension of the immutable String.