AhmadLang / Java, How To Program, 2004
.pdf
red cyan white tan gray green orange blue peach
Method printNonDuplicates (lines 2435), which is called from the constructor, takes a Collection argument. Line 27 constructs a HashSet from the Collection argument. Note that both Set and HashSet are generic types. By definition, Sets do not contain any duplicates, so when the HashSet is constructed, it removes any duplicates in the Collection. Lines 3132 output elements in the Set.
[Page 940]
Sorted Sets
The collections framework also includes interface SortedSet (which extends Set) for sets that maintain their elements in sorted ordereither the elements' natural order (e.g., numbers are in ascending order) or an order specified by a Comparator. Class treeSet implements SortedSet. The program in Fig. 19.19 places strings into a treeSet. The strings are sorted as they are added to the treeSet. This example also demonstrates range-view methods, which enable a program to view a portion of a collection.
[Page 941]
Figure 19.19. Using SortedSets and treeSets.
(This item is displayed on pages 941 - 942 in the print version)
1// Fig. 19.19: SortedSetTest.java
2// Using TreeSet and SortedSet.
3import java.util.Arrays;
4import java.util.SortedSet;
5import java.util.TreeSet;
6
7public class SortedSetTest
8{
9private static final String names[] = { "yellow", "green",
10"black", "tan", "grey", "white", "orange", "red", "green" };
12// create a sorted set with TreeSet, then manipulate it
13public SortedSetTest()
14{
15// create TreeSet
16SortedSet< String > tree =
17new TreeSet< String >( Arrays.asList( names ) );
19System.out.println( "sorted set: " );
20printSet( tree ); // output contents of tree
22// get headSet based on "orange"
23System.out.print( "\nheadSet (\"orange\"): " );
24printSet( tree.headSet( "orange" ) );
26// get tailSet based upon "orange"
27System.out.print( "tailSet (\"orange\"): " );
28printSet( tree.tailSet( "orange" ) );
30// get first and last elements
31System.out.printf( "first: %s\n", tree.first() );
32System.out.printf( "last : %s\n", tree.last() );
33} // end SortedSetTest constructor
35// output set
36private void printSet( SortedSet< String > set )
37{
38for ( String s : set )
39System.out.printf( "%s ", s );
41System.out.println();
42} // end method printSet
44public static void main( String args[] )
45{
46new SortedSetTest();
47} // end main
48} // end class SortedSetTest
sorted set:
black green grey orange red tan white yellow
headSet ("orange"): black green grey
tailSet ("orange"): orange red tan white yellow first: black
last : yellow
[Page 942]
Lines 1617 of the constructor create a TReeSet of String that contains the elements of array names and assigns the SortedSet to the reference tree. Both SortedSet and TReeSet are generic types. Line 20 outputs the initial set of strings using method printSet (lines 3642), which we discuss momentarily. Line 24 calls treeSet method headSet to get a subset of the TReeSet in which every element is less than "orange". The view returned from headSet is then output with printSet. If any changes are made to the subset, they will also be made to the original treeSet because the subset returned by headSet is a view of the TReeSet.
Line 28 calls treeSet method tailSet to get a subset in which each element is greater than or equal to "orange". The view returned by tailSet is then output. Any changes made through the tailSet view are made to the original treeSet. Lines 3132 call SortedSet methods first and last to get the smallest and largest elements of the set, respectively.
Method printSet (lines 3642) accepts a SortedSet as an argument and prints it. Lines 3839 print each element of the SortedSet using the enhanced for statement.
[Page 942 (continued)]
19.10. Maps
Maps associate keys to values and cannot contain duplicate keys (i.e., each key can map to only one value; this is called one-to-one mapping). Maps differ from Sets in that Maps contain keys and values, whereas Sets contain only values. Three of the several classes that implement interface Map are
Hashtable, HashMap and TreeMap. Hashtables and HashMaps store elements in hash tables, and TReeMaps store elements in trees. This section discusses hash tables and provides an example that uses a HashMap to store key/value pairs. Interface SortedMap extends Map and maintains its keys in sorted ordereither the elements' natural order or an order specified by a Comparator. Class TReeMap implements
SortedMap.
Map Implementation with Hash Tables
Object-oriented programming languages facilitate creating new types. When a program creates objects of new or existing types, it may need to store and retrieve them efficiently. Storing and retrieving information with arrays is efficient if some aspect of your data directly matches a numerical key value and if the keys are unique and tightly packed. If you have 100 employees with nine-digit Social Security numbers and you want to store and retrieve employee data by using the Social Security number as a key, the task would require an array with one billion elements, because there are one billion unique nine-digit numbers (000,000,000999,999,999). This is impractical for virtually all applications that use Social Security numbers as keys. A program that had an array that large could achieve high
performance for both storing and retrieving employee records by simply using the Social Security number as the array index.
[Page 943]
There are numerous applications that have this problem, namely, that either the keys are of the wrong type (e.g., not positive integers that correspond to array subscripts) or they are of the right type, but sparsely spread over a huge range. What is needed is a high-speed scheme for converting keys such as Social Security numbers, inventory part numbers and the like into unique array indices. Then, when an application needs to store something, the scheme could convert the application's key rapidly into an index, and the record of information could be stored at that slot in the array. Retrieval is accomplished the same way: Once the application has a key for which it wants to retrieve a data record, the application simply applies the conversion to the keythis produces the array index where the data is stored and retrieved.
The scheme we describe here is the basis of a technique called hashing. Why the name? When we convert a key into an array index, we literally scramble the bits, forming a kind of "mishmashed," or hashed, number. The number actually has no real significance beyond its usefulness in storing and retrieving a particular data record.
A glitch in the scheme is called a collisionthis occurs when two different keys "hash into" the same cell (or element) in the array. We cannot store two values in the same space, so we need to find an alternative home for all values beyond the first that hash to a particular array index. There are many schemes for doing this. One is to "hash again" (i.e., to apply another hashing transformation to the key to provide a next candidate cell in the array). The hashing process is designed to distribute the values throughout the table, so the assumption is that an available cell will be found with just a few hashes.
Another scheme uses one hash to locate the first candidate cell. If that cell is occupied, successive cells are searched in order until an available cell is found. Retrieval works the same way: The key is hashed once to determine the initial location and check whether it contains the desired data. If it does, the search is finished. If it does not, successive cells are searched linearly until the desired data is found.
The most popular solution to hash-table collisions is to have each cell of the table be a hash "bucket," typically a linked list of all the key-value pairs that hash to that cell. This is the solution that Java's
Hashtable and HashMap classes (from package java.util) implement. Both Hashtable and HashMap implement the Map interface. The primary differences between them are that HashMap is unsynchronized (multiple threads can modify a HashMap concurrently), and allows null keys and null values.
A hash table's load factor affects the performance of hashing schemes. The load factor is the ratio of
38 |
if ( map.containsKey( word ) |
) |
// |
is word in |
map |
|
39 |
{ |
|
|
|
|
|
40 |
int count = map.get( |
word |
); // get current count |
|||
41 |
map.put( word, count |
+ 1 |
); |
// |
increment |
count |
42} // end if
43else
44 |
map.put( word, 1 ); // add new word with a count of 1 to map |
45} // end while
46} // end method createMap
48// display map content
49private void displayMap()
50{
51Set< String > keys = map.keySet(); // get keys
53// sort keys
54TreeSet< String > sortedKeys = new TreeSet< String >( keys );
56System.out.println( "Map contains:\nKey\t\tValue" );
58// generate output for each key in map
59for ( String key : sortedKeys )
60 |
System.out.printf( "%-10s%10s\n", key, map.get( key ) ); |
61 |
|
62System.out.printf(
63"\nsize:%d\nisEmpty:%b\n", map.size(), map.isEmpty() );
64} // end method displayMap
65
66public static void main( String args[] )
67{
68new WordTypeCount();
69} // end main
70} // end class WordTypeCount
Enter a string:
To be or not to be: that is the question Whether 'tis nobler to suffer Map contains:
Key |
Value |
'tis |
1 |
be |
1 |
be: |
1 |
is |
1 |
nobler |
1 |
not |
1 |
or |
1 |
question |
1 |
suffer |
1 |
that |
1 |
the |
1 |
to |
3 |
whether |
1 |
size:13
isEmpty:false
[Page 945]
Line 17 creates an empty HashMap with a default initial capacity (16 elements) and a default load factor (0.75)these defaults are built into the implementation of HashMap. When the number of occupied slots in the HashMap becomes greater than the capacity times the load factor, the capacity is doubled automatically. Note that HashMap is a generic class that takes two type arguments. The first type argument specifies the type of key (i.e., String), and the second type argument specifies the type of value (i.e., Integer). Recall that the type arguments passed to a generic class must be reference types,
hence the second type argument is Integer, not int. Line 18 creates a Scanner that reads user input from the standard input stream. Line 19 calls method createMap (lines 2446), which uses a map to store the number of occurrences of each word in the sentence. Line 27 invokes method nextLine of scanner to obtain the user input, and line 30 creates a StringTokenizer to breaks the input string into its component individual words. This StringTokenier constructor takes a string argument and creates a StringTokenizer for that string and will use the whitespace to separate the string. The condition in the while statement in lines 3345 uses StringTokenizer method hasMoreTokens to determine whether there are more tokens in the string being tokenized. If so, line 35 converts the next token to lowercase letters. The next token is obtained with a call to StringTokenizer method nextToken that returns a String. [Note: Section 29.6 discusses class StringTokenizer in detail.] Then line 38 calls Map method containsKey to determine whether the word is in the map (and thus has occurred previously in the string). If the Map does not contain a mapping for the word, line 44 uses Map method put to create a new entry in the map, with the word as the key and an Integer object containing 1 as the value. Note that autoboxing occurs when the program passes integer 1 to method put, because the map stores the number of occurrences of the word as Integer. If the word does exist in the map, line 40 uses Map method get to obtain the key's associated value (the count) in the map. Line 41 increments that value and uses put to replace the key's associated value in the map. Method put returns the prior value associated with the key, or null if the key was not in the map.
[Page 946]
Method displayMap (lines 4964) displays all the entries in the map. It uses HashMap method keySet (line 51) to get a set of the keys. The keys have type String in the map, so method keySet returns a generic type Set with type parameter specified to be String. Line 54 creates a treeSet of the keys, in which the keys are sorted. The loop in lines 5960 accesses each key and its value in the map. Line 60 displays each key and its value using format specifier %-10s to left justify each key and format specifier %10s to right justify each value. Note that the keys are displayed in ascending order. Line 63 calls Map method size to get the number of key-value pairs in the Map. Line 64 calls isEmpty, which returns a boolean indicating whether the Map is empty.
[Page 946 (continued)]
19.11. Properties Class
A Properties object is a persistent Hashtable that normally stores key-value pairs of stringsassuming that you use methods setProperty and getProperty to manipulate the table rather than inherited Hashtable methods put and get. By "persistent," we mean that the Properties object can be written to an output stream (possibly a file) and read back in through an input stream. In fact, most objects in Java can be output and input with Java's object serialization, presented in Chapter 14. A common use of Properties objects in prior versions of Java was to maintain applicationconfiguration data or user preferences for applications. [Note: The Preferences API (package
java.util.prefs), introduced in Java 1.4, is meant to replace the use of class Properties, but is beyond the scope of this book. To learn more, visit java.sun.com/j2se/5.0/docs/guide/lang/preferences.html.]
Class Properties extends class Hashtable. Figure 19.21 demonstrates several methods of class
Properties.
Figure 19.21. Properties class of package java.util.
(This item is displayed on pages 947 - 949 in the print version)
1 // Fig. 19.21: PropertiesTest.java
2 // Demonstrates class Properties of the java.util package.
3import java.io.FileOutputStream;
4import java.io.FileInputStream;
5import java.io.IOException;
6import java.util.Properties;
7import java.util.Set;
8
9public class PropertiesTest
10{
11private Properties table;
13// set up GUI to test Properties table
14public PropertiesTest()
15{
16table = new Properties(); // create Properties table
18// set properties
19table.setProperty( "color", "blue" );
20table.setProperty( "width", "200" );
22System.out.println( "After setting properties" );
23listProperties(); // display property values
25// replace property value
26table.setProperty( "color", "red" );
28System.out.println( "After replacing properties" );
29listProperties(); // display property values
31saveProperties(); // save properties
33table.clear(); // empty table
35System.out.println( "After clearing properties" );
36listProperties(); // display property values
38loadProperties(); // load properties
40// get value of property color
41Object value = table.getProperty( "color" );
43// check if value is in table
44if ( value != null )
45System.out.printf( "Property color's value is %s\n", value );
46else
47System.out.println( "Property color is not in table" );
48} // end PropertiesTest constructor
49
50// save properties to a file
51public void saveProperties()
52{
53// save contents of table
54try
55{
56FileOutputStream output = new FileOutputStream( "props.dat" );
57table.store( output, "Sample Properties" ); // save properties
58output.close();
59System.out.println( "After saving properties" );
60listProperties();
61} // end try
62catch ( IOException ioException )
63{
64ioException.printStackTrace();
65} // end catch
66} // end method saveProperties
67
68// load properties from a file
69public void loadProperties()
70{
71// load contents of table
72try
73{
74FileInputStream input = new FileInputStream( "props.dat" );
75table.load( input ); // load properties
76input.close();
77System.out.println( "After loading properties" );
78listProperties(); // display property values
79} // end try
80catch ( IOException ioException )
81{
82ioException.printStackTrace();
83} // end catch
84} // end method loadProperties
85
86// output property values
87public void listProperties()
88{
89Set< Object > keys = table.keySet(); // get property names
91// output name/value pairs
92for ( Object key : keys )
93{
94System.out.printf(
95 |
"%s\t%s\n", key, table.getProperty( ( String ) key ) ); |
96 |
} // end for |
97 |
|
98System.out.println();
99} // end method listProperties
101public static void main( String args[] )
102{
103new PropertiesTest();
104} // end main
105} // end class PropertiesTest
After setting properties color blue
width 200
After replacing properties color red
width 200
After saving properties
color red width 200
After clearing properties
After loading properties color red
width 200
Property color's value is red
[Page 947]
Line 16 uses the no-argument constructor to create an empty Properties table with no default properties. Class Properties also provides an overloaded constructor that receives a reference to a Properties object containing default property values. Lines 19 and 20 each call Properties method setProperty to store a value for the specified key. If the key does not exist in the table, setProperty returns null; otherwise, it returns the previous value for that key.
Line 41 calls Properties method getProperty to locate the value associated with the specified key. If the key is not found in this Properties object, getProperty returns null. An overloaded version of this method receives a second argument that specifies the default value to return if getProperty cannot locate the key.
Line 57 calls Properties method store to save the contents of the Properties object to the OutputStream object specified as the first argument (in this case, FileOutputStream output). The second argument, a String, is a description of the Properties object. Class Properties also provides method list, which takes a PrintStream argument. This method is useful for displaying the list of properties.
[Page 949]
Line 75 calls Properties method load to restore the contents of the Properties object from the InputStream specified as the first argument (in this case, a FileInputStream). Line 89 calls Properties method keySet to obtain a Set of the property names. Line 94 obtains the value of a property by passing a key to method getProperty.
[Page 949 (continued)]
19.12. Synchronized Collections
In Chapter 23, we discuss multithreading. The collections in the collections framework are unsynchronized by default, so they can operate efficiently when multithreading is not required. Because they are unsynchronized, however, concurrent access to a Collection by multiple threads could cause indeterminate results or fatal errors. To prevent potential threading problems, synchronization wrappers are used for collections that might be accessed by multiple threads. A wrapper object receives method calls, adds thread synchronization (to prevent concurrent access to the collection) and delegates the calls to the wrapped collection object. The Collections API provides a set of static methods for wrapping collections as synchronized versions. Method headers for the synchronization wrappers are listed in Fig. 19.22. Details about these methods are available at java.sun.com/j2se/5.0/docs/api/java/util/Collections.html. All these methods take a generic type and return a synchronized view of the generic type. For example, the following code creates a synchronized List (list2) that stores String objects:
|
|
|
[Page 950] |
|
|
|
List< String > list1 = new ArrayList< String >(); |
|
|
|
List< String > list2 = Collections.synchronizedList( list1 ); |
|
|
|
Figure 19.22. Synchronization wrapper methods. |
public static method headers |
|||
|
|
|
|
< |
T |
> |
Collection< T > synchronizedCollection( Collection< T > c ) |
< |
T |
> |
List< T > synchronizedList( List< T > aList ) |
< |
T |
> |
Set< T > synchronizedSet( Set< T > s ) |
< |
T > |
SortedSet< T > synchronizedSortedSet( SortedSet< T > s ) |
|
< |
K, V > Map< K, V > synchronizedMap( Map< K, V > m ) |
||
< |
K, |
V |
> SortedMap< K, V > synchronizedSortedMap( SortedMap< K, V > m ) |
|
|
|
|
