Streams de Bytes con ejemplos en Java

Avanzado

Comenzaremos nuestro estudio de las E/S de Java con los flujos de bytes. Como se explicó, en la parte superior de la jerarquía de flujos de bytes están las clases InputStream y OutputStream.

Lectura Recomendada
Entrada y Salida de datos con Streams

1. Métodos de Streams de Bytes

La Tabla 1 muestra los métodos en InputStream, y la Tabla 2 muestra los métodos en OutputStream.

1.1. Métodos InputStream

Tabla 1:

Tabla 1: Métodos streams de bytes definidos por InputStream.
MétodoDescripción
int available ()Devuelve la cantidad de bytes de entrada disponibles para leer.
void close ()Cierra la fuente de entrada. Si se intenta leer incluso después de cerrar el flujo, se genera IOException.
void mark(int numBytes)Coloca una marca en el punto actual del flujo de entrada que seguirá siendo válida hasta que se lean los bytes ‘numBytes’
boolean markSupported()Devuelve true si mark()/reset() son compatibles con el flujo de invocación
int read()Devuelve una representación entera del siguiente byte de entrada disponible, se devuelve -1 cuando se intenta leer al final del flujo.
int read (byte buffer [])Intenta leer los bytes de buffer.length en el búfer y devuelve el número total de bytes leídos con éxito. Devuelve -1 cuando se intenta leer al final del flujo.
int read (byte buffer [], int offset, int numBytes)Intenta leer los bytes de ‘numBytes’ en el búfer comenzando en el búfer [offset] y devuelve el número total de bytes leídos con éxito. Devuelve -1 cuando se intenta leer al final del flujo.
byte[] readAllBytes()Lee y devuelve, en forma de una matriz de bytes, todos los bytes disponibles en el flujo. Un intento de leer al final del flujo da como resultado una matriz vacía. [Añadido en JDK9]
int readNBytes(byte byffer[], int offset, int numBytes)Intenta leer hasta numBytes bytes en el búfer comenzando en el búfer [offset], devolviendo la cantidad de bytes leídos con éxito. Un intento de leer al final del flujo da como resultado la lectura de cero bytes. [Añadido en JDK9]
void reset ()Restablece el puntero de entrada a la marca establecida previamente.
long skip (long numBytes)Ignora (es decir, omite) numBytes bytes de entrada, devolviendo la cantidad de bytes ignorados.
long transferTo(OutputStream outStrm)Copia los contenidos del flujo de invocación a outStrm, devolviendo la cantidad de bytes copiados. [Añadido en JDK9]

1.1. Métodos OutputStream

Tabla 2:

Tabla 2: Métodos streams de bytes definidos por OutputStream.
MétodoDescripción
void close ()Cierra el flujo de salida. Los intentos de escritura posteriores generarán una IOException.
void flush ()Hace que cualquier salida que se haya almacenado en el búfer se envíe a su destino. Es decir, vacía el búfer de salida.
void write (int i)Escribe un solo byte en un flujo de salida. Tenga en cuenta que el parámetro es un int, que le permite llamar a write() con expresiones sin tener que volver a convertirlas en byte.
void write (byte buffer [] )Escribe una matriz completa de bytes en un flujo de salida.
Void write(bytes buffer[],int offset, int numBytes)Escribe un subrango de bytes numBytes desde la matriz buffer comenzando en el buffer(offset).

En general, los métodos en InputStream y OutputStream pueden arrojar una IOException en caso de error. Los métodos definidos por estas dos clases abstractas están disponibles para todas sus subclases. Por lo tanto, forman un conjunto mínimo de funciones de E/S que tendrán todos los flujos de bytes.

2. Lectura de Entrada de Consola

Originalmente, la única forma de realizar una entrada por consola era usar streams de bytes, y gran parte del código de Java todavía utiliza streams de bytes exclusivamente. Ahora, puedes usar streams de bytes o caracteres. Para el código comercial, el método preferido para leer la entrada de la consola es usar un flujo orientado a caracteres. Hacerlo hace que tu programa sea más fácil de internacionalizar y más fácil de mantener. También es más conveniente operar directamente en caracteres en lugar de convertir hacia atrás y hacia adelante entre caracteres y bytes.

Sin embargo, para los programas de ejemplo, los programas de utilidad simples para tu propio uso y las aplicaciones que se ocupan de la entrada de teclado sin formato, utilizar los streams de bytes es aceptable. Por esta razón, la E/S de la consola que utiliza flujos de bytes se examina aquí.

Como System.in es una instancia de InputStream, automáticamente tiene acceso a los métodos definidos por InputStream. Esto significa que, por ejemplo, puede usar el método read() para leer bytes de System.in. Hay tres versiones de read(), que se muestran aquí:

int read( ) throws IOException
int read(byte data[ ]) throws IOException
int read(byte data[ ], int inicio, int max) throws IOException
  • La primera versión de read() es para leer un solo carácter del teclado (desde System.in). Devuelve -1 cuando se intenta leer al final del flujo.
  • La segunda versión lee los bytes del flujo de entrada y los coloca en data hasta que la matriz está llena, se llega al final del flujo o se produce un error. Devuelve el número de bytes leídos, o -1 cuando se intenta leer al final del flujo.
  • La tercera versión lee la entrada en data comenzando en la ubicación especificada por inicio. Se almacenan hasta max bytes. Devuelve el número de bytes leídos, o -1 cuando se intenta leer al final del flujo.
  • Todos arrojan una IOException cuando ocurre un error. Cuando se lee desde System.in, al pulsar la tecla INTRO se genera una condición de fin de flujo.

Aquí hay un programa que demuestra la lectura de una matriz de bytes de System.in. Tenga en cuenta que cualquier excepción de E/S que pueda generarse simplemente se descarta de main(). Este enfoque es común cuando lee desde la consola, pero puede manejar estos tipos de errores usted mismo, si lo desea.

Ejemplo:

//Leer una matriz de bytes desde el teclado
import java.io.*;
class LeerBytes {
    public static void main(String[] args) throws IOException {
            byte data[]= new byte[20];

            System.out.println("Ingrese algunos caracteres.");
            //Lee un array de bytes desde el teclado
            System.in.read(data);
            System.out.print("Usted ingresó: ");
            for (int i=0; i<data.length; i++)
                System.out.print((char)data[i]);
    }
}

Salida:

Ingrese algunos caracteres.
Java desde Cero
Usted ingresó: Java desde Cero

3. Escritura de Salida de Consola

Como en el caso de la entrada de la consola, Java originalmente solo proporcionaba streams de bytes para la salida de la consola. Java 1.1 agregó streams de caracteres. Para el código más portátil, se recomiendan streams de caracteres. Sin embargo, debido a que System.out es un stream de bytes, la salida de consola basada en bytes sigue siendo ampliamente utilizada. De hecho, todos los programas en este curso hasta este punto lo han usado! Por lo tanto, se examina aquí.

La salida de la consola se logra más fácilmente con print() y println(), con los cuales ya está familiarizado. Estos métodos están definidos por la clase PrintStream (que es el tipo de objeto al que hace referencia System.out). Aunque System.out es un stream de bytes, todavía es aceptable usar este stream para una salida de consola simple.

Como PrintStream es un stream de salida derivada de OutputStream, también implementa el método de bajo nivel write(). Por lo tanto, es posible escribir en la consola utilizando write(). La forma más simple de write() definida por PrintStream se muestra aquí:

void write(int valbyte)

Este método escribe el byte especificado por valbyte. Aunque valbyte se declara como un entero, solo se escriben los 8 bits de orden inferior. Aquí hay un pequeño ejemplo que usa write() para generar el carácter X seguido de una nueva línea:

//Demostración de System.out.write().

class DemoWrite {
    public static void main(String[] args) {
           int b;
           b='X';

        System.out.write(b);
        System.out.write('\n');
    }
}

Salida:

X

No utilizará a menudo write() para realizar la salida de la consola (aunque puede ser útil en algunas situaciones), ya que print() y println() son sustancialmente más fáciles de usar.

PrintStream proporciona dos métodos de salida adicionales: printf() y format(). Ambos le dan un control detallado sobre el formato preciso de los datos que genera. Por ejemplo, puede especificar el número de decimales que se muestran, un ancho de campo mínimo o el formato de un valor negativo. Aunque no usaremos estos métodos en los ejemplos de este libro, son características que deseará examinar a medida que avanza en su conocimiento de Java.

4. Lectura y escritura de archivos usando Streams de bytes

Java proporciona una cantidad de clases y métodos que le permiten leer y escribir archivos. Por supuesto, los tipos más comunes de archivos son archivos de disco. En Java, todos los archivos están orientados por bytes, y Java proporciona métodos para leer y escribir bytes desde y hacia un archivo.

Por lo tanto, leer y escribir archivos usando flujos de bytes es muy común. Sin embargo, Java le permite envolver un stream de archivos orientada a bytes dentro de un objeto basado en caracteres (se muestra más adelante).

Para crear una stream de bytes vinculada a un archivo, use FileInputStream o FileOutputStream. Para abrir un archivo, simplemente cree un objeto de una de estas clases, especificando el nombre del archivo como un argumento para el constructor. Una vez que el archivo está abierto, puede leer o escribir en él.

4.1. Lectura de Ficheros en Java

Un archivo se abre para la entrada creando un objeto FileInputStream. Aquí hay un constructor de uso común:

FileInputStream(String nombreArchivo) throws FileNotFoundException

Aquí, nombreArchivo especifica el nombre del archivo que desea abrir. Si el archivo no existe, se lanza FileNotFoundException. FileNotFoundException es una subclase de IOException.

Para leer desde un archivo, puede usar read(). La versión que utilizaremos se muestra aquí:

int read() throws IOException

Cada vez que se invoca, read() lee un solo byte del archivo y lo devuelve como un valor entero. Devuelve -1 cuando se encuentra el final del archivo. Lanza una IOException cuando ocurre un error. Por lo tanto, esta versión de read() es la misma que se usa para leer desde la consola.

Cuando haya terminado con un archivo, debe cerrarlo llamando a close(). Su forma general se muestra aquí:

void close() throws IOException

Al cerrar un archivo, se liberan los recursos del sistema asignados al archivo, lo que les permite ser utilizados por otro archivo. Si no se cierra un archivo, se pueden producir “pérdidas de memoria” debido a la asignación de recursos no utilizados.

4.2. Ejemplo para leer archivo de texto

El siguiente programa usa read() para ingresar y mostrar el contenido de un archivo de texto, cuyo nombre se especifica como un argumento de línea de comandos. Observe cómo los bloques try/catch manejan los errores de E/S que pueden ocurrir.

Ejemplo:

/* Mostrar un archivo de texto
     Para usar este programa, especifique el nombre del archivo que desea ver.
     Por ejemplo, para ver un archivo llamado Java.txt,
     use la siguiente línea de comando.

     java MostrarArchivo Java.txt
 */

import java.io.*;
class MostrarArchivo {
    public static void main(String[] args) {
          int i;
          FileInputStream fin;

         //Primero asegúrese de que haya especificado un archivo
         if (args.length!=1){
             System.out.println("Uso: MostrarArchivo.");
             return;
         }

         try {
             //Abrir el archivo
             fin=new FileInputStream(args[0]);
         }catch (FileNotFoundException exc){
             System.out.println("Archivo no encontrado");
             return;
         }

         try {
             //Leer bytes hasta que se encuentre el EOF
             //EOF es un concepto para determinar el final de un archivo
             do {
                 i=fin.read();
                 if (i !=-1) System.out.print((char)i);
             }while (i!=-1); //Cuando i es igual a -1, se ha alcanzado el final del archivo
         }catch (IOException exc){
             System.out.println("Error al leer el archivo");
         }

         try {
             fin.close();
             //Cerrar el archivo
         }catch (IOException exc){
             System.out.println("Error cerrando el archivo.");
         }
    }
}

Salida: Como ejemplo tengo un archivo de texto llamado Java.txt en la misma ubicación que MostrarArchivo.java

Hola desde Java desde Cero
Ejemplo Java para leer archivo de texto
Ejemplo Java para leer archivo de texto

Observe que el ejemplo anterior cierra el flujo del archivo después de que se haya completado el bloque try que lee el archivo. Aunque este enfoque a veces es útil, Java admite una variación que a menudo es una mejor opción. La variación es llamar a close() dentro de un bloque finally. En este enfoque, todos los métodos que acceden al archivo están contenidos dentro de un bloque try, y el bloque finally se usa para cerrar el archivo.

De esta forma, sin importar cómo finaliza el bloque try, el archivo se cierra. Asumiendo el ejemplo anterior, así es como se puede recodificar el bloque try que lee el archivo:

try {
    fin=new FileInputStream(args[0]);

    do {
        i=fin.read();
        if (i !=-1) System.out.print((char)i);
    }while (i!=-1);
}catch (IOException exc){
    System.out.println("Error al leer archivo");
}finally {
    //Cierra el archivo cualquier sea la salida en try
    try{
        fin.close();
    }catch (IOException exc){
        System.out.println("Error al cerrar archivo.");
    }
}

Una ventaja de este enfoque en general es que si el código que accede a un archivo termina debido a alguna excepción no relacionada con E/S, el archivo aún está cerrado por el bloque finally. Aunque no es un problema en este ejemplo (ni en la mayoría de los otros programas de ejemplo) porque el programa simplemente finaliza si ocurre una excepción inesperada, esto puede ser una fuente importante de problemas en programas más grandes. El uso de finally evita este problema.

A veces es más fácil ajustar las partes de un programa que abren el archivo y acceden al archivo dentro de un único bloque try (en lugar de separar los dos), y luego usar un bloque finally para cerrar el archivo. Por ejemplo, aquí hay otra forma de escribir el programa MostrarArchivo.java:

/* Esta variación ajusta el código que se abre
   y accede al archivo dentro de un solo bloque try.
   El archivo es cerrado por el bloque finally.
   java MostrarArchivo Java.txt
 */

import java.io.*;
class MostrarArchivo {
    public static void main(String[] args) {
          int i;
          //fin es inicalizado como nulo
          FileInputStream fin=null;


         //Primero asegúrese de que haya especificado un archivo
         if (args.length!=1){
             System.out.println("Uso: MostrarArchivo.");
             return;
         }

        //El siguiente código abre un archivo,
        // lee caracteres hasta que se encuentra el EOF,
        // y luego cierra el archivo a través de un bloque finally.
        // EOF es un concepto para determinar el final de un archivo
         try {
             fin=new FileInputStream(args[0]);

             do {
                 i=fin.read();
                 if (i !=-1) System.out.print((char)i);
             }while (i!=-1); //Cuando i es igual a -1, se ha alcanzado el final del archivo
         }catch (FileNotFoundException exc){
             System.out.println("Archivo no encontrado");
         }catch (IOException exc){
             System.out.println("Ha ocurrido un error de E/S");
         }finally {
             //Cerrar archivo en todos los casos
             try{
                 //Cierra fin sólo si no es nulo
                 if (fin!=null) fin.close();
             }catch (IOException exc){
                 System.out.println("Error al cerrar archivo.");
             }
         }
    }
}

En este enfoque, observe que fin se inicializa como nulo. Luego, en el bloque finally, el archivo se cierra solo si fin no es nulo. Esto funciona porque fin no será nula si el archivo se abrió con éxito. Por lo tanto, close() no se invocará si se produce una excepción al abrir el archivo.

Es posible hacer que la secuencia de try/catch en el ejemplo anterior sea un poco más compacta. Debido a que FileNotFoundException es una subclase de IOException, no necesita capturarse por separado. Por ejemplo, esta cláusula catch podría utilizarse para detectar ambas excepciones, eliminando la necesidad de capturar FileNotFoundException por separado. En este caso, se muestra el mensaje de excepción estándar, que describe el error.

...
}catch (IOException exc){
             System.out.println("Ha ocurrido un error de E/S"+exc);
}finally {
...

En este enfoque, cualquier error, incluido un error al abrir el archivo, será manejado simplemente por la instrucción catch única. Debido a su tamaño compacto, este enfoque es utilizado por la mayoría de los ejemplos de E/S en este curso.

Tenga en cuenta, sin embargo, que no será apropiado en los casos en los que desee tratar por separado un error al abrir un archivo, como podría ser causado si un usuario escribe mal un nombre de archivo. En tal situación, es posible que desee solicitar el nombre correcto, por ejemplo, antes de ingresar a un bloque try que acceda al archivo.

4.3. Escritura de Ficheros en Java

Para abrir un archivo para salida, cree un objeto FileOutputStream. Aquí hay dos constructores de uso común:

FileOutputStream(String nombreArchivo) throws FileNotFoundException
FileOutputStream(String nombreArchivo, boolean adjuntar) throws FileNotFoundException

Si el archivo no puede ser creado, entonces se lanza FileNotFoundException.

  • En la primera forma, cuando se abre un archivo de salida, se destruye cualquier archivo preexistente con el mismo nombre.
  • En la segunda forma, si adjuntar es true, la salida se agrega al final del archivo. De lo contrario, el archivo se sobrescribe.

Para escribir en un archivo, usará el método write(). Su forma más simple se muestra aquí:

void write(int valbyte) throws IOException

Este método escribe el byte especificado por valbyte para el archivo. Aunque valbyte se declara como un entero, solo los 8 bits de orden inferior se escriben en el archivo. Si se produce un error durante la escritura, se lanza una IOException.

Una vez que haya terminado con un archivo de salida, debe cerrarlo usando close(), que se muestra aquí:

void close() throws IOException

Al cerrar un archivo, se liberan los recursos del sistema asignados al archivo, lo que les permite ser utilizados por otro archivo. También ayuda a garantizar que cualquier salida que quede en un búfer de salida se escriba realmente en el dispositivo físico.

4.4. Ejemplo para copiar archivo de texto

El siguiente ejemplo copia un archivo de texto. Los nombres de los archivos de origen y destino se especifican en la línea de comando.

/* Copia un archivo de texto.
Para usar este programa, especifique el nombre del archivo fuente y el archivo de destino.
Por ejemplo, para copiar un archivo llamado PRIMERO.TXT a un archivo llamado SEGUNDO.TXT,
use la siguiente línea de comando.

java CopiarArchivo PRIMERO.TXT SEGUNDO.TXT
 */

import java.io.*;
class CopiarArchivo {
    public static void main(String[] args) throws Exception {
        int i;
        FileInputStream fin= null;
        FileOutputStream fout=null;


        //Primero, asegúrese de que ambos archivos hayan sido especificados.
        if(args.length!=2){
            System.out.println("Uso: CopiarArchivo de - a -");
            return;
        }

        //Copiar un Archivo
        try{
            //Intentar abrir los archivos
            fin=new FileInputStream(args[0]);
            fout=new FileOutputStream(args[1]);

            do {
                i=fin.read();
                if (i!=-1)fout.write(i);
            }while (i!=-1);
        }catch (IOException exc){
            System.out.println("Error de E/S: "+exc);
        }finally {
            try {
                if (fin!=null) fin.close();
            }catch (IOException exc){
                System.out.println("Error al cerrar el archivo de entrada.");
            }
            try {
                if(fout!=null) fout.close();
            }catch (IOException exc){
                System.out.println("Error al cerrar el archivo de salida.");
            }
        }
    }
}

Salida:

java CopiarArchivo PRIMERO.TXT SEGUNDO.TXT
Escritura de Ficheros en Java
Escritura de Ficheros en Java

5. Cerrar un archivo automáticamente

En la sección anterior, los programas de ejemplo han realizado llamadas explícitas a close() para cerrar un archivo una vez que ya no se necesita. Esta es la forma en que se han cerrado los archivos desde que se creó Java por primera vez. Como resultado, este enfoque está muy extendido en el código existente. Además, este enfoque sigue siendo válido y útil.

Sin embargo, a partir de JDK 7, Java ha incluido una función que ofrece otra forma más simplificada de administrar recursos, como flujos/streams de archivos, mediante la automatización del proceso de cierre. Se basa en otra versión de la declaración try llamada try-with-resources, y a veces se denomina administración automática de recursos. La ventaja principal de try con recursos es que previene situaciones en las que un archivo (u otro recurso) no se libera inadvertidamente después de que ya no se necesita. Como se explicó, olvidarse de cerrar un archivo puede ocasionar fugas de memoria y otros problemas.

La declaración try-with-resources (try con recursos) tiene esta forma general:

try (especificacion-recursos) {
// usa el recurso
}

A menudo, especificacion-recursos es una declaración que declara e inicializa un recurso, como un archivo. En este caso, consiste en una declaración de variable en la que la variable se inicializa con una referencia al objeto que se está gestionando. Cuando el bloque try finaliza, el recurso se libera automáticamente. En el caso de un archivo, esto significa que el archivo se cierra automáticamente.

Por lo tanto, no es necesario llamar a close() explícitamente. Una instrucción try-con-recursos también puede incluir cláusulas catch y finally.
A partir de JDK 9, también es posible que la especificación del recurso de try consista en una variable que ha sido declarada e inicializada anteriormente en el programa. Sin embargo, esa variable debe ser efectivamente final, lo que significa que no se le ha asignado un nuevo valor después de recibir su valor inicial.

La instrucción try-with-resources se puede usar solo con los recursos que implementan la interfaz AutoCloseable definida por java.lang. Esta interfaz define el método close(). AutoCloseable es heredado por la interfaz Closeable definida en java.io. Ambas interfaces son implementadas por las clases de flujo, incluyendo FileInputStream y FileOutputStream. Por lo tanto, try-con-recursos se puede usar cuando se trabaja con streams, incluidos los flujos de archivos.

5.1. Ejemplo de try con recursos

Como primer ejemplo de cierre automático de un archivo, aquí hay una versión modificada del programa MostrarArchivo que lo usa:

/* Esta versión del programa MostrarArchivo
   usa la declaración try-with-resources para cerrar
   automáticamente un archivo cuando ya no es necesario.
 */

import java.io.*;
class MostrarArchivo {
    public static void main(String[] args) {
          int i;

         //Primero asegúrese de que haya especificado un archivo
         if (args.length!=1){
             System.out.println("Uso: MostrarArchivo.");
             return;
         }

        // El siguiente código usa try-with-resources para abrir un archivo
        // y cerrarlo automáticamente cuando se deja el bloque try.
         try  (FileInputStream fin=new FileInputStream(args[0])){
             do{
                 i=fin.read();
                 if (i!=-1) System.out.println((char)i);
             }while ((i!=-1));
         }catch (IOException exc){
             System.out.println("Error de E/S: "+exc);
         }
    }
}

En el programa, preste especial atención a cómo se abre el archivo dentro de la declaración try-with-resources:

try(FileInputStream fin = new FileInputStream(args[0])) {

Observe cómo la porción de especificación de recursos del try declara un FileInputStream llamado fin, que luego se le asigna una referencia al archivo abierto por su constructor.

Por lo tanto, en esta versión del programa, la variable fin es local al bloque try, que se crea cuando se ingresa try. Cuando se sale de try, el archivo asociado con fin se cierra automáticamente mediante una llamada implícita a close(). No necesita llamar a close() explícitamente, lo que significa que no puede olvidar cerrar el archivo.

Esta es una ventaja clave de la administración automática de recursos. Es importante entender que un recurso declarado en la declaración try es implícitamente final. Esto significa que no puede asignar el recurso después de que se haya creado. Además, el alcance del recurso se limita a la declaración try-with-resources.

5.2. Administración de varios recursos

Puede administrar más de un recurso dentro de una única declaración try. Para hacerlo, simplemente separe cada especificación de recursos con un punto y coma. El siguiente programa muestra un ejemplo. Se vuelve a trabajar el programa CopiarArchivo que se muestra anteriormente para que use una única declaración try-with-resources para administrar tanto fin como fout.

/* Una versión de Copiar Archivo que usa try-with-resources.
   Demuestra dos recursos (en este caso archivos)
   administrados por una única declaración try.
 */

import java.io.*;
class CopiarArchivo {
    public static void main(String[] args) throws Exception {
        int i;

        //Primero, asegúrese de que ambos archivos hayan sido especificados.
        if(args.length!=2){
            System.out.println("Uso: CopiarArchivo de - a -");
            return;
        }

        //Copiar y gestionar dos archivos con try
        try ( FileInputStream fin= new FileInputStream(args[0]);
              FileOutputStream fout=new FileOutputStream(args[1]);){
            do {
                i=fin.read();
                if (i!=-1)fout.write(i);
            }while (i!=-1);
        }catch (IOException exc){
            System.out.println("Error de E/S: "+exc);
        }

    }
}

En este programa, observe cómo se abren los archivos de entrada y salida dentro del try:

try ( FileInputStream fin= new FileInputStream(args[0]);
      FileOutputStream fout=new FileOutputStream(args[1]);){

Después de que este bloque try finalice, tanto fin como fout se habrán cerrado. Si compara esta versión del programa con la versión anterior, verá que es mucho más corta. La capacidad de simplificar el código fuente es un beneficio secundario de try con recursos.

E/S en Java
  • Streams de Bytes

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.