Escribir Nuestros Propios Canales Filtrados

Lo siguiente es una lista de pasos a seguir cuando se escriban canales de I/O filtrados propios: Esta página le muestra cómo implementar sus propios canales filtrados a través de un ejemplo que impletan un par de canales de entrada y salida.

Los dos canales utilizan una clase Checksumm para calcular la suma de los datos escritos o leidos en un canal. La suma puede ser utiliza para determinar si los datos leidos por la entrada corresponden con los escritos en la salida.

Este programa lo componen cuatro clases y un interface:

La Clase CheckedOutputStream

La clase CheckedOutputStream es una subclase de FilterOutputStream que calcula la suma de los datos que se están escribiendo en un canal. Cuando se cree un CheckedOutputStream se debe utilizar su único constructor:
public CheckedOutputStream(OutputStream out, Checksum cksum) {
    super(out);
    this.cksum = cksum;
}
Este constructor toma dos argumentos: un OutputStream y un Cheksum. El argumento OutputStream es el canal de salida que CheckedOutputStream debería filtrar. El objeto Checksum es un objeto que puede calcular una suma. CheckepOutputStream se inicializa a sí mismo llamando al constructor de su superclase e inicializa una variable privada, cksum, con el objeto Checksum. El CheckedOutputStream utiliza cksum que actualiza la suma en el momento en que se están escribiendo los datos en el canal.

CheckedOutputStream necesita sobreescribir los métodos write() de FilterOutputStream para que cada vez que se llame a un método write() la suma se actualice. FilterOutputStream define tres versiones del método write():

  1. write(int i)
  2. write(byte[] b)
  3. write(byte[] b, int offset, int length)
CheckedOutputStream sobreescribe los tres métodos:
public void write(int b) throws IOException {
    out.write(b);
    cksum.update(b);
}

public void write(byte[] b) throws IOException {
    out.write(b, 0, b.length);
    cksum.update(b, 0, b.length);
}

public void write(byte[] b, int off, int len) throws IOException {
    out.write(b, off, len);
    cksum.update(b, off, len);
}
La implementación de estos tres métodos write() es sencilla: escriben los datos en el canal de salida al que este canal filtrado ha sido añadido, y luego actualizan la suma.

La Clase CheckedInputStream

La clase CheckedInputStream es muy similar a la clase CheckedOutputStream. CheckedInputStream es una subclase de FilterInputStream que calcula la suma de los datos que están siendo leidos desde un canal. Cuando se cree un CheckedInputStream, debe utilizar su único constructor:
public CheckedInputStream(InputStream in, Checksum cksum) {
    super(in);
    this.cksum = cksum;
}
Este constructor es similar al constructor de CheckedOutputStream.

Al igual que CheckedOutputStream se necesitará sobreescribir los métodos wite() de FilterOutputStream, CheckedInputStream necesita sobreescribir los métodos read() de FilterInputStream, para que cada vez que se llame a un método read() se actualice la suma. FilterInputStream también define tres versiones del método read() y CheckedInputStream los sobreescribe:

public int read() throws IOException {
    int b = in.read();
    if (b != -1) {
        cksum.update(b);
    }
    return b;
}

public int read(byte[] b) throws IOException {
    int len;
    len = in.read(b, 0, b.length);
    if (len != -1) {
        cksum.update(b, 0, b.length);
    }
    return len;
}

public int read(byte[] b, int off, int len) throws IOException {
    len = in.read(b, off, len);
    if (len != -1) {
        cksum.update(b, off, len);
    }
    return len;
}
La implementación de estos tres métodos read() es sencilla: leen los datos del canal de entrada al que este; canal filtrado ha sido añadido, y luego si se ha leido algún dato, actualizan la suma.

El Interface Checksum y la Clase Adler32

El interface Checksum define cuatro métodos para sumar objetos, estos métodos resetean, actualizan y devuelve el valor de la suma. Se podría escribir una clase Checksum que calcule la suma de un tipo de dato específico como la suma de CRC-32. Observa que la herencia en la suma es la noción de estado. El objeto checksum no sólo calcula una suma de una forma. En su lugar, la suma es actualizada cada vez que se lee o escribe información en el canal sobre el que este objeto está calculando la suma. Si quieres reutilizar un objeto Checksum, debes resetearlo.

Para este ejemplo, implementamos el objeto checksum Adler32, que es casi una versión de un objeto checksum CRC-· pero que puede ser calculado mucho más rápidamente.

Un Programa Principal para Probarlo

La última clase de este ejemplo, CheckedIOTest, contiene el método main() para el programa:
import java.io.*;

class CheckedIOTest {
    public static void main(String[] args) {

       Adler32 inChecker = new Adler32();
       Adler32 outChecker = new Adler32();
       CheckedInputStream cis = null;
       CheckedOutputStream cos = null;

       try {
           cis = new CheckedInputStream(new FileInputStream("farrago.txt"), inChecker);
           cos = new CheckedOutputStream(new FileOutputStream("outagain.txt"), outChecker);
       } catch (FileNotFoundException e) {
           System.err.println("CheckedIOTest: " + e);
           System.exit(-1);
       } catch (IOException e) {
           System.err.println("CheckedIOTest: " + e);
           System.exit(-1);
       }

       try {
           int c;

           while ((c = cis.read()) != -1) {
              cos.write(c);
           }

           System.out.println("Suma del Canal de Entrada: " + inChecker.getValue());
           System.out.println("Suma del Canal de Salida:: " + outChecker.getValue());

           cis.close();
           cos.close();
       } catch (IOException e) {
           System.err.println("CheckedIOTest: " + e);
       }
    }
}
El método main() crea dos objetos checksum Adler32, uno para CheckedOutputStream y otro para CheckedInputStream. El ejemplo requiere dos objetos checksum porque se actualizan durante las llamadas a read() y write() y estas llamadas ocurren de forma concurrente.

Luego, main() abre un CheckedInputStream sobre un pequeño fichero de texto farrago.txt, y un CheckedOutputStream sobre un fichero de salida llamado outagain.txt (que no existirá hasta que se ejecute el programa por primera vez).

El método main() lee el texto desde CheckedInputStream y lo copia en CeckedOutputStream. Los métodos read() y write() utilizar el objeto checksum Adler32 para calcular la suma durante la lectura y escritura. Después de que se haya leido completamente el fichero de entrada ( y consecuentemente, se haya terminado de escribir el fichero de salida), el programa imprime la suma de los dos canales ( que deberían corresponder) y luego cierra los dos canales.

Cuando se ejecute CheckedIOTest deberías ver esta salida:

Suma del Canal de Entrada: 736868089
Suma del sanal de Salida: 736868089

Filtrar Ficheros de Acceso Aleatorio

Los canales filtrados del paquete java.io descienden de InputStream o OutputStream que implementan los ficheros de acceso secuencial. Entonces, si se subclasifica FilterInputStream o FilterOutputStream los canales filtrados también serán ficheros de acceso secuencial. Escribir filtros para ficheros de acceso aleatorio más adelante es está lección le muestra cómo re-escribir este ejemplo para que trabaje en un RandomAccessFile así como con un DataInputStream o un DataOutputStream.


Ozito