DEV Community

Tincho
Tincho

Posted on

Streams de Java

Introducción

Una gran parte de lo que hacen los programas de red es una simple entrada y salida: mover bytes de un sistema a otro. Los bytes son bytes; en gran medida, leer los datos que le envía un servidor no es tan diferente de leer un archivo. Enviar texto a un cliente no es tan diferente de escribir en un archivo. Sin embargo, la entrada y salida (I/O) en Java es organizada diferentemente que en la mayoría de lenguajes como C o C++.

I/O en Java es construido con streams.

  • Input streams leen datos.
  • Output streams escriben datos.

Existen diferentes clases de streams para leer y escribir en diferentes fuentes de datos. Sin embargo, todos ellos con los mismos métodos básicos tanto para la escritura y lectura de datos, así luego de haber creado un stream puedas ignorar los detalles de lo que estás leyendo o escribiendo.

Como todo lo que se escribe o lee son bytes, existen los filter streams para modificar los datos de escritura y lectura. Estos pueden encadenarse para realizar transformaciones de los datos.

Ahora tenemos una herramienta un nivel un poco más alto que los filtros que nos ayuda a que podamos escribir programas para leer y escribir texto en lugar de bytes, estos son los Readers y Writers. En lugar que nosotros mismos transformemos a bytes nuestros datos, estos los hacen por nosotros.

Los streams son síncronos, es decir, cuando un programa pide al stream para leer o escribir datos, espera por el dato para ser leído o escrito antes de hacer cualquier cosa. Sin embargo, desde versiones posteriores a Java 1.4 soporta I/O sin bloqueo usando canales y buffers. Esto un poco más complicado, pero es muchos más rápido para aplicaciones de alto volumen de datos, tales como servidores web. De igual manera, I/O sin bloqueo no será discutido en este post, pero era valioso mencionarlo.

Para terminar la introducción, quiero que veas los streams como un sistemas de capas donde vamos de un nivel más bajo a uno más alto:

  1. Ocupando las clases bases de streams tendremos que hacer un poco de trabajo manipulando los bytes a los formatos que nosotros queremos.
  2. Ocupando los filtros ya nos facilita más el trabajo.
  3. Ocupando los Readers y Writers nos facilita más el manejo de texto.

Output streams

  • La clase de salida base de Java es java.io.OutputStream.
  • La clase proporciona los métodos necesarios para escribir datos.
  • Las clases que extiendan de esta usan sus métodos para escribir datos en distintos medios.
  • Consideraciones al utilizarla directamente y no por medio de un filtro: por problemas con el buffer siempre es aconsejable vaciarlo con flush().

Una vistazo de cómo se ve la clase OutputStream:

public abstract class OutputStream {
    //hay que implementarla segun el medio
    public abstract void write(int b) throws IOException
    //en lugar de enviar uno por uno, enviar un conjunto de bytes
    public void write(byte[] data) throws IOException
    public void write(byte[] data, int offset, int length) throws IOException
    //enviar los datos en buffer y vaciarlo
    public void flush( ) throws IOException
    //cerrar el stream
    public void close( ) throws IOException
}

Input Streams

  • La clase de entrada base de Java es java.io.InputStream.
  • Proporciona los métodos necesarios para leer datos como bytes crudos.
  • Cada clase que lo extienda usará sus métodos para leer datos de un particular medio. Una vistazo de cómo se ve la clase OutputStream:
public abstract class InputStream {
    //hay que implementarla segun el medio
    public abstract int read() throws IOException
    //en lugar de enviar uno por uno, enviar un conjunto de bytes
    public int read(byte[] input) throws IOException
    public int read(byte[] input, int offset, int length) throws IOException

    public int skip(long n) throws IOException
    public int available() throws IOException
    public void close() throws IOException
}

Ejemplo

Vamos a comunicar un servidor y cliente mediante una conexión sockets TCP. En el ejemplo el cliente hace uso del OutputStream del socket para escribir el mensaje y el servidor hace uso del InputStream para leer el mensaje. Para esto el mensaje a enviar debe pasar de su formato original (String) a un array de bytes y viceversa en el servidor. El código se ve así:

public class Cliente {
    public static void main(String[] args) {
        String mensaje = "Hola servidor!";
        byte bytesDelMensaje[] = mensaje.getBytes();
        try {
            Socket socket = new Socket("localhost", 8000);
            OutputStream alServidor = socket.getOutputStream();
            alServidor.write(bytesDelMensaje);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Servidor {
    public static void main(String[] args) {
        ServerSocket svc;
        try {
            svc = new ServerSocket(8000);
            Socket socket = svc.accept();

            byte bytesDelMensaje[] = new byte[1024];

            InputStream delCliente = socket.getInputStream();
            delCliente.read(bytesDelMensaje);
            String mensaje = new String(bytesDelMensaje);
            System.out.println(mensaje);

            socket.close();
            svc.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Quiero que tengas en cuenta que hay muchos detalles que se pueden implementar si se quiere llevar a mundo más real, donde la comunicación se realice por una WAN y no localmente en una computadora. Pero a fines de aprender lo esencial y las bases de cómo funciona, es suficiente.En otro posts podría explicar esos detalles.

Filters

Dentro de lo que son los filtros tenemos dos versiones:

  • los filter streams.
  • los readers y writers. Estos filtros son encadenables, esto hace muy flexible la transformación de los datos. ## Filter Streams Trabajan con datos crudos como los bytes y tenemos varias clases de estos que proporcionan distintas funciones como:
  • Comprimir los datos a números binarios.
  • Encriptar datos.
  • Hacer un buffer de software para almacenar los datos.
  • etc.

Entre estos filtros tenemos:

  • BufferedStream.
  • PrintStream.
  • PushbackInputStream.
  • DataStream.
  • CompressingStream.
  • DigestStream.
  • EncryptingStream.

Ejemplo

Vamos a utilizar el ejemplo anterior pero implementando el filtro DataStream para que puedan apreciar 2 cosas:

  • El encadenamiento que se puede ir haciendo desde un nivel más bajo (InputStream o OutputStream) a un nivel más alto (DataInputStream y DataOutputStream).
  • Y ya no nos preocupamos de la transformación de String a bytes y viceversa. El código se ve así:
public class Cliente {
    public static void main(String[] args) {
        String mensaje = "Hola servidor!";

        try {
            Socket socket = new Socket("localhost", 8000);
            DataOutputStream alServidor = new DataOutputStream(socket.getOutputStream());
            alServidor.writeUTF(mensaje);

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Servidor {
    public static void main(String[] args) {
        ServerSocket svc;
        try {
            svc = new ServerSocket(8000);
            Socket socket = svc.accept();

            DataInputStream delCliente = new DataInputStream(socket.getInputStream());
            String mensaje = delCliente.readUTF();
            System.out.println(mensaje);

            socket.close();
            svc.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Writers y Readers

Trabajan con el manejo de casos especiales de texto y su variedad de codificados tales como UTF-8 e ISO 8859-1.
Por ahora, quiero que de estos tengas las idea como son copias de todo lo anterior, pero en lugar de trabajar con bytes nos abstrae a trabajar con la variedad de textos que hay disponibles.

Top comments (0)