|
Tech Tips Archive
February 08, 2001
WELCOME to the Java Developer Connection sm(JDC) Tech Tips, February 8, 2001. This issue covers:
This tip was developed using Java 2 SDK, Standard Edition, v 1.3.
This issue of the JDC Tech Tips is written by Glen McClusky.
PIPED STREAMS
Piped streams are a mechanism in the Java I/O library to set up
a stream of data between two threads. Pipes are input/output
pairs. Data written on the output stream shows up on the input
stream at the other end of the pipe. You can think of pipes as
a buffer, with a connection at each end.
Before getting into the details of pipes, here is a simple
example of how you can use them:
import java.io.*;
class MyThread extends Thread {
private Writer out;
public MyThread(Writer out) {
this.out = out;
}
public void run() {
// write into one end of pipe
try {
BufferedWriter bw = new
BufferedWriter(out);
bw.write("testing");
bw.newLine();
bw.close();
}
catch (IOException e) {
System.err.println(e);
}
}
}
public class PipeDemo1 {
public static void main(String args[])
throws IOException {
// create a pipe
PipedWriter writer = new PipedWriter();
PipedReader reader = new
PipedReader(writer);
// start thread going and write into pipe
new MyThread(writer).start();
// read from other end of pipe
BufferedReader br = new
BufferedReader(reader);
String str = br.readLine();
br.close();
System.out.println(str);
}
}
|
PipeDemo1 creates a pipe, consisting of a PipedWriter object, and
a PipedReader on the other end of the pipe. The PipedReader reads
data written by the PipedWriter.
PipeDemo1 then creates and starts an instance of MyThread. The
MyThread thread writes into one end of the pipe, then the main
thread reads the data that was written, that is:
testing
When piped streams are used to communicate between threads, the
action of the threads is coordinated. For example, if a thread
tries to read from a pipe, and no input is available, the thread
blocks, that is, it waits until input is available, preventing
other activity on the thread. The thread also blocks if it writes
into a pipe, and the pipe buffer fills up.
If both the pipe's reader and writer are the same thread, and the
pipe fills up, the thread blocks permanently. In other words, if
a thread writes into a pipe, and the pipe's buffer is full, the
thread blocks until the reader on the other end can take some of
the data from the buffer. But if the reader and writer are the
same thread, the thread is blocked, and so the reader cannot do
its job. Here's an example of a program that hangs because of
a blocked thread:
import java.io.*;
public class PipeDemo2 {
public static void main(String args[])
throws IOException {
// create a pipe
PipedWriter writer = new PipedWriter();
PipedReader reader = new
PipedReader(writer);
// write into one end of pipe, writing
// more than the internal buffer size
BufferedWriter bw = new
BufferedWriter(writer);
for (int i = 1; i <= 200; i++) {
bw.write("testing");
bw.newline();
}
bw.close();
// read from the pipe, from within the
// same thread as the writer above
bufferedreader br = new
bufferedreader(reader);
string str = br.readline();
br.close();
system.out.println(str);
}
}
|
The internal buffer size is 1024, but you can't rely on this
number in writing your programs. PipeDemo2 overflows this buffer,
and because of this, the writer thread blocks. The program hangs
because the writer and reader threads are the same, and there is
no way for the reader to reduce the size of the buffer contents.
How can you use piped streams in practice? One example is an
application that has a producer thread that generates data, and
another thread that consumes the data. You could use a piped
stream to communicate between the threads. Here's what this looks
like:
import java.util.*;
import java.io.*;
class ProdThread extends Thread {
private static final int N = 100;
private static final int MAXLEN = 63;
private Writer out;
public ProdThread(Writer out) {
this.out = out;
}
public void run() {
Random rn = new Random(0);
char num[] = new char[MAXLEN];
try {
// write N lines of binary numbers,
// each binary number
// 1 - MAXLEN digits in length
BufferedWriter bw = new
BufferedWriter(out);
for (int i = 1; i <= n; i++) {
int len = rn.nextint(maxlen) + 1;
for (int j = 0; j < len; j++) {
num[j] =
(rn.nextboolean() ? '1' : '0');
}
bw.write(num, 0, len);
bw.newline();
}
bw.close();
}
catch (ioexception e) {
system.err.println(e);
}
}
}
class consthread extends thread {
private reader in;
public consthread(reader r) {
in = r;
}
public void run() {
// read the generated test data that
// was written into the pipe above
try {
bufferedreader br = new
bufferedreader(in);
string str;
while ((str = br.readline()) != null)
{
system.out.println(str);
}
br.close();
}
catch (ioexception e) {
system.err.println(e);
}
}
}
public class pipedemo3 {
public static void main(string args[])
throws ioexception {
// create a pipe
pipedwriter writer = new pipedwriter();
pipedreader reader = new
pipedreader(writer);
// create and start the generator
// and consumer threads
thread prodthread = new
prodthread(writer);
thread consthread = new
consthread(reader);
prodthread.start();
consthread.start();
}
}
|
In this demonstration, the producer thread, ProdThread,
generates random binary numbers to be used as test data for the
consumer thread, ConsThread. The numbers are written into a pipe.
Then ConsThread reads the values from the other end of the pipe.
The first few of lines of output look like this:
1011010110001111010000001001011001
001111011111101011110100101110000001010
0111111
There are other ways to produce the same results. For example,
you could have the producer thread fill up a collection object
such as an ArrayList, and pass it to the consumer. However, if
the data is very large, this could chew up a lot of memory.
Also this approach doesn't allow the threads to run
simultaneously, something that might be important if you have
multiple CPUs available. You could use temporary files, but
this technique has many of the same problems.
Or you could use a limited size data structure, and coordinate
the action of the threads so that when one thread fills the
structure, it blocks. Then the other thread empties the
structure. But if you're going to do it this way, you might as
well use piped streams, and let them take care of the details
for you.
For more information, see Section 15.4.4, Piped Streams, in
"The Java Programming Language Third Edition" by Arnold,
Gosling, and Holmes
(http://java.sun.com/docs/books/javaprog/thirdedition/).
USING SETS
Set and SortedSet are Java collection interfaces. You use them to
specify a collection that has no duplicate elements. These
interfaces are implemented using the HashSet and TreeSet classes.
Here's a simple example:
import java.util.*;
public class SetDemo1 {
public static void main(String args[]) {
boolean b;
// create a set
Set s = new HashSet();
// add strings the first time
b = s.add("string1");
System.out.println("string1
add returns " + b);
b = s.add("string2");
System.out.println("string2
add returns " + b);
b = s.add("string3");
System.out.println("string3
add returns " + b);
// try to add some duplicate strings
b = s.add("string1");
System.out.println("string1
add returns " + b);
b = s.add("string2");
System.out.println("string2
add returns " + b);
// dump out set contents
Iterator i = s.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
|
This example creates a set and then adds some strings to it. The
add method returns true. So the output for the initial adds is:
string1 add returns true
string2 add returns true
Sets do not allow duplicate elements, so when an attempt is made
to add duplicates, the add method returns false. The output
for these attempts is:
string1 add returns false
string2 add returns false
After the elements are added, the contents of the set are
displayed using an iterator. The output is:
string1
string3
string2
This output illustrates an important fact about sets: by default,
the elements retrieved by the iterator are not in sorted order.
If you want to change that, you can use a SortedSet:
import java.util.*;
public class SetDemo2 {
public static void main(String args[]) {
// create a SortedSet implemented as a tree structure
SortedSet s = new TreeSet();
s.add("string1");
s.add("string2");
s.add("string3");
s.add("string1");
s.add("string2");
Iterator i = s.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
|
Using the SortedSet in SetDemo2 returns the elements in natural
order:
string1
string2
string3
You can also specify a Comparator object to order the set's
elements.
How do sets differ from lists and maps? Sets are different from
lists in that they have no duplicate elements. Unlike lists,
there is no way to manipulate set elements based on their
position in the set. For example, you can't retrieve the 17th
element of a set by random access, or insert an element before
the 59th element of the set. Sets are are different from maps in
that a map contains pairs of elements (that is, key/value pairs),
with each key mapped to a value.
An important concept relating to sets is defining what
constitutes a duplicate element. Consider this example:
import java.util.*;
public class SetDemo3 {
public static void main(String args[]) {
// create a set and add two objects
// to it; the objects are distinct
// but have the same displayable string
Set s = new HashSet();
s.add("37");
s.add(new Integer(37));
Iterator i = s.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
|
In this example, the two elements have the same printable string
(37), but are distinct when compared using the equals method.
This example leads to an important question. If you use a
SortedSet, where the elements must be ordered, what happens if you
add elements that have nothing in common with each other, that is,
the elements have object types that cannot be compared? Here's an
example that tries to do that:
import java.util.*;
public class SetDemo4 {
public static void main(String args[]) {
// create a SortedSet
SortedSet s = new TreeSet();
// add two objects to the set;
// the objects are not comparable
s.add("37");
s.add(new Integer(37));
Iterator i = s.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
|
SetDemo4 adds a string "37" and an Integer object "37" to a set.
When the program runs, the result is a ClassCastException. The
exception is thrown because an attempt is made to order the
elements of the set. A String object has no relationship to
an Integer object, so the relative order of the two objects
cannot be determined.
Because of the ordering implied by SortedSets, there are some
additional operations you can do with this type of set. Here's
a program that illustrates these operations:
import java.util.*;
public class SetDemo5 {
public static void main(String args[]) {
// create a SortedSet
SortedSet s = new TreeSet();
// add some elements to it
s.add("string1");
s.add("STRING2");
s.add("STRING3");
s.add("string4");
s.add("STRING5");
// get the first/lowest object in the set
System.out.println("first
= " + s.first());
// get a subset of the set and display it
Set sub = s.subSet("A", "ZZZZZZ");
Iterator i = sub.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
|
This example creates a set, adds some elements to it, and
retrieves the first element of the set. Then it retrieves and
displays a subset. The subset consists of elements greater than
or equal to a minimum element, and less than a maximum element.
Here is the output:
first = STRING2
STRING2
STRING3
STRING5
A subset is an example of a "backing view," that is, it's not
a copy of elements in the original set, but an actual view of
the set that filters out some elements. This means that if the
original set changes, so will the subset. Here's a program that
illustrates this point:
import java.util.*;
public class SetDemo6 {
public static void main(String args[]) {
// create a sorted set
SortedSet s = new TreeSet();
s.add("string1");
s.add("STRING2");
s.add("STRING3");
s.add("string4");
s.add("STRING5");
// get a subset of the set and display it
Set sub = s.subSet("A", "ZZZZZZ");
Iterator i = sub.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
System.out.println();
// remove an element from the original set
s.remove("STRING3");
// display the subset again
i = sub.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
|
Here is the displayed output of the SetDemo6 program:
STRING2
STRING3
STRING5
STRING2
STRING5
|
Notice that the second time the subset is displayed, the
"STRING3" element is gone. That's because it was removed from
the original set that backs the subset.
A final point about sets is illustrated by the following
example of bad programming style:
import java.util.*;
class MyObject {
public String s;
public String toString() {
return s;
}
public int hashCode() {
return s.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof MyObject)) {
return false;
}
MyObject mo = (MyObject)o;
boolean b = s.equals(mo.s);
return b;
}
}
public class SetDemo7 {
public static void main(String args[]) {
Set s = new HashSet();
// create two MyObjects and
// add them to a set
MyObject obj1 = new MyObject();
MyObject obj2 = new MyObject();
obj1.s = "string1";
obj2.s = "string2";
s.add(obj1);
s.add(obj2);
// change one of the object's contents
obj2.s = "string1";
// remove both objects from the set
s.remove(obj1);
s.remove(obj2);
// dump out the contents of the set
Iterator i = s.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
}
|
A set cannot have duplicate elements. This example shows what can
happen if you violate this rule by changing an element's contents
after the fact. The example adds two MyObject objects to a set.
The objects are distinct from each other. Then one of the objects
is changed so that it is no longer distinct. Then both objects
are removed from the set, and the contents of the set are
displayed. The set should be empty, but it's not, as you can see
from the displayed result:
string1
To understand why the set is not empty, consider some internal
details of set implementation. A Set is implemented using a
HashMap, with the set element as the key, and a dummy object as
the value to be inserted in the map.
If you insert two elements into a map, they are inserted at
random points in the map, based on their hash codes. If you
change the key for one of the elements, without updating the map,
the element in the map is "stranded." This means that the element
is probably inaccessible except through a sequential scan of the
map. That's why you see the anomalous behavior illustrated by the
SetDemo7 program.
For more information about sets, see Section 16.5, Set and
SortedSet, in "The Java Programming Language Third Edition"
by Arnold, Gosling, and Holmes
(http://java.sun.com/docs/books/javaprog/thirdedition/)
Note
Sun respects your online time and privacy. The Java Developer
Connection mailing lists are used for internal Sun MicrosystemsTM purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page
(https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the
appropriate checkbox, and click the Update button.
Subscribe
To subscribe to a JDC newsletter mailing list, go to the
Subscriptions page (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to, and click Update.
Feedback
Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com
Archives
You'll find the JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
Copyright
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
JDC Tech Tips
February 08, 2001
|