Utilizar Canales para Implementar Tuberías

El paquete java.io contiene dos clases, PipedInputStream y PipedOutputStream, que implementan los componentes de entrada y salida para una tubería. Las tuberías se utilizan para canalizar la salida de un programa (o un thread) hacia la entrada de otro.

Los canales de entrada y salida para tuberías son convenientes para métodos que producen una salida para ser utiliza como entrada de otro método. Por ejemplo, supongamos que estamos; escribiendo una clase que implementa varias utilidades de texto, como la ordenación y la inversión del texto. Seria bonito que la salida de uno de esos métodos pudiera utilizarse como la entrada de otro. Así podríamos concatenar una serie de esos métodos para realizar alguna función. La tubería utilizada aquí utiliza los métodos reverse, sort y reverse sobre una lista de palabras para ordenarla de forma rítmica:

Sin los canales de tuberías, tendríamos que crear un fichero temporal entre cada uno de los pasos. Echemos un vistazo al programa que implementa los métodos reverse y sort descritos arriba utilizando los canales de tuberías, y luego utiliza estas métodos para mostrar la tubería anterior para generar una lista de palabras rítmicas.

Primero, la clase RhymingWords contiene tres métodos: main(), reverse(), y sort(). El método main() proporciona el código para el programa principal, el cual abre un fichero de entrada, utiliza los otros dos métodos para invertir, ordenar y volver a invertir las palabras del fichero de entrada, y escribe la salida en el canal de salida estandard.

reverse() y sort() están diseñados para ser utilizados en una tubería. Estos dos métodos leen datos desde un InputStream, los procesan (invirtiendo las cadenas u ordenandolas), y producen un PipedInputStream adecuado para que otro método pueda leerlo. Echemos un vistazo en más detalle al método reverse(); sort es muy similar al anterior, por eso no merece la pena explicarlo.

public static InputStream reverse(InputStream source) {
    PipedOutputStream pos = null;
    PipedInputStream pis = null;

    try {
        DataInputStream dis = new DataInputStream(source);
        String input;

        pos = new PipedOutputStream();
        pis = new PipedInputStream(pos);
        PrintStream ps = new PrintStream(pos);

        new WriteReversedThread(ps, dis).start();

    } catch (Exception e) {
        System.out.println("RhymingWords reverse: " + e);
    }
    return pis;
}
El método reverse() toma un InputStream llamado source que contiene la lista de cadenas a invertir. reverse() proyecta un DataInputStream sobre el InputStream source para que pueda utilizar el método readLine() de DataInputStream para leer una a una las líneas del fichero. (DataInputStream es un canal filtrado que debe ser añadido a un InputStream (o proyectado sobre) cuyos datos se deben filtrar mientras son leidos.Trabajar con Canales Filtrados explica más cosas sobre esto.)

Luego reverse() crea un PipedOutputStream y le conecta un PipedInputStream. Recuerda que un PipedOutputStream debe estar conectado a un PipedInputStream. Después, reverse() proyecta un PrintStream sobre el PipedOutputStream para que pueda utilizar el método println() de PrintStream para escribir las cadenas en el PipedOutputStream.

Ahora reverse() crea un objeto thread WriteReversedThread, que cuelga de dos canales -- el PrintStream añadido a PipedOutputStream y el DataInputStream añadido a source-- y lo arranca. El objeto WriteReversedThread lee las palabras desde el DataInputStream, las invierte y escribe la salida en el PrintStream (por lo tanto, escribe la salida en una tubería). El objeto thread permite que cada final de la tubería se ejecute independientemente y evita que el método main() se bloquee si una de las tuberías se bloquea mientras espera a que se complete una llamada a I/O.

Aquí tienes el método run de WriteReversedThread:

public void run() {
    if (ps != null && dis != null) {
        try {
            String input;
            while ((input = dis.readLine()) != null) {
                ps.println(reverseIt(input));
                ps.flush();
            }
            ps.close();
        } catch (IOException e) {
            System.out.println("WriteReversedThread run: " + e);
        }
    }
} 
Como el PipedOutputStream está conectado al PipedInputStream, todos los datos escritos en el PipedOutputStream caen dentro del PipedInputStream. Los datos pueden ser leidos desde PipedInputStream por cualquier otro programa o thread. reverse() devuelve el PipedInputStream para que lo utilice el programa llamador.

El método sort() sigue el siguiente patrón:

Las llamadas a los métodos reverse() y sort() pueden situarse en cascada para que la salida de un método pueda ser la entrada del siguiente método. De hecho, el método main() lo hace. Realiza llamadas en casacada a reverse(), sort(), y luego a reverse() para generar una lista de palabras rítmicas:
InputStream rhymedWords = reverse(sort(reverse(words)));
Cuando ejecutes RhymingWords con este fichero de texto verás la siguiente salida:
Java
interface
image
language
communicate
integrate
native
string
network
stream
program
application
animation
exception
primer
container
user
graphics
threads
tools
class
bolts
nuts
object
applet
environment
development
argument
component
input
output
anatomy
security
Si miras detenidamente puedes ver que las palabras rítmicas como environment, development, argument, y component están agrupadas juntas.


Ozito