Parsing, Tokenizing y Formatting en Java

Buenos días, en esta entrada vamos a ver las funciones de Parsing, Tokenizing y Formatting en Java.


Vamos a ir con otro apartado importante. Esta pequeña sección no nos va a convertir de un novato de las expresiones regulares a un gurú. En esta sección cubriremos 3 ideas básicas:

  • Buscar cosas:: Tendremos grandes pilas de texto para buscar en ellos. Puede darse el caso de que estemos buscando desde un archivo, y para buscar en el necesitaremos herramientas fáciles de buscar agujas textuales en pajares textuales. Usaremos las clases java.util.regex.Pattern, java.util.regex.Matcher y java.util.Scanner para ayudarnos con esta tares.
  • Tokenización de cosas: Tenemos una rchivo delimitado del que queremos extraer ciertos datos. Queremos transformar una pieza del archivo de texto que se parece a : “1500.00,343.77,123.4” y queremos pasarlo a algunas variables float. Veremos el uso básico del método String.split() y la clase java.util.Scanner, para tokenizar nuestra información.
  • Formatear cosas: Tenemos que crear un reporte y necesitamos coger una variable float con el valor de 32500.000f y transformarla en un String con el valor de “$32,500.00”. Nos introduciremos en la clase java.util.Formatter y los métodos printf() y format().
  • Un Tutorial de Búsqueda

    Si estamos buscando cosas o tenemos que tokenizarlas, la mayoría de los conceptos son los mismos, por lo que vamos a empezar con lo básico. No importa que lenguaje estemos usando, tarde o temprano tendremos que enfrentarnos con la necesidad de buscar a través de grandes cantidades de información en texto, y tendremos que buscar cosas muy específicas.

    Las expresiones regulares (regex a partir de ahora para abreviar) son un tipo de lenguaje dentro de un lenguaje, diseñado para ayudar a los programadores con estas tareas de búsqueda. Todos los lenguajes que proveen capacidades para usar regex usa uno o mas motores de búsqueda. Los motores de regex buscan a través de información de texto usando instrucciones que están codificadas en una expresión. Una expresión regex es como un programa muy corto o un script. Cuando invocamos al motor regex, le pasaremos un pedazo de la información que queremos que procese (en Java normalmente es un String o un stream), y pasamos la expresión que queremos que use para buscar a través de la información.

    Es justo pensar que un regex es un lenguaje, y nos referiremos a el a través de esta sección. EL lenguaje regex es usado para crear expresiones, y como veremos a través de esta sección, donde quiera que hablemos sobre expresiones o sintaxis de expresiones, estaremos hablando sobre la sintaxis para el “lenguaje” regex. Tenemos que tener en mente que la mayoría de partes de las expresiones que vamos a usar son solo una pequeña porción de todo el set de instrucciones que existen.

    • Búsquedas Simples

      Para nuestro primer ejemplo, queremos buscar a través de un String que tenemos:

      				abaaaba
      				

      Intentaremos buscar todas las ocurrencias de la expresión:

      ab

      En todas nuestras discusiones vamos a asumir que nuestras fuentes de información están basadas en índices basados en 0, por lo que aplicamos los índices a nuestro String:

      				source:	abaaaba
      				index:	0123456
      				

      Podemos ver que tenemos 2 ocurrencias de la expresión ab: una empieza en la posición 0 y la segunda empieza en la posición 4. Si enviamos la fuente de información que tenemos y la expresión a un motor de regex, nos respondería que ha encontrado coincidencias en las posiciones 0 y 4:

      				class RegexSmall {
      					public static void main(String[] args) {
      						Pattern p = Pattern.compile("ab");	// La expresión
      						Matcher m = p.matcher("abaaaba");	// La fuente
      						while (m.find()){
      							System.out.println(m.start() + " ");
      						}
      						
      					}
      				
      				}
      				

      Esto producirá:

      				0 4 
      				

      No vamos a explicar este código ahora mismo. Mas adelante veremos más código sobre los regex, pero primero vamos a ver un poco mas de sintaxis de regex. Una vez que entendamos un poco mas los regex, los ejemplos de código tendrán mas sentido. Aquí tenemos un ejemplo mas complicado con una fuente de información y una expresión:

      				source:		abababa
      				index:		0123456
      				expression:	aba
      				

      ¿Cuántas ocurrencias tendremos en este caso? Bien, hay una ocurrencia que claramente podemos ver en la posición 0, y otra que empieza en la posición 4. ¿Qué pasa con la posición 2? En general en el mundo de las regex, la cadena “aba” que empieza en la posición 2 no será considerada como una ocurrencia válida. La primera regla general sobre la búsqueda en regex es:

      En general, una búsqueda regex se ejecuta de izquierda a derecha, y una vez que el carácter de una fuente ha sido usado en una coincidencia, no puede ser reusado.

      Por lo que en nuestro anterior ejemplo, las primeras coincidencias usan las posiciones 0, 1 y 2 para la primera expresión. (Otro término común para esto es que los tres primeros caracteres de la fuente han sido consumidos). Como el caracter de al posición 2 fué consumido en la primera ocurrencia, no puede volver a ser usado. Por lo que el motor ha seguido moviendose y no encontrará otra ocurrencia de “aba” hasta la posición 4. Esta es la manera típica del funcionamiento de un motor regex. sin embargo, en algunas páginas, veremos una excepción a la primera regla que tenemos arriba.

      Por ahora hemos coincidido en algunas cadenas exactamente, pero ¿qué pasaría si queremos encontrar algo mas dinámico? Por ejemplo, ¿Qué podemos hacer si queremos encontrar todas las ocurrencias de números hexadecimales o números de teléfono o códigos postales?.

    • Búsqueda Usando Metacaracteres

      Por suerte, las regex tienen un mecanismo muy potente para tratar con los casos que hemos descrito arriba. En el corazón de este mecanismo tenemos la idea del metacaracter. Como un ejemplo fácil, vamos a decir que queremos buscar a través de una fuente de información todas las ocurrencias de dígitos numéricos. En regex, la siguiente expresión es usada para buscar dígitos de números:

      				d
      				

      Vamos a cambiar el programa previo para aplicar la expresión d a la siguiente fuente:

      				source:	a12c3e456f
      				index:	0123456789
      				

      El regex nos dirá que ha encontrado dígitos en las posiciones 1, 2, 4, 6, 7, y 8. (Para probar este método tenemos que “escapar” del método “d” como argumento añadiéndole una barra mas “\d”).

      Regex nos provee de un rico set de metacaracteres que podemos encontrar buscando en la documentación de la API para java.util.regex.Pattern. No discutiremos todos aquí, pero describiremos algunos de los más usados:

      				d	Un dígito
      				s	Un caracter espacio
      				s	Un caracter de palabra (letras, dígitos, o "_")
      				

      Por ejemplo, dado lo siguiente:

      				source:	"a 1 56 _Z"
      				index:	 012345678
      				pattern: w
      				

      Regex nos retornará las posiciones 0, 2, 4, 5, 7 y 8. Los únicos caracteres en esta fuente que no coinciden con la definición de caracter de palabra son los espacios en blanco. (Nota: En este ejemplo hemos cerrado al fuente entre comillas para indicar claramente que no había espacios en blanco ni al principio ni al final).

      Podemos también especificar sets de caracteres para buscar usando corchetes y rangos de caracteres para buscar usando corchetes y guión:

      				[abc]	Busca solo caracteres a, b o c
      				[a-f]	Busca solo caracteres desde la a hasta la f
      				

      En adición, podemos buscar entre diferentes rangos a la vez. La siguiente expresión está buscando ocurrencias para las letras a – f o A – F, pero NO está buscando combinaciones de fa:

      				[a-fA-F]	Busca las primeras 6 letras del alfabeto, en ambos casos
      				

      Por ejemplo:

      				source:	"cafeBABE"
      				index:	 01234567
      				pattern: [a-cA-C]
      				

      Esto nos retornará las posiciones: 0, 1, 4, 5 y 6.

    • Búsquedas Usando Cuantificadores

      Vamos a decir que queremos crear un patrón regex para buscar literales hexadecimales. Como primer paso, vamos a solventar el problema para números hexadecimales con un digito:

      				0[xX][0-9a-fA-F]
      				

      La expresión anterior podría ser reproducida de la siguiente manera: “Buscar un set de caracteres los cuales el primer carácter sea un “0” , el segundo carácter puede ser una “x” o “X”, y el tercer carácter puede ser un dígito desde el “0” al “9”, una letra desde la “a” a la “f” o una letra mayúscula desde la “A” hasta la “F””. Usando la expresión anterior, y la siguiente información:

      				source:	"12 0x 0x12 0Xf 0xg"
      				index:	 012345678901234567
      				

      La anterior regex nos retornará 6 y 11. (Note: 0x y 0xg no son números válidos hexadecimales).

      Como segundo paso, vamos a pensar sobre un problema fácil. ¿Qué pasaría si hubiéramos querido que el regex buscara ocurrencias de enteros? Los enteros pueden ser de uno o mas dígitos de largo, por lo que sería bueno si pudiéramos decir”uno o mas” en una expresión. Hay un set de constructores regex llamados cuantificadores que nos permiten especificar conceptos como “uno o mas”. De hecho, los cuantificadores que representan “uno o mas” es el carácter “+”. Veremos los otros dentro de poco.

      El otro problema que esto levanta es que cuando estamos buscando algo cuya longitud es variable, obtener solo la posición de comienzo como valor de retorno tiene un valor limitado. Por lo que, en adición a retornar la posición de comienzo, otro poco de información que el motor de regex puede retornar es la coincidencia completa o el grupo que ha encontrado. Vamos a cambiar la manera de la que hablamos sobre lo que el regex retorna especificando en cada retorno en su propia línea, recordando que ahora por cada retorno vamos a obtener la posición de comienzo Y el grupo:

      				source:	"1 a12 234b"
      				pattern: d+
      				

      Ahora podemos leer la expresión como si dijéramos: “Busca uno o mas dígitos en una fila”. Esta expresión produciría la siguiente salida de regex:

      				0 1
      				3 12
      				6 234
      				

      Podemos leerlo de la siguiente manera: “En la posición 0 hay un integer con el valor de 1, luego en la posición 3 hay un integer con el valor de 12, luego en la posición 6 hay un integer con el valor 234”. Retornando de nuevo a nuestro ejemplo de los hexadecimales, lo último que necesitamos saber es como especificar el uso de un cuantificador para solo las partes de una expresión. En este caso debemos tener exactamente una ocurrencia de 0x o 0X pero podemos tener una o varias ocurrencias de los dígitos hexadecimales que lo siguen. La siguiente expresión añade paréntesis al límite del cuantificador “+” para solo los dígitos hexadecimales:

      				0[xX]([0-9a-fA-F])+
      				

      Los paréntesis y el “+” aumenta la expresión de buscar hexadecimales. Podríamos decirlo de la siguiente manera: “Una vez que encontremos nuestro 0x o 0X, podemos buscar de uno a varias ocurrencias de dígitos hexadecimales”. Vemos que hemos puesto el cuantificador “+” al final de la expresión. Es útil pensar en los cuantificadores siempre que cuantifiquemos la parte de la expresión que lo precede.

      Los otros 2 cuantificadores que vamos a ver son:

      				* 	Cero o mas ocurrencias
      				?	Cero o una ocurrencia
      				

      Vamos a decir que tenemos un archivo de texto que contiene una lista delimitada por comas de todos los nombres de ficheros en un directorio que contiene algunos proyectos muy importantes. Queremos crear una lista de todos los archivos cuyos nombres empiecen con proj1. Podemos descubrir archivos .txt, archivos .java, archivos .pdf, ¿Quién sabe?. ¿Qué tipo de expresión regex podríamos usar para buscar todos los archivos que comiencen por proj1?. Primero vamos a ver una parte del texto que podría ser como el siguiente:

      				..."proj3.txt, proj1sched.pdf, proj1, proj2, proj1.java"...
      				

      Para solventar este problema vamos a usar el carácter ^, el cual mencionamos antes. Nos ayudará a crear una solución mas limpia a nuestro problema. El ^ es el símbolo de negación en regex. Por ejemplo, si queremos buscar cualquier cosa que no sea ni a, ni b ni c en un fichero podríamos decir:

      				[^abc]
      				

      Por lo que armados con el operador ^ y el * (cero o mas) vamos a crear lo siguiente:

      				proj1([^,])*
      				

      Si aplicaramos esta expresión a la porción de fichero de texto que pusimos arriba, el regex retornaría:

      				10 proj1sched.pdf
      				25 proj1
      				37 proj1.java
      				

      La parte clave de esta expresión es el “dame cero o mas caracteres que no sean una coma”.

      El último ejemplo de cuantificador que vamos a ver es el cuantificador ?. Vamos a decir que nuestro trabajo esta vez es buscar en fichero de texto y encontrar cualquier cosa que sea un número local de teléfono de 7 dígitos. Vamos a decir, arbitrariamente, que si encontramos 7 dígitos en una fila, o tres dígitos seguidos de un guión o un espacio seguido de otros 4 dígitos, tendremos un candidato. Aquí vemos los ejemplos de los números de teléfono válidos:

      				1234567
      				123 4567
      				123-4567
      				

      La clave para crear esta expresión es ver que necesitamos “0 o una instancia ya sea de espacio o guión” en el medio de nuestros dígitos:

      				ddd([-s])?dddd
      				
    • El punto predefinido

      En adición a s, d y w, metacaracteres que ya hemos discutido, tenemos también que entender el metacaracter “.” o punto. Cuando vemos este carácter en una expresión regex, significa “cualquier carácter sirve aquí”. Por ejemplo, en la siguiente fuente y patrón:

      				source:	"ac abc a c"
      				pattern: a.c
      				

      Producirá la salida:

      				3 abc
      				7 a c
      				

      El “.” fue capaz de coincidir tanto “b” como ” ” en la fuente de información.

    • Cuantificadores “Codiciosos”

      Cuando usamos los cuantificadores *, +, y ?, podemos podemos configurarlos un poco para producir un comportamiento que es conocido como “greedy”, “reluctant”, o “possesive”:

      				? es "greedy", ?? es "reluctant", para cero o ninguno
      				* es "greedy", *? es "reluctant", para cero o ninguno
      				+ es "greedy", +? es reluctant, para uno o varios
      				

      ¿Qué pasa cuando tenemos la siguiente fuente o patrón?

      				source:	 yyxxxyxx
      				pattern: .*xx
      				

      Lo primero, estamos haciendo algo un poco diferente aquí buscando caracteres que tienen un prefijo para la porción estática xx de la expresión. Podemos pensar que estamos diciendo: “Busca sets de caracteres que terminan con xx”. Antes vamos a ver que está pasando. Recordemos que dijimos antes que en general, los motores regex trabajan de izquierda a derecha, y los caracteres consumidos no volvían a ser revisados. Por lo que, trabajando de izquierda a derecha, podemos predecir que el motor buscaría los primeros 4 caracteres (0-3), encuentra xx empezando en la posición 2, y entonces tendría su primera coincidencia. Entonces procedería y encontraría la segunda coincidencia empezando en al posición 6. Esto nos llevaría a un resultado como el siguiente:

      				0 yyxx
      				4 xyxx
      				

      Un segundo argumento plausible es que, dado que pedimos un set de caracteres que terminen en xx puede que obtengamos un resultado como el siguiente:

      				0 yyxxxyxx
      				

      La manera de pensar sobre esto es considerar el nombre “greedy” o codicioso. Para que la segunda respuesta sea correcta, el motor regex tendría que mirar (codiciosamente) la fuente entera antes de que pudiera determinar que había un xx al final. Así que de hecho, el segundo resultado es el resultado correcto porque en el ejemplo original habíamos usado el cuantificador greedy o codicioso *. El resultado que encuentra 2 sets diferentes puede ser generado usando el cuantificador “reluctant” o reacio *?. Vamos a verlo:

      				source:	 yyxxxyxx
      				pattern: .*xx
      				

      Está usando el cuantificador codicioso * y produce:

      				0 yyxxxyxx
      				

      Si cambiamos el patrón a :

      				source:	yyxxxyxx
      				patter:	.*?xx
      				

      Estaríamos usando ahora el *?, por lo que obtendríamos lo siguiente:

      				0 yyxx
      				4 xyxx
      				

      El cuantificador codicioso de hecho lee la fuente de información entera, y entonces empieza a trabajar al revés (desde la derecha) hasta que encuentra la coincidencia mas próxima a la derecha. En este punto, incluye todo desde el principio de la fuente de información.

    • Cuando los Metacaracteres y las Cadenas chocan

      Hasta aquí hemos estado hablando sobre regex desde una perspectiva teórica. Antes de poner regex a trabajar tenemos que discutir un asunto mas. Cuando tengamos que implementar regex en nuestro código, será muy común que nuestra fuente de información y/o nuestras expresiones estén almacenadas en Strings. EL problema es que los metacaracteres y los Strings no se llevan muy bien. Por ejemplo, vamos a decir que queremos hacer un patrón regex simple que busque dígitos. Podemos intentar algo como esto:

      				String pattern = "d";	// Error de compilador
      				

      Esta línea de código no compilará. El compilador ve la barra y piensa, “Ok, una secuencia de escape, puede ser una nueva linea!” Pero no, lo siguiente es una d y el compilador dice “Nunca he oido nada sobre la secuencia de escape d”. La manera de satisfacer al compilador es añadir otra barra antes de d.

      				String pattern = "\d";		// Un metacaracter compilable
      				

      La primera barra le dice al compilador que lo que venga después debe ser cogido literalmente, no como una secuencia de escape. ¿Qué pasa con el metacaracter punto “.”? Si queremos un punto en nuestra expresión para que sea usado como metacaracter, entonces no hay problema, pero ¿Qué pasaría si estuviéramos leyendo una fuente de información que usa puntos como delimitador? Aquí hay otra manera de ver las opciones:

      				String p = ".";	// regex ve esto como un metacaracter "."
      				String p = ","; 	// El compilador ve esto como una secuencia de escape ILEGAL	
      				String p = "\."; 	// El compilador estará feliz, y regex verá un punto, no un metacaracter
      				

      Un problema similar puede ocurrir cuando tenemos metacaracters en un programa Java mediante los argumentos de la linea de comandos. Si queremos pasar el metacaracter d en nuestro programa Java, nuestra JVM hará lo correcto si decimos:

      				% java DoRegex "d"
      				

      Pero puede que nuestra JVM no pueda. Si tenemos problema ejecutando los ejemplos, puede que tengamos que intentar poniendo una barra mas en nustra linea de comandos.

  • Localizando Información via Coincidencia de Patrones

    Ahora que sabemos un poco mas sobre regex, usando las clases java.util.regex.Pattern y java.util.regex.Matcher es mas fácil. La clase Pattern es usada para almacenar la representación de la expresión regex, por lo que puede ser usada y reutilizada por instancias de la clase Matcher. La clase Matcher es usada para invocar el motor regex con la intención de realizar operaciones de coincidencias. El siguiente programa muestra las clases Pattern y Matcher en acción, y no es una mala idea de hacer experimentos por nuestra cuenta sobre regex. Puede que queramos modificar la siguiente clase añadiendo alguna funcionalidad de la clase Console. De esta manera obtendremos mas práctica con la clase Console, y será mas facil ejecutar múltiples experimentos de regex.

    		class Regex {
    			public static void main(String[] args) {
    				Pattern p = Pattern.compile(args[0]);
    				Matcher m = p.matcher(args[1]);
    				System.out.println("Pattern is " + m.pattern());
    				while (m.find()){
    					System.out.print(m.start() + " " + m.group());
    				}
    				
    			}
    		
    		}
    		

    Este programa usa el primer argumento de la linea de comando para representar al expresión regex que queremos usar, y usa el segundo argumento para representar la fuente de información que queremos buscar. Aquí hay un test para ejecutar:

    		% java Regex "dw" "ab4 56_7ab"
    		

    Produce la salida:

    		Pattern is dw
    		4 56
    		7 7a
    		

    Ya que a menudo tendremos caracteres especiales o espacios en blanco como parte de nuestros argumentos, probablemente queramos añadir ” ” siempre para encerrar nuestros argumentos. Vamos a ver un poco el código en mas detalle. Lo primero, vemos que no estamos usando new para crear un nuevo Pattern; si comprobamos la API, veremos que no encontraremos constructor en su listado. Usaremos el método sobrecargado y static compile() para crear una instancia de Pattern.

    El método mas importante en este programa es el método find(). Este es el método que actualmente hace funcionar el motor regex y empieza a realizar alguna búsqueda. El método find() retorna true si ha recogido alguna coincidencia, y recuerda la posición de comienzo de la coincidencia. Si find() retorna true, podemos llamar el método start() para empezar desde la posición de comienzo de la coincidencia, y entonces podemos llamaral método group() para obtener la cadena que representa el pedazo de la fuente de código que se corresponde al patrón.

    La clase Matcher nos permite mirar en subsets de nuestra fuente de información usando un concepto llamado regions. En la vida real, las regiones pueden incrementar de una gran manera el rendimiento.

    • Buscando Usando la Clase Scanner

      Aunque la clase java.util.Scanner es primariamente usada para tokenizar información, puede también ser usada para buscar cosas, como las clases Pattern y Matcher. Mientras Scanner no provee de una información de localización o la funcionalidad de buscar y reemplazar, podemos usarla para aplicar expresiones regex a una fuente de información para decirnos cuantas instancias de una expresión existen en la pieza dada de la fuente. El siguiente programa usa el primer argumento de la linea de comandos para la expresion regex y entonces pregunta por una entrada usando System.in. Imprime un mensaje siempre que una ocurrencia sea encontrada:

      				public class ScanIn {
      					public static void main(String[] args) {
      						// TODO Auto-generated method stub
      						System.out.print("input: ");
      						System.out.flush();
      						try {
      							Scanner s = new Scanner(System.in);
      							String token;
      							s.close();
      							do {
      								token = s.findInLine(args[0]);
      								System.out.println("found " + token);
      							} while (token != null);
      						} catch (Exception e) {
      							System.out.println("scan exc");
      						}
      					}
      				}
      				

      La invocación y entrada:

      				java ScanIn "dd"
      				input: 1b2c335f456
      				

      Producirá la siguiente salida:

      				found 33
      				found 45
      				found null
      				
  • Tokenizing

    Es el proceso de coger grandes piezas de una fuente de información, romperla en piezas mas pequeñas, y almacenarlas en variables. Probablemente la situación de tokenización mas común es leer un fichero delimitado en orden para obtener el contenido del fichero y moverlo a otros lugares como objetos, arrays o colecciones. Veremos 2 clases en la API que nos proveen de capacidades de tokenización: String (usando el método split()) y Scanner, el cual tiene diferentes métodos que son útiles para la tokenización.

    • Tokens y Delimitadores

      Cuando hablamos de tokenización, estamos hablando sobre información que tiene 2 cosas: tokens y delimitadores. Tokens son las piezas de información, y delimitadores son las expresiones que separan tokens los unos con los otros. Cuando la mayoría de la gente piensa en delimitadores, piensan en caracteres singulares, como pueden ser: comas, barras o un espacio en blanco. Estos son delimitadores muy comunes, pero estrictamente hablando, los delimitadores pueden ser mucho mas dinámicos. De hecho, como hemos dicho un par de frases antes, los delimitadores pueden ser cualquier cosa que califica una expresión regex. Vamos a coger una pieza de una fuente de información y vamos a tokenizarla usando un par de delimitadores diferentes:

      				source: "ab,cd5bb,6x,z4"
      				

      Si decemos que nuestro delimitador es una coma, entonces tendremos 4 tokens:

      				ab
      				cd5b
      				6x
      				z4
      				

      Si usaramos la misma fuente, pero declaramos que nuestro delimitador será d, entonces tendremos 3 tokens:

      				ab,cd
      				b,
      				x,z
      				

      En general, cuando tokenizamos fuentes de información, los delimitadores por sí mismo son descartados, y todo lo que nos queda son los tokens. Por lo que en el segundo ejemplo, hemos definido dígitos como delimitadores, por lo tanto el 5, 6 y 4 no aparecerán en los tokens finales.

    • Tokenizando con String.split()

      El método de la clase String llamado split() recoge una expresión regex como argumento y retorna un array de String cargado con los tokens que se han producido por el proceso del split. Esto es un ejemplo práctico de como tokenizar pequeñas piezas de datos. El siguiente programa usa args[0] para almacenar una cadena, y args[1] para almacenar el patrón reegx que usaremos como delimitador:

      				public class SplitTest {	
      					public static void main(String[] args) {
      						String[] tokens = args[0].split(args[1]);
      						System.out.println("count " + tokens.length);
      						for (String s : tokens){
      							System.out.println(">" + s + "<");
      						}
      					}
      				
      				}
      				

      Todo ocurre de golpe cuando el método split() es invocado. La cadena se ha partido en varias piezas, y las piezas se han cargado en el array de String tokens. Todo el código después es solo para verificar que la operación de partición se ha generado. La siguiente invocación:

      				java SplitTest "ab5 ccc 45 @" "d"
      				

      Producirá la salida:

      				count 4
      				>ab<
      				> ccc <
      				><
      				> @<
      				

      Hemos puesto los tokens dentro de “> <" para mostrar los espacios en blanco. Vemos que cada dígito ha sudo usado como delimitador, y los dígitos contiguos crearon un token vacio.

      Un inconveniente de usar el método String.split() es que a menudo querremos mirar los tokens que fueron producidos, y posiblemente salir de la operación de tokenizacion tan pronto como hayamos creado los tokens que necesitábamos. Por ejemplo, puede que estemos buscando un fichero muy grande para localizar un número de teléfono. Si el número de teléfono es encontrado pronto porque está cerca del principio del archivo, querremos salir del proceso de la tokenizacion tan pronto como tengamos el número. La clase Scanner provee de una API rica para hacer justo estas operaciones.

    • Tokenizando con Scanner

      La clase java.util.Scanner es el Cadillac de la tokenización. Cuando necesitamos hacer alguna tokenizacion seria, no debemos buscar mas que Scanner. En adición a las capacidades básicas de tokenización, proveída por String.split(), la clase Scanner nos ofrece las siguientes caracteristicas:

      • Scanner puede ser construido usando ficheros, streams, o Strings como fuente.
      • La tokenización es realizada dentro de un bucle por lo que podemos salir de el en cualquier punto.
      • Los Tokens pueden ser convertidos a un tipo de primitivo apropiado automáticamente.

      Vamos a ver un programa que demeustra el uso de diferentes métodos de la clase Scanner y sus capacidades. El delimitados por defecto del Scanner es el espacio en blanco, el cual el programa usa.

      El programa crea dos objetos Scanner: s1 es iterado con el método next(), el cual retorna cada token como un String, mientras que s2 es analizado con diferentes métodos nextXxx() (donde Xxx es un tipo primitivo):

      				class ScanNext {
      					public static void main(String[] args){
      						boolean b2, b;
      						int i;
      						String s, hits = &quot; &quot;;
      						Scanner s1 = new Scanner(args[0]);
      						Scanner s2 = new Scanner(args[0]);
      						while (b = s1.hasNext()){
      							s = s1.next(); hits += &quot;s&quot;;
      						}
      						while (b = s1.hasNext()){
      							if (s2.hasNextInt()){
      								i = s2.nextInt(); hits += &quot;i&quot;;
      							}else if (s2.hasNextBoolean()){
      								b2 = s2.nextBoolean(); hits += &quot;b&quot;;
      							}else{
      								s2.next(); hits += &quot;s2&quot;;
      							}
      						}
      						System.out.println(&quot;hits &quot; + hits);
      						s1.close();
      						s2.close();
      					}
      				}
      				

      Si este programa es invocado con:

      				java ScanNext &quot;1 true 34 hi&quot;
      				

      Producirá:

      				hits ssssibis2
      				

      Por supuesto, no estamos haciendo nada con los tokens una vez que los tenemos, pero podemos ver que los tokens de “s2” son convertidos a sus respectivos primitivos. Un punto clave aquí es que los métodos nombrados hasNextXxx() comprueban el valor del siguiente token pero no lo obtiene, ni se mueve al siguiente token en la fuente de información. Los métodos nextXxx() realizan 2 funciones; obtienen el siguiente token, y entonces se mueven hasta el siguiente token.

      La clase Scanner tiene métodos nextXxx() para cada primitivo excepto para el tipo char. En adición, la clase Scanner tiene un método useDelimiter() que permite establecer el delimitador a cualquier expresión regex válida.

  • Formateando con printf() y format()

    Los métodos format() y printf() fueron añadidos a java.io.PrintStream en Java 5. Esto son 2 métodos se comportan exactamente de la misma manera, así que cualquier cosa que digamos sobre uno de ellos se aplica a ambos.

    Tras las escenas, el método format() usa la clase java.util.Formatter para hacer trabajos fuertes de formateo. Podemos usar la clase Formatter directamente si queremos. Estos métodos aceptan argumentos en su sintaxis. La documentación para los argumentos de formateado pueden ser encontrados en la API de Formatter. Vamos a hacer un pequeño tour de la sintaxis de formateado de String, el cual será mas que suficiente para empezar con tareas grandes de formateado. Vemos cual es su sintaxis básica:

    		printf(&quot;cadena a formatear&quot;, argumento(s));
    		

    La cadena a formatear puede contener un literal string que no está asociado a ningún argumento, y una información a formatear con argumentos específicos. La pista para determinar cuando estamos buscando por un dato a formatear, es que el dato a formatear empezará siempre con un signo de porcentaje (%). Vamos a ver un ejemplo, y aunque podamos no entender nada, lo veremos a continuación:

    		System.out.printf(&quot;%2$d + %1$d&quot;, 123, 456);
    		

    Esto producirá la salida:

    		456 + 123
    		

    Vamos a ver que ha pasado. Dentro de las comillas dobles hay una cadena para ser formateada, seguida del símbolo + y la segunda cadena a formatear. Tengamos en cuenta que hemos mezclado literales con cadenas para formatear. Ahora vamos a ir un poco mas al fondo y a ver la construcción de estas cadenas para formatear:

    		%[arg_index$][flags][width][.precision]conversion char
    		

    Los valores dentro de [ ] son opcionales. En otras palabras, los únicos elementos requeridos de la cadena a formatear es el % y el caracter de conversión. En el ejemplo de arriba los únicos valores opcionales que usamos son los que representan el index del argumento. El 2$ representa el segundo argumento, y el 1$ representa el primer argumento. La “d” después de los argumentos es el carácter de conversión (mas o menos el tipo del carácter). Aquí hay un resumen de los elementos para las cadenas que mas se usan y que podemos aprender para ir empezando:

    • arg_index: Un entero seguido directamente por un $, indica que argumento debería ser impreso en esta posición.
    • flags: Mientras hay varios flags disponibles, los mas básicos son los siguientes:
      • “-” Justifica a la izquierda este argumento.
      • “+” Incluye el signo (+ p -) con este argumento.
      • “0” Rellena este argumento con ceros.
      • “,” Usa separadores de grupo específicos de una localización (Ejemplo, la coma en 123,456).
      • “(” Cierra números negativos entre paréntesis.
    • width: Este valor indica el número mínimo de caracteres a imprimir.
    • precision: En el caso de números que son float, la precisión indica el número de dígitos después del punto decimal.
    • conversion: El tipo de argumentos que vamos a formatear. Tenemos que saber:
      • b boolean
      • c char
      • d integer
      • f floating point
      • s string

    Vamos a ver algunas de estas cadenas para formatear en acción:

    		int i1 = -123;
    		int i2 = 12345;
    		System.out.printf(&quot;&gt;%1$(7d&lt; n&quot;, i1);
    		System.out.printf(&quot;&gt;%0,7d&lt; n&quot;, i2);
    		System.out.printf(&quot;&gt;%+-7d&lt; n&quot;, i2);
    		System.out.printf(&quot;&gt;%2$b + %1$5d&lt; n&quot;, i1, false);
    		

    Producirá la salida:

    		&gt;  (123)&lt; 
    		&gt;012.345&lt; 
    		&gt;+12345 &lt; 
    		&gt;false +  -123&lt; 
    		

    (Hemos agregado los literales > y < para ayudar a ver la cantidad de ancho, y el relleno con los ceros y la alineación). Finalmente, es importante recordar que si el tipo especificado en el carácter de conversión y el argumento no coinciden, obtendremos una excepción en tiempo de ejecución:

    		System.out.format(&quot;%d&quot;, 12.3);
    		

    Esto produce la salida:

    		Exception in thread &quot;main&quot; java.util.IllegalFormatConversionException: d != java.lang.Double
    			at java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4045)
    			at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2748)
    			at java.util.Formatter$FormatSpecifier.print(Formatter.java:2702)
    			at java.util.Formatter.format(Formatter.java:2488)
    			at java.io.PrintStream.format(PrintStream.java:970)
    			at sekth.droid.format.FormatData.main(FormatData.java:11)
    		


En esta entrada tenemos una introducción al formateado de cadenas, y con ello podemos empezar a dar formato a todo aquello que necesitemos.

Sin más, cualquier aporte o corrección es bienvenida.

Saludos!!!