domingo, 20 de julio de 2014

BufferedReader vs Scanner


En el lenguaje java hemos visto dos de las clases más usadas para lectura que son el BufferedReader y el Scanner. En resumen sabemos que el Scanner trabaja con tokens que son cadenas de caracteres que se separan mediante delimitadores que por defecto el Scanner tiene el espacio y que el BufferedReader trabaja manualmente carácter por carácter. Usted como programador seguramente las has usado alguna vez y se ha preguntado:

¿Cuál es más fácil de usar?


Para saber esto hemos elaborado el siguiente ejemplo:
Se requiere leer los siguientes valores en una sola línea, como se muestran abajo.

hola 12345 12.22

Se deberán guardar los valores en variables de tipo de dato String, int y float, a continuación se brindaran algunas soluciones para poder resolver el anterior ejemplo.

Solución sencilla con Scanner:

Código:
import java.util.Scanner;
/**
 *
 * @author Luis
 */
public class Solucion1Scanner {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String caracteres = sc.next(); //Lectura con Scanner
        int entero = sc.nextInt();
        float enteroFlotante = sc.nextFloat();
        System.out.println("Palabra " + caracteres);
        System.out.println("Numero " + entero);
        System.out.println("Flotante " + enteroFlotante);
    }
}
Podemos observar que los métodos que tiene Scanner nos pueden apoyar en cuestión de lecturas con cualquier tipo de dato primitivo (exepto: el char), pero con el BufferedReader tenemos que arreglárnoslas para poder tener cualquier tipo de dato, aquí se brindaran dos tipos de soluciones.

Solución sencilla con BufferedReader :

Código:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
 *
 * @author Luis
 */
public class Solucion1BufferedReader {
    public static void main(String[] args) throws IOException{
        InputStreamReader flujoEntrada = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(flujoEntrada);
        String[] arreglo = br.readLine().split(" ");
        //el metodo split Divide los strings conforme a los espacios
        String caracteres = arreglo[0];
        int entero = Integer.parseInt(arreglo[1]);
        float enteroFlotante = Float.parseFloat(arreglo[2]);
        System.out.println("Palabra " + caracteres);
        System.out.println("Numero " + entero);
        System.out.println("Flotante " + enteroFlotante);
    }
}

Solución compleja con BufferedReader:

Codigo:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Pattern;
/**
 *
 * @author Luis
 */
public class Solucion2BufferedReader {
    public static void main(String[] args) {
        InputStreamReader flujoEntrada = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(flujoEntrada);
        try {
            String linea = br.readLine(); 
            String[] arregloStrings = linea.split(" "); 
            int numero = 0;
            String palabra = "";
            float numeroFlotante = 0;
            for (int i = 0; i < arregloStrings.length; i++) { //Recorremos nuestro arreglo de strings
                String expresionRegular = "[a-z]+";
                boolean bandera = Pattern.matches(expresionRegular, arregloStrings[i]);
                //El metodo matches retornara true si el string corresponde al expresion
                if (bandera == true) { 
                    palabra = arregloStrings[i]; //
                }
                expresionRegular = "[0-9]+";
                bandera = Pattern.matches(expresionRegular, arregloStrings[i]);
                if (bandera == true) {
                    numero = Integer.parseInt(arregloStrings[i]);
                }
                expresionRegular = "^[0-9]*[.][0-9]+$";
                bandera = Pattern.matches(expresionRegular, arregloStrings[i]);
                if (bandera == true) {
                    numeroFlotante = Float.parseFloat(arregloStrings[i]);
                }
            }
            System.out.println("Palabra " + numero);
            System.out.println("Numero " + palabra);
            System.out.println("Flotante " + numeroFlotante);
        } catch (IOException e) {
            System.out.println("Error");
        }
    }
}
Resultado del ejemplo anterior


Estas soluciones que fueron aplicadas al BufferedReader también podrán ser aplicadas con el Scanner, usted podrá elaborarlas sin problemas.
Quedo claro que en cuestión de comodidad el Scanner es amplio vencedor sobre el BufferedReader, ya que este nos hace trabajar con caracteres.

¿Cuál hace menor tiempo?


Ahora veremos quién es el mejor en tiempo con las siguientes pruebas que hemos realizado:
Primero elaboraremos un archivo de texto que será nuestra lectura por Scanner y BufferedReader, con el mismo numero de líneas y contenido de texto.

Codigo
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
/**
 *
 * @author Luis
 */
public class CrearArchivoPrueba {
    public static void main(String[] args) {
        InputStreamReader flujoEntrada = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(flujoEntrada);
        System.out.println("**Crear archivo de texto**\n" + "Ruta del archivo:");
        try {
            String ruta = br.readLine(); 
            System.out.println("Nombre del archivo:");
            String nombre = br.readLine();
            System.out.println("Texto:");
            String texto = br.readLine();
            System.out.println("Numero de lineas:");
            int numero = Integer.parseInt(br.readLine());
            System.out.println("Se ha finalizado");
            crearArchivo(ruta, nombre, texto, numero);
        } catch (IOException e) {
            System.out.println("Error");
        }
    }
    
    public static void crearArchivo(String ruta, String nombreDelArchivo,
            String texto, int numeroLineas) throws IOException {
        
        ArrayList lineas = new ArrayList<>();
        for (int i = 0; i < numeroLineas; i++) { //Numero de lineas que seran agregadas al arraylist
            lineas.add(texto);
        }
        File archivo = new File(ruta + "\\" + nombreDelArchivo); 
        /*La clase File nos permite crear un archivo de tipo txt,
        en el constructor le agregamos el directorio donde queremos crear el archivo*/
        FileWriter archivoEscritura = new FileWriter(archivo, true);  
        //La clase FileWriter hace que podamos escribir caracteres dentro del archivo
        BufferedWriter bw = new BufferedWriter(archivoEscritura);
        //BufferedWriter permite crear un flujo de caracteres de salida
        for (int i = 0; i < lineas.size(); i++) {
            String linea = lineas.get(i);
            bw.write(linea); 
            bw.newLine(); //Salto de linea
        }
        bw.close(); // Cierra el flujo
    }
}
//

Ingresamos los datos como en el siguiente ejemplo:
Archivo de texto PruebaLineas.txt


Este archivo de texto será para realizar pruebas para nuestro métodos de lectura para toda la línea que tiene BufferedReader con readLine() y Scanner con nextLine().


Archivo de texto PruebaNext.txt


También este archivo se usara para pruebas con los métodos de Scanner next() y BufferedReader nextManual() “este método no lo tiene el BufferedReader por defecto”.
Bueno, para ver los resultados de las pruebas de BufferedReader y Scanner usaremos el siguiente código:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
/**
 *
 * @author Luis
 */
public class Pruebas {
    public static void main(String[] args) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("**Pruebas de BufferredRead y Scanner**\n" + "Metodos de BufferedReader:\n"
                + "a)readLine " + "b)nextManual\n" + "Metodos de Scanner:\n" + "c)nextLine " + "d)next");
        try {
            char letra = (char) br.read();
            long tInicial = System.currentTimeMillis(); // Para Medir el tiempo inicial
            switch (letra) {
                case 'a':
                    pruebaReadLine("PruebaLineas.txt"); //En el constructor ponemos el nombre de nuestro txt realizado                
                    break;
                case 'b':
                    pruebaNextManual("PruebaNext.txt");
                    break;
                case 'c':
                    nextLine("PruebaLineas.txt");
                    break;
                case 'd':
                    next("PruebaNext.txt");
                    break;
                default:
                    System.out.println("Caracter incorrecto");
                    break;
            }
            long tFinal = System.currentTimeMillis() - tInicial;
            double promedio = (double) tFinal / 1000000;
            /*Realizamos la operacion de forma directa para evitar que se tome 
             mas tiempo de esto con un contador*/
            System.out.printf("%.7f", promedio);
        } catch (IOException e) {
            System.out.println("Error " + e);
        }
    }

    public static void pruebaReadLine(String nombreArchivo) throws IOException {
        BufferedReader brFileReader = new BufferedReader(new FileReader(nombreArchivo));
        //La clase FileReader hace que podamos leer caracteres dentro del archivo        
        while (brFileReader.ready()) { //Mientras que el buffer no este vacio 
            String linea = brFileReader.readLine();
        }
        brFileReader.close();  // Se cierra el flujo
    }

    public static void pruebaNextManual(String nombreArchivo) throws IOException {
        BufferedReader brFileReader = new BufferedReader(new FileReader(nombreArchivo));
        while (brFileReader.ready()) {
            char caracter = '/'; //Se inicializa el char
            String palabra = ""; //Se incializa el string 
            while (true) {
                caracter = (char) brFileReader.read(); //Lee un caracter
                palabra += caracter;
                if (caracter == '\n' || caracter == ' ') { 
                  // Si es salto de linea o esta vacio se rompe el ciclo
                    break;
                }
            }
        }
        brFileReader.close();
    }

    public static void next(String nombreDelArchivo) throws IOException {
        Scanner scFile = new Scanner(new FileReader(nombreDelArchivo));
        while (scFile.hasNext()) { //Mientras tenga caracteres
            String palabra = scFile.next();
        }
    }

    public static void nextLine(String nombreDelArchivo) throws IOException {
        Scanner scFile = new Scanner(new FileReader(nombreDelArchivo));
        while (scFile.hasNextLine()) { //Mientras tenga una linea
            String linea = scFile.nextLine();
        }
    }
}

Metodos
Tiempo en milisegundos
readLine()
0.0005620
nextManual()
0.0006850
nextLine()
0.0025680
next()
0.0015290
                      Tabla de resultados

Como se pudo observar el ganador en esta prueba fue el uso de la clase BufferedReader respecto a los métodos utilizados, podemos decir que si queremos elaborar un código optimo con mejor tiempo recomendamos usar esta clase.

¿Cuál es mejor?


Elegir una ganadora es muy complicado porque cada una tiene sus propias ventajas unas sobre otras, el programador deberá saber que es lo más conveniente para utilizar en su código por ejemplo si se quiere trabajar con una lectura de línea de forma manual para realizar trabajos sencillos se podrá utilizar el BufferedReader o bien queremos usar una manera más automatizada se usara el Scanner, usted tendrá la decisión de escoger la que crea más conveniente para su caso.

jueves, 10 de julio de 2014

Uso básico del BufferedReader

Las instancias (objetos) de la clase BufferedReader te permiten hacer lecturas sencillas desde flujos de caracteres, dentro de este post verás ejemplos sencillos de algunas formas de instanciar la clase y el uso de algunos de los métodos (los más relevantes según mi criterio) que trae consigo BufferedReader, tu tarea será darle un vistazo a aquellos que no se mencionen en este post, el contenido de la entrada será el siguiente:



1.- Definición

Como se mencionó antes BufferedReader es una clase cuyas instancias nos permiten hacer lecturas sencillas de texto desde un flujo de caracteres, debido a que esta clase trabaja con su propio buffer provee una lectura eficiente de caracteres, arreglos y líneas de texto. Otra ventaja de BufferedReader es que esta clase está sincronizada lo cual es sinónimo de seguridad al utilizarla en programación concurrente.

Volver al inicio ↑

2.- Ubicación

Primero que todo, esta clase se ubica en el paquete java.io por lo que en los códigos donde se utilice BufferedReader es necesario poner los siguientes import:

	import java.io.BufferedReader;
	import java.io.IOException;
			

Paréntesis: El último import es necesario puesto que al manejar BufferedReader manejamos FLUJOS por lo que se pueden presentar situaciones extraordinarias o también llamadas Excepciones un ejemplo de esto es que el flujo que estamos utilizando se cierre o sea modificado por un agente externo a nuestro programa.

Con el objetivo hacer más claro el funcionamiento de algunos métodos de BufferedReader agregaremos los siguientes imports al código:

	import java.io.FileReader;
	import java.io.InputStreamReader;
			

Volver al inicio ↑

3.- Creación de una instancia

Para instanciar la clase BufferedReader solo es necesario tener el flujo de caracteres del cual nuestra instancia, tomará hará las lecturas. La clase contiene dos constructores

BufferedReader(Reader in) BufferedReader(Reader in, int sz)

El primero únicamente requiere el flujo de caracteres que utilizará, en el segundo se puede especificar el tamaño del buffer que manejará. De presentarse el caso de que durante la ejecución del programa suceda que el tamaño del buffer que se haya especificado se quede corto para la cantidad de caracteres que tiene el flujo, no es problema, puesto que la misma instancia de BufferedReader se encarga de redimensionar el buffer al tamaño que sea necesario.

Aquí algunos ejemplos de cómo instanciar BufferedReader.

Instancia de BufferedReader que utiliza el flujo estándar asociado al teclado, este flujo es el que comúnmente se utiliza cuando se trabaja con consola.

	InputStreamReader flujo = new InputStreamReader(System.in);
	BufferedReader br = new BufferedReader(flujo);
			

Instancia de BufferedReader que utiliza el flujo estándar asociado al teclado, y cuyo tamaño de buffer se especificó de 1000 caracteres.

	InputStreamReader flujo = new InputStreamReader(System.in);
	BufferedReader br = new BufferedReader(flujo, 1000);
			

Instancia de BufferedReader que utiliza como flujo de caracteres un archivo de texto plano.

	FileReader flujo = new FileReader(“src/archivo.txt”);
	BufferedReader br = new BufferedReader(flujo);
			

Nota: Cuando se crea la instancia de FileReader es necesario que es su constructor se especifique la ruta relativa de la carpeta en que se encuentra el archivo, se puede asumir que en este ejemplo el archivo se encuentra en la misma carpeta donde se encuentra el código fuente del ejemplo.

Volver al inicio ↑

4.- Métodos

En la siguiente tabla se encuentran algunos de los métodos que se usarán en los ejemplos más adelante:

Método Tipo de Retorno Descripción
mark(int limiteCaracteres) void Marca la posición actual en la que se encuentre el apuntador en el flujo, el parámetro del método indica el número de caracteres que pueden ser leídos mientras se mantenga esta marca.
read() void Lee un solo carácter del flujo, lo curioso de este método es que retorna un número entero, ¿Por qué? Más adelante la respuesta.
readLine() String Lee una línea completa de texto.
ready() boolean Este método es utilizado para saber si aún hay caracteres en el flujo para ser leídos, detalles más adelante.
reset() void Reinicia el flujo hasta la marca más reciente que se haya hecho.
skip(long n) long Mueve el apuntador del flujo las posiciones necesarios para evitar la cantidad de caracteres de n.

Volver al inicio ↑

5.- Ejemplos

Aquí te presentamos ejemplos donde se utiliza de forma sencilla algunos métodos de BufferedReader, al final del post se anexa un enlace a algún servidor de archivos donde podrán descargar el código del ejemplo.

Nota: Dentro del código de los métodos presentados en todos los ejemplos se agrega lo siguiente throws IOException, pues con esto nos deshacemos de las excepciones de I/O que se puedan presentar durante la ejecución del programa, OJO dejar excepciones sin manejar nunca es una buena práctica de programación pero para los ejemplos nos es suficiente dejarlo así.

5.1.- Ejemplo 1: Lectura de un carácter método read().

Para este ejemplo utilizamos el método read() para la lectura de un solo carácter, como se mencionó en la sección anterior este método tiene algo curioso, su tipo de retorno es un int, - pero se supone que lee caracteres ¿WDF?-, bien esto se debe a que el valor entero que retorna es el valor de código ASCII que representa a ese carácter.

public static void lecturaChar() throws IOException {
	System.out.println(br.read());
}
					
				

Volver al inicio ↑

5.2.- Ejemplo 2: Lectura de un carácter método read() con casting.

Debido a que seguramente el código ASCII de un carácter no te es de mucha utilidad salvo en casos particulares (como que te encuentres en la Comunidad Estudiantil de Programación Competitiva), es necesario que le hagamos un casting de entero a carácter al valor que nos retorna el método read().

Paréntesis: Un cast o casting es forzar un tipo de dato para que sea tratado como otro tipo de dato COMPATIBLE, también se puede hacer casting a objetos de una clase para ser tratado como de otra clase. Este último procedimiento es comúnmente utilizado en Herencia y Polimorfismo.

public static void lecturaCharCasting() throws IOException {
	char caracter = (char) br.read(); //casting
	System.out.println(caracter);
}
					
				

Volver al inicio ↑

5.3.- Ejemplo 3: Lectura de caracteres hasta EOF.

En este ejemplo leemos caracteres hasta llegar al fin de archivo (EOF o End Of File) del flujo, para este ejemplo como flujo de entrada utilizamos un archivo de texto puesto que es más claro ejemplificar el EOF con archivos.

Paréntesis: El fin de archivo es un indicador cuya función es decir que se ha llegado al final del flujo y ya no hay más caracteres en él para ser leídos, el EOF de los archivos de texto plano es un -1 puesto que en el código ASCII no hay carácter alguno que posea un código negativo.

public static void lecturaChars() throws IOException{
	br = new BufferedReader(new FileReader("src/bufferedreader/texto_para_pruebas")) ;
	int codigo = br.read();
	char caracter;
        
	//mientras el código no sea -1 (EOF) continuo leyendo
	while (codigo != -1) { 
		caracter = (char) codigo; //casting
		System.out.print(caracter);
		codigo = br.read();
	}
}
					
				

Volver al inicio ↑

5.4.- Ejemplo 4: Lectura de una línea de texto.

El método readLine() como su nombre lo indica, nos permite hacer lectura de líneas completas de texto de nuestro flujo. A grandes rasgos el funcionamiento de este se basa en ir leyendo caracteres hasta encontrarse con el carácter ‘\n’ de salto de línea o ‘\r’ de retorno de carro, de esta forma BufferedReader sabe que ha terminado una línea y retorna los caracteres que leyó anteriormente.

En el código de ejemplo se agrega este String "(¬°o°)¬" al final del String leído por readLine() para hacer notar que se hizo la lectura.

public static void lecturaLinea() throws IOException{
	System.out.println(br.readLine()+"(¬°o°)¬");
}
					
				

Volver al inicio ↑

5.5.- Ejemplo 5: Lectura de líneas hasta EOF.

Similar al ejemplo 3 pero esta ocasión utilizando readLine() para la lectura de líneas completas. el concepto es similar pero la implementación es diferente.

public static void lecturaLineas() throws IOException{
	/*
	Si línea contiene null significa que se ha llegado al EOF, 
	es entonces cuando el ciclo termina.
	*/
	for(String linea = br.readLine(); linea != null; linea = br.readLine()){
		System.out.println(linea+"(¬°o°)¬");
	}
}
					
				

Volver al inicio ↑

5.6.- Ejemplo 6: Lectura de líneas hasta EOF método ready().

En el ejemplo anterior nosotros nos encargamos de programar la detección del EOF pero BufferedReader ya tiene un método definido para esto, ready(). El método ready() devuelve un valor booleano, true si aún hay caracteres en el flujo para leer y en caso contrario false.

public static void lecturaLineasPro() throws IOException{
	br = new BufferedReader(new FileReader("src/bufferedreader/texto_para_pruebas")) ;
	String linea;
	while (br.ready()) {//ready() retornará false cuando se llege al EOF
		linea = br.readLine();
		System.out.println(linea+"(¬°o°)¬");
	}
}

				

Volver al inicio ↑

5.7.- Ejemplo 7: Uso del método mark().

Tal vez mientras pensabas sobre la clase BufferedReader vino a tu mente la siguiente pregunta. ¿Qué pasa si quiero leer el contenido del flujo más de una vez?, cuando se presenta ese caso los métodos mark(long) y reset() son la solución. El método mark(long) establece una marca en la posición actual del apuntador del flujo de esta forma cuando se llame al método reset() regresará el apuntador a la última marca que haya sido hecha.

public static void lecturaConMarcado() throws IOException {
	br = new BufferedReader(new FileReader("src/bufferedreader/BufferedReaderEjemplo.java"));
	//se hace la marca al inicio de la lectura
	br.mark(100000);
	//lecturaLineas lee hasta EOF y termina su ejecución
	lecturaLineas();
	System.out.println("");
	//regresamos el apuntador del flujo al inicio que es donde hicimos la marca
	br.reset();
	//volvemos a leer todo el texto aún cuando ya se había llegado al EOF
	lecturaLineas();
}

				

Volver al inicio ↑

Código completo

A continuación te presentamos todo el código de ejemplo utilizado en el post, tambíen puedes descargar el proyecto en este enlace.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BufferedReaderEjemplo {

    public static BufferedReader br;

    public static void main(String[] args) throws IOException {
        InputStreamReader flujo = new InputStreamReader(System.in);
        br = new BufferedReader(flujo);

        //ejemplo 1 lectura de caracteres método read() (el método read lee los caracteres pero los interpreta en su código ASCII)
        lecturaChar();
        //ejemplo 2 lectura de caracteres método read() con casting a carácter
        lecturaCharCasting();
        //ejemplo 3 lectura de caracteres hasta EOF con método read()
        lecturaChars();
        //ejemplo 4 lectura de linea de texto
        lecturaLinea();
        //ejemplo 5 lectura de lineas hasta EOF
        lecturaLineas();

        //ejemplo 6 lectura de lineas hasta EOF con el método ready()
        lecturaLineasPro();
        //ejemplo 7 uso del método mark()
        lecturaConMarcado();
    }

    public static void lecturaChar() throws IOException {
        System.out.println(br.read());
    }

    public static void lecturaCharCasting() throws IOException {
        char caracter = (char) br.read(); //casting
        System.out.println(caracter);
    }

    public static void lecturaChars() throws IOException {
        br = new BufferedReader(new FileReader("src/bufferedreader/texto_para_pruebas"));
        int codigo = br.read();
        char caracter;

        //mientras el código no sea -1 (EOF) continuo leyendo carácteres
        while (codigo != -1) {
            caracter = (char) codigo; //casting
            System.out.print(caracter);
            codigo = br.read();
        }
    }

    public static void lecturaLinea() throws IOException {
        System.out.println(br.readLine() + "(¬°o°)¬");
    }

    public static void lecturaLineas() throws IOException {
        /*
         Si línea contiene null significa que se ha llegado al EOF, 
         es entonces cuando el ciclo termina.
         */
        for (String linea = br.readLine(); linea != null; linea = br.readLine()) {
            System.out.println(linea + "(¬°o°)¬");
        }
    }

    public static void lecturaLineasPro() throws IOException {
        br = new BufferedReader(new FileReader("src/bufferedreader/texto_para_pruebas"));
        String linea;
        while (br.ready()) {//ready() retornará false cuando se llege al EOF
            linea = br.readLine();
            System.out.println(linea + "(¬°o°)¬");
        }
    }

    public static void lecturaConMarcado() throws IOException {
        br = new BufferedReader(new FileReader("src/bufferedreader/BufferedReaderEjemplo.java"));
        //se hace la marca al inicio de la lectura
        br.mark(100000);
        //lecturaLineas lee hasta EOF y termina su ejecución
        lecturaLineas();
        System.out.println("");
        //regresamos el apuntador del flujo al inicio que es donde hicimos la marca
        br.reset();
        //volvemos a leer todo el texto aún cuando ya se había llegado al EOF
        lecturaLineas();
    }
}

				

Esperamos que el post te haya sido de ayuda, cualquier duda o comentario no dudes en hacérnoslo saber.

Volver al inicio ↑

6.- Referencias

Para más información sobre BufferedReader visita los siguientes enlaces:

Volver al inicio ↑