domingo, 22 de diciembre de 2019

Usar y tirar (autoboxing)

A partir de java 5 es posible evitar hacer conversiones explícitas de int a java.lang.Integer, por ejemplo:

   public void main() {
      Integer suma1 = sum(1, 2);
      suma1 = sum(1, 2);
   }

   static int sum(Integer a, Integer b) {
      return a + b;
   }

Cuando cuando se de-compila el código de la functión main (Útilizando Bytecode viewer), se obtiene lo siguiente:

     public main() { //()V
         ...

         L1 {
             iconst_1
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             iconst_2
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             invokestatic AutoboxExample.sum(Ljava/lang/Integer;Ljava/lang/Integer;)I
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             astore1
         }
         L3 {
             iconst_1
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             iconst_2
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             invokestatic AutoboxExample.sum(Ljava/lang/Integer;Ljava/lang/Integer;)I
             invokestatic java/lang/Integer.valueOf(I)Ljava/lang/Integer;
             astore1
         }
         L4 {
             return
         }
         L2 {
         }
     }

Donde se ve que el código compilado es idéntico a cuando se utiliza valueOf explícitamente.

Esta funcionalidad del compilador, evita que se creen objetos de tipo Integer mediante el constructor Integer(int). Internamente la función valueOf(int), utiliza un cache,  para disminuir la creación de objetos de un solo uso.

Este cache está configurado por defecto para los número de -128 a 127 y se puede configurar para incrementar la cantidad de elementos (-XX:AutoBoxCacheMax=<size>).

miércoles, 27 de noviembre de 2019

Usar y tirar (foreach)

Para iterar sobre un List se puede realizar de la siguiente manera:

  List<string> list = ...;

  int size = list.size();
  for (int i = 0; i < size; i++) {
   String str = list.get(i);
   // ...
   System.out.println(str);
  }

Alternativamente se puede escribir utilizando un interator y un ciclo while:

  List<string> list = ...;

  Iterator<string> iterator = list.iterator();
  while (iterator.hasNext()) {
   String str = iterator.next();
   // ...
   System.out.println(str);
  }


A partir de java 5 se puede escribir así utilizando un foreach:

  List<string> list = ...;

  for (String str : list) {
   // ...
   System.out.println(str);
  }

Al de-compilar la versión con foreach (última) se observa que el compilador, en genera el mismo código que la versión iterator-while. Pero el iterador es un objeto de "usar y tirar", debido a su corta vida, por lo que básicamente es basura, por lo que hay que justificar muy bien su uso. El objetivo de utilizar un iterator, se da principalmente en ambiente multi-hilo, donde pueden ocurrir modificaciones, mientras se esta iterando la lista, pero si la iteración se da en un ambiente de un solo hilo, no es necesario crear el iterator y lo ideal es utilizar el for-size.

martes, 26 de noviembre de 2019

Usar y tirar (vargs)

Objetos de un solo uso

Los objetos de un solo uso son comunes y muchas veces no es trivial visualizarlos, pues son creados implícitamente, por ejemplo:

 public static void main(String[] args) {
  test1("a", "b");
  test2("a", "b");
  test3(new String[] { "a", "b" });
 }
 public static void test1(String... args) {
 }
 public static void test2(String arg1, String arg2) {
 }
 public static void test3(String[] args) {
 }

Al de-compilar la función main, utilizando Bytecode Viewer, se obtiene lo siguiente 


Los segmentos L1 y L3, son la invocación del método test1 y el segmento L5 es la invocación del método test3. El código en ambos casos es idéntico, creando un arreglo de cadenas con longitud 2 (iconst_2 y newarray). La implicación es que se crea un arreglo implícitamente, al invocar una función con argumentos variables.

Si el código se modifica, para invocar 2 veces la misma función:

  test1("a", "b");

  test1("a", "b");

El resultado de de-compilar el .class se obtiene lo siguiente:


Donde es posible ver que se crean 2 arreglos distintos, lo cual provoca que el garbage collector sea invocado demasiadas veces.

Para lidiar con el problema algunas herramientas o APIs, evitan utilizar varargs. Por ejemplo Log4j 2, declara múltiples funciones con el mismo nombre, pero que reciben parámetros Object desde el 1 hasta el 9.

Funcionalidades similares, se dan con el foreach o la creación de cadenas.

domingo, 24 de noviembre de 2019

assert en Java

Encontré un caso de uso útil para la sentencia assert de java. Cuando se puede definir una constante dependiente de otra previamente definida, por ejemplo:

    public static final String EXT = ".js";
    public static final int EXT_LENGTH = 3;

    static {
        assert EXT_LENGTH == EXT.length();
    }

    public static void main(String[] args) {
        // ...
        String filename = args[0];
        if (filename.endsWith(EXT)) {
            String name = filename.substring(0, filename.length() - EXT_LENGTH);
            System.out.println(name);
            name = filename.substring(0, filename.length() - EXT_LENGTH_ALT);
            System.out.println(name);
        }
        // ...
    }

Al observar el .class, utilizando BYTECODE VIEWER, podemos ver el código equivalente al generado por el compilador:


Como es posible observar, al utilizar la constante literal EXT_LENGTH (cuyo valor se conoce en tiempo de compilación), el valor se substituye directamente, mientras que al utilizar la constante, el código no cambia, pues el valor se conoce al momento de la ejecución. El tema de utilizar assert, es validar que cuando se realiza un cambio (por ejemplo de extensión en este caso), se verifique que el valor de la constante literal se modifique también, si se utiliza maven, es posible definir la propiedad enableAssertions con el valor de true, lo que permite que los asserts se evalúen en conjunto con las pruebas unitarias (ver Surefire maven plugin).

sábado, 23 de noviembre de 2019

foreach en Java (for para Collections y arrays nativos)

A partir de la versión 5 de java la secuencia de control for, puede utilizarse como un foreach. El uso de dicha notación hace que la cantidad de código escrito sea menor, pero tiene sus implicaciones negativas, veamos el siguiente ejemplo:

void iterateArray() {
String[] array = new String[] { "alfa", "beta" };
for (String current : array) {
System.out.println(current);
}
}

void iterateCollection() {
Collection collection = Arrays.asList("alfa", "beta");
for (String current : collection) {
System.out.println(current);
}
}

Ambos códigos se "parecen", pero al revisar el código generado podemos observar las diferencias de fondo, utilizando BYTECODE VIEWER, donde es posible decompilar la clase resultante y ver lo que genera el compilador (también muestra las instrucciones de la JVM, pero en este caso nos interesa el código generado)


Como se puede ver, de inicio si bien el código escrito por el programador es menor, el código generador por el compilador, para ser ejecutado por la JVM, es similar al que se empleaba antes de java 5 (es decir sin la sentencia de control foreach). Cabe destacar que ademas por lo generar al ejecutar la instrucción iterator de la colección, se crea un objeto para iterar (ver complementación de Iterable en ArrayList por ejemplo, el cual regresa "new Itr()")

martes, 22 de mayo de 2012

Conversión de una fecha a una cadena

Cuando es importante el que el tiempo de respuesta sea mínimo, la conversión a cadenas de tipos complejos, pero comunes, puede volverse importante. Un ejemplo de esto, es la conversión de fechas a cadenas. La mayoría de los APIs de generación de bitácoras, tocan el tema como un factor importante en el desempeño de una aplicación, puesto que la generación de las bitácoras, pueden influir drásticamente en tiempo de respuesta, dada la cantidad de las mismas que pueden generarse.

Para convertir una fecha a una cadena, en un formato determinado, particularmente sin utilizar los nombres de los días o de los meses, se puede realizar utilizando un arreglo de caracteres y utilizando funciones matemáticas simples.

01: package org.test.util;
02: 
03: import java.util.Calendar;
04: 
05: public final class DateTimeUtil {
06: 
07:         private static final Calendar calendar;
08:         private static final char[] buffer;
09: 
10:         static {
11:                 calendar = Calendar.getInstance();
12:                 buffer = new char[23];
13:         }
14: 
15:         private DateTimeUtil() {
16:         }
17: 
18:         /**
19:          * formato equivalente a "yyyy/MM/dd HH:mm:ss,SSS"
20:          */
21:         public static String formatDate(long time) {
22:                 int year;
23:                 int month;
24:                 int day;
25:                 int hour;
26:                 int minute;
27:                 int second;
28:                 int millisecond;
29:                 synchronized (calendar) {
30:                         calendar.setTimeInMillis(time);
31:                         year = calendar.get(Calendar.YEAR);
32:                         month = calendar.get(Calendar.MONTH);
33:                         day = calendar.get(Calendar.DAY_OF_MONTH);
34:                         hour = calendar.get(Calendar.HOUR_OF_DAY);
35:                         minute = calendar.get(Calendar.MINUTE);
36:                         second = calendar.get(Calendar.SECOND);
37:                         millisecond = calendar.get(Calendar.MILLISECOND);
38:                 }
39:                 synchronized (buffer) {
40:                         buffer[0] = (char) ('0' + year / 1000);
41:                         year %= 1000;
42:                         buffer[1] = (char) ('0' + year / 100);
43:                         year %= 100;
44:                         buffer[2] = (char) ('0' + year / 10);
45:                         year %= 10;
46:                         buffer[3] = (char) ('0' + year);
47:                         buffer[4] = '/';
48:                         buffer[5] = (char) ('0' + month / 10);
49:                         month %= 10;
50:                         buffer[6] = (char) ('0' + month);
51:                         buffer[7] = '/';
52:                         buffer[8] = (char) ('0' + day / 10);
53:                         day %= 10;
54:                         buffer[9] = (char) ('0' + day);
55:                         buffer[10] = ' ';
56:                         buffer[11] = (char) ('0' + hour / 10);
57:                         hour %= 10;
58:                         buffer[12] = (char) ('0' + hour);
59:                         buffer[13] = ':';
60:                         buffer[14] = (char) ('0' + minute / 10);
61:                         minute %= 10;
62:                         buffer[15] = (char) ('0' + minute);
63:                         buffer[16] = ':';
64:                         buffer[17] = (char) ('0' + second / 10);
65:                         second %= 10;
66:                         buffer[18] = (char) ('0' + second);
67:                         buffer[19] = ',';
68:                         buffer[20] = (char) ('0' + millisecond / 100);
69:                         millisecond %= 100;
70:                         buffer[21] = (char) ('0' + millisecond / 10);
71:                         millisecond %= 10;
72:                         buffer[22] = (char) ('0' + millisecond);
73:                         return new String(buffer);
74:                 }
75:         }
76: 
77: }

Cómo se puede observar, se utiliza una sola instancia de java.util.Calendar, así como una sola instancia de un arreglo de caracteres, con el fin de minimizar el uso de memoria. Debido a esto, es necesario sincronizar el código que extrae el año, mes, día, hora, minuto, segundo y milisegundo. Posteriormente se libera el objeto calendar y se sincroniza el código que realiza el formateo los datos extraídos previamente, esto utilizando las operaciones división, suma y modulo. Finalmente, se construye una cadena con el arreglo de caracteres.

El formato es fijo, por lo que no hay necesidad de tratar de detectar posibles opciones. No se utilizan formatos que dependan del lenguaje, por lo que la longitud, también es fija. Esto en conjunto ayuda a minimizar el tiempo de respuesta, dando una opción al uso de la clase java.util.SimpleDateFormat, la cual al ser una clase genérica, puede tardar un poco más, debido a la necesidad de resolver las situaciones con el idioma.

jueves, 17 de mayo de 2012

Constantes dinámicas

En ocasiones es neceasario cargar valores constantes de un archivo de configuración. Estos archivos de configuración se cargan en memoria parseando el archivo y posteriormente obteniendo la propiedad respectiva de objeto que resulta del parseo del archivo. Incluso puede exiatir la neceaidad de realizar la conversión de la esta a número, fecha, booleano, etc..

El siguiente código es común encontrarlo en cualquier desarrollo (se presume que la variable properties es del tipo java.util.Properties):


 while (condition) {

  System.out.println(Float.parseFloat(properties
    .getProperty("percent")));
  System.out.println(currentData.getCurrentAmount()
    * Float.parseFloat(properties.getProperty("percent")));
  float amount = currentData.getCurrentAmount()
    * Float.parseFloat(properties.getProperty("percent"));
  save(amount);

 }

Donde, como se puede ver, se parsea hasta 3 veces la misma propiedad. Para solventar este problema, es posible declarar en la clase (o en una clase auxiliar) esta propiedad, incluso realizando la conversión previamente.


  private static final float PERCENT = Float.parseFloat(properties
   .getProperty("percent"));

Y en el código anterior: 

  while (condition) {
    System.out.println(PERCENT);
    System.out.println(currentData.getCurrentAmount() * PERCENT);
    float amount = currentData.getCurrentAmount() * PERCENT;
    save(amount);
  }

Con lo cual se evita el procesamiento adiciónal de recuperar la propiedad y posteriormente realizar la conversión.

miércoles, 19 de octubre de 2011

Extraer un caracter de una cadena en java





En ocasiones, es necesario extraer un caracter de una cadena. Existen varias formas de realizar esta accion, una de ellas, es la siguiente:

String s = "Hola mundo";
  s.substring(0,1);

Sin embargo, la forma optima de realizar esta tarea, es utilizado la funcion charAt.

String s = "Hola mundo";
  s.charAt(0);

La principal ventaja en utilizar esta ultima funcion, radica en que se obtiene un valor directamente, mientras que en el primer codigo, se obtiene un objeto, por lo que al comparar, en el primer caso, es necesario utilizar la funcion equals, mientras que en la segunda se utiliza el operador de comparacion ==, derivado de lo mismo (al menos hasta java 1.6), el valor retornado por la funcion charAt, se puede utilizar dentro de una instruccion switch.

domingo, 21 de agosto de 2011

Concatenacion de dos cadenas (concat)

La concatenacion de cadenas, es una operacion ampliamente utilizada en los lenguajes de programacion. Pero en el caso de algunos lenguajes compilados, existen metodos poco utilizados, pero muy utiles. Un ejemplo es el metodo java.lant.String.concat o System.String.Concat que tienen algunos lenguajes de programación como java o C#. Veamos por que.


Cuando es necesario concatenar exactamente 2 cadenas y utilizar inmediatamente el resultado de esto (por ejemplo, agregar una extension a un nombre de archivo, para crearlo) es recomendable utilizar este metodo y no algun otro alternativo, como el uso de operadores o de clases auxiliares, como lo es java.lang.StringBuilder o System.StringBuilder.

¿Que ocurre, cuando se emplea StringBuilder?
  1. Creacion del objeto StringBuilder, inicializado, con la primer cadena (Creacion del primer objeto)
  2. Agregar los elementos caracteres de la segunda cadena (posiblemente expandiendo el buffer del builder, posiblemente creando el segundo objeto implicito, un arreglo de caracteres).
  3. Crear un nuevo objeto cadena (Creacion del segundo o tercer objeto)
¿Que ocurre cuando se emplea el metodo concat?
  1. Cracion de un nuevo objeto cadena, con la longitud exacta de la suma de ambos elementos (Creacion del primer y unico nuevo objeto)
  2. Copiar los elementos de la primer cadena
  3. Copiar los elementos de la segunda cadena
Cuando se trata de concatenar exactamente dos cadenas, el metodo concat es optimo, en terminos de uso de memoria, por que solo utiliza la cantidad exacta de memoria, mientras que la clase StringBuilder, al ser generica (para concatenar cualquier cantidad de cadenas) aloja bloques de memoria de tamaños distintos, tratando de evitar alojar memoria posteriormente (normalmente, son bloques cada vez mas grandes) y al finalmente, crea un objeto String, con el resultado de la concatenacion.

Nota: Dependiendo del lenguaje de programacion, asi como la version del mismo, el metodo de concatenacion, puede o no tener alguna de las siguientes optimizaciones.

  •  Si el metodo recibe dos parametros exactamente y es miembro estatico
    • Si ambos parametros son nulos, se regresa la cadena vacia.
    • Cuando alguno de los dos parametros es nulo, se regresa el otro parametro.
    • Cuando alguno de los dos parametros es la cadena vacia, se regresa el otro parametro. 
  • Cuando el metodo no es estatico y se invoca utilizando un objeto de la clase String
    • Si el parametro que se recibe es nulo, se regresa la cadena referenciada por this.
    • Si el parametro que se recibe es la cadena vacia, se regresa la cadena referenciada por this.

viernes, 15 de julio de 2011

Utilizar la memoria RAM como cache en Linux (ramdisk)

La mayoria de las distribuciones de linux cuentan con la capacidad de utilizar la memoria de acceso aleatorio como un sistema de archivos normal, donde se puede crear, leer, escribir y borrar un archivo, asi como crear directorios y borrarlos.

Lo primero que hay que ubicar, es que existan los archivos que inician con el prefijo ram, en el directorio /dev. Al ejecutar el listado de estos archivos, se obtiene una salida similar a esta:

   $ ls -l /dev/ram*
   brw-rw----. 1 root disk 1, 0 Jul 14 22:36 /dev/ram0
   brw-rw----. 1 root disk 1, 1 Jul 14 22:49 /dev/ram1
   brw-rw----. 1 root disk 1, 10 Jul 14 22:36 /dev/ram10
   brw-rw----. 1 root disk 1, 11 Jul 14 22:36 /dev/ram11
   brw-rw----. 1 root disk 1, 12 Jul 14 22:36 /dev/ram12
   brw-rw----. 1 root disk 1, 13 Jul 14 22:36 /dev/ram13
   brw-rw----. 1 root disk 1, 14 Jul 14 22:36 /dev/ram14
   brw-rw----. 1 root disk 1, 15 Jul 14 22:36 /dev/ram15
   brw-rw----. 1 root disk 1, 2 Jul 14 22:36 /dev/ram2
   brw-rw----. 1 root disk 1, 3 Jul 14 22:36 /dev/ram3
   brw-rw----. 1 root disk 1, 4 Jul 14 22:36 /dev/ram4
   brw-rw----. 1 root disk 1, 5 Jul 14 22:36 /dev/ram5
   brw-rw----. 1 root disk 1, 6 Jul 14 22:36 /dev/ram6
   brw-rw----. 1 root disk 1, 7 Jul 14 22:36 /dev/ram7
   brw-rw----. 1 root disk 1, 8 Jul 14 22:36 /dev/ram8
   brw-rw----. 1 root disk 1, 9 Jul 14 22:36 /dev/ram9

Para formatear y utilizar estos archivos, es necesario hacerlo como el usuario root. Esto, debido a que el propietario de los archivos es root.

   $ su -
   Password:
   # mkfs.ext2 -q /dev/ram1
   # mkdir tmp
   # mount /dev/ram1 tmp
   # df -k tmp
   Filesystem 1K-blocks Used Available Use% Mounted on
   /dev/ram1 15863 140 14904 1% /root/tmp

Ahora en el directorio tmp, se tiene un sistema de archivos, que utiliza la memoria de acceso aleatorio, como un directorio normal dentro del sistema. Cabe destacar que este directorio, sera destruido cuando se reinicie el sistema, por lo que cualquier informacion importante, debe ser respaldada previamente antes de reiniciar.


---

jueves, 14 de julio de 2011

Utilizar la memoria RAM como cache automatico en Linux

En los sistemas linux, el uso de memoria RAM como cache, es gestionado por el kernel de manera automatica, sin necesidad de configurar o ejecutar los programas de manera distinta a la usual.

Veamos el siguiente ejemplo, en el cual, se tiene un archivo de 25.85M aproximadamente.

   $ ls -l archivo.bin
   -rwxr-xr-x. 1 cmartine cmartine 27109840 Jul  1 10:52 archivo.bin

Utilizando el comando time, para determinar el tiempo de ejecucion y el comando cat para realizar la lectura del archivo, obtenemos lo siguiente en la primer ejecucion. Nota: se envia la salida al dispositivo /dev/null, para descartarla.

   $ time cat archivo.bin > /dev/null
   real   0m0.466s
   user   0m0.002s
   sys   0m0.123s

El tiempo que se tardo en ejecutar fue de 0.466 segundo. Posteriormente, ejecutando el mismo comando inmediatamente 3 veces seguidas, se obtiene el siguiente resultado:

   $ time cat archivo.bin > /dev/null
   real    0m0.040s
   user    0m0.002s
   sys     0m0.037s

   $ time cat archivo.bin > /dev/null
   real    0m0.026s
   user    0m0.001s
   sys     0m0.025s

   $ time cat archivo.bin > /dev/null
   real    0m0.029s
   user    0m0.000s
   sys     0m0.025s

Como se puede observar, el tiempo real, disminuyo drasticamente a menos de la decima parte del tiempo original. Esto es debido a que el kernel de linux maneja de manera automatica la memoria RAM que no se emplea en crear un cache de los archivos utilizados, sin necesidad de realizar configuracion alguna. La cantidad de memoria que esta siendoutilizada en este cache, se puede consultar utilizando el comando top.

¿Por que linux hace esto? Sencillamente por que si no se emplea la memoria, estaria desocupada (desperdiciandose cuando el sistema no la esta utilizando para realizar algun trabajo). Es importante aclara, que cuando se ejecuta un programa, el cual requiere utilizar mucha memoria, se libera parte de la memoria utilizada para el cache y se asigna al programa, pudiendo llegar a la situacion en que el cache ya no tenga memoria asignada, por que un programa requiere utilizar toda la memoria del sistema

jueves, 23 de junio de 2011

Comparacion entre 2 fechas

Comparar 2 fechas, para determinar cual es mayor, puede ser sencillo, sobre todo, si con lo que trabajas soporta el concepto de fecha y/o tiempo, por ejemplo: java.util.Date en java o System.DateTime en C#, que son lenguajes de programacion o incluso en hojas de datos, tipo Microsoft Excel o LibreOffice Calc.

El problema, se presenta, cuando es necesario trabajar con lenguajes que no conocen el concepto, como los shells de unix o cuando se almacenan archivos que contienen la fecha en el nombre del mismo.

En este punto la abstraccion de la fecha y la forma en como se representa, suele tomar un papel importante, dado que puede resolver completamente el problema, si se emplea el formato correcto.

Formas comunes de representar una fecha son: dia/mes/año en mexico o mes/dia/año en EEUU. Para nombres de archivos se suelen substituir las diagonales, por guiones, asi como el uso del nombre del mes y no el numero de mes. Por ejemplo:
  • Jun/27/2011
  • Oct/21/2011
  • Jul/1/2011
  • Oct/24/2005
  • Jul/4/2011
Si se ordena alfabeticamente, se obtiene lo siguiente:
  • Jul/1/2011
  • Jul/4/2011
  • Jun/27/2011
  • Oct/21/2011
  • Oct/24/2005
Lo cual no es lo que se requiere, sin embargo, si se reacomoda la fecha utilizando el orden año, mes, dia:
  • 2011/Jun/27
  • 2011/Oct/21
  • 2011/Jul/1
  • 2005/Oct/24
  • 2011/Jul/4
Al ordenar se obtiene lo siguiente:
  • 2005/Oct/24
  • 2011/Jul/1
  • 2011/Jul/4
  • 2011/Jun/27
  • 2011/Oct/21
El problema con el que nos encontramos es que la l esta antes que la n, en orden alfabetico, para sortear este inconveniente, en vez del nombre del mes, utilizamos el numero del mes:

  • 2011/6/27
  • 2011/10/21
  • 2011/7/1
  • 2005/10/24
  • 2011/7/4 
Al ordenar, obtenemos lo siguiente:

  • 2005/10/24
  • 2011/6/27
  • 2011/10/1
  • 2011/7/1
  • 2011/7/4
 Este resultado tampoco es correcto, esto es debido a que la longitud de los meses, es variable, por lo que, agregamos un 0 a la izquierda, cuando el mes esta formado por un solo digito (es recomendable tambien hacerlo para los dias)

  • 2011/06/27
  • 2011/10/21
  • 2011/07/01
  • 2005/10/24
  • 2011/07/04 
Al ordenar, obtenemos lo siguiente:

  • 2005/10/24
  • 2011/06/27
  • 2011/07/01
  • 2011/07/04
  • 2011/10/01
Que es finalmente lo que se requiere.

Esto, se puede aplicar a la creacion de nombres de archivos, por ejemplo, para poder ordenarlos por una fecha en particular, sobre todo cuando la fecha de creacion, es incorrecta (cuando son enviados por correo electronico y se pierde la fecha de creacion del archivo), por ejemplo los siguientes nombres de archivos seran ordenados correctamente por el sistema operativo al momento de listarlos (esto tanto en windows, linux o mac os)
  • reporte_2011-06-27.txt
  • reporte_2011-10-21.txt
  • reporte_2011-07-01.txt
  • reporte_2005-10-24.txt
  • reporte_2011-07-04.txt
 Al ser listados, se obtiene lo siguiente:
  • reporte_2005-10-24.txt
  • reporte_2011-06-27.txt
  • reporte_2011-07-01.txt
  • reporte_2011-07-04.txt
  • reporte_2011-10-21.txt
En unix, incluso, es posible emplear las fechas en este formato, con el comando expr, el cual puede comparar dos cadenas y determinar cual es mayor o menor.

jueves, 5 de mayo de 2011

Implementación la funcion compareTo de IComparable en C#






La interfaces IComparable e IComparable<t> de C#, se utilizan para comparar objetos, normalmente del mismo tipo y determinar, si alguno es mayor, menor o la equivalencia. A continuacion, se presentan 4 ejemplos de como implementar esta funcionalidad.

Nota1: Para el primer ejemplo, se utiliza el concepto de National Identification Number (CURP en México, RUN en Chile, DNI en España y Argentina o Tarjeta de Identidad en colombia, por ejemplo)
Nota2: Por razones de compatibilidad, se implementan 2 interfaces distintas, con funcionalidades similares, en la implementacion de estas clases.

class Person1 : System.IComparable, System.IComparable<Person1>
    {
        private string nin; // National Identification Number
        private string name;
        private string surname;

        //...

        public int CompareTo(object obj)
        {
            if (obj is Person1)
            {
                return obj == null ? 1 : nin.CompareTo(((Person1)obj).nin);
            }
            throw new System.ArgumentException();
        }

        public int CompareTo(Person1 p)
        {
            return p == null ? 1 : nin.CompareTo(p.nin);
        }

    }

    class Person2 : System.IComparable, System.IComparable<Person2>
    {
        private int id;
        private string name;
        private string surname;

        //...

        public int CompareTo(object obj)
        {
            if (obj is Person2)
            {
                return obj == null ? 1 : id.CompareTo(((Person2)obj).id);
            }
            throw new System.ArgumentException();
        }

        public int CompareTo(Person2 p)
        {
            return p == null ? 1 : id.CompareTo(p.id);
        }


    }

    class Person3 : System.IComparable, System.IComparable<Person3>
    {
        private int id;
        private string name;
        private string surname;

        //...

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            else
            {
                if (obj is Person3)
                {
                    Person3 o = (Person3)obj;
                    return id - o.id;
                }
                throw new System.ArgumentException();
            }
        }

        public int CompareTo(Person3 p)
        {
            return p == null ? 1 : id - p.id;
        }

    }

    class Person4 : System.IComparable, System.IComparable<Person4>
    {
        private int idPart1;
        private string idPart2;
        private string name;
        private string surname;

        //...

        private int CompareTo0(Person4 p)
        {
            int v = idPart1 - p.idPart1;
            if (v == 0)
            {
                v = idPart2.CompareTo(p.idPart2);
            }
            return v;
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
            {
                return 1;
            }
            else
            {
                if (obj is Person4)
                {
                    return CompareTo0((Person4)obj);
                }
                throw new System.ArgumentException();
            }
        }

        public int CompareTo(Person4 p)
        {
            if (p == null)
            {
                return 1;
            }
            else
            {
                return CompareTo0(p);
            }
        }

    }


En el primer y segundo ejemplo, se implementa la funcionalidad, utilizando la funcion CompareTo implementada por la clase string y por la estructura int32. Regresando el resultado de ejecutar dicha funcion. En el tercer ejemplo, se muestra, como se puede implementar utilizando la diferencia entre 2 enteros (es posible utilizar cualquier tipo entero, como un byte, short, int). Finalmente, en el cuarto ejemplo, se implementa la funcionalidad utilizando 2 propiedades, donde se compara la primer propiedad y en caso de igualdad, se regresa el resultado de comparar la segunda propiedad. Tambiem, en este ultimo ejemplo, define una funcion intermedia (CompareTo0), la cual hace la comparacion real y es llamada por las 2 implementaciones del CompareTo.

Como se observa. por razones de compatibilidad, es necesario implementar la funcionalidad de 2 interfaces, una utlizando Generics y otra sin pre-generics. En la version Generics, es necesario validar que el tipo de corresponde con la clase que implementa, en caso de no coincidir, hay que generar una excepcion del tipo ArgumentException. En el caso del null, la documentacion tambien especifica regresar 1 explicitamente. Final mente, en C#, como en C, se especifican valores validos cualquier entero positivo, negativo o cero (dentro de los rangos de int32)

Es recomendable, por cuestiones de simplicidad en la implementacion, incluir en la comparacion solo los valores que corresponden a la llave primaria, comparandolos, en orden de importancia, regresando el valor, del primero que resulte distinto (como se muestra en el ejemplo 4).

jueves, 28 de abril de 2011

Implementación la funcion compareTo de Comparable en java





La interface Comparable se emplea, para indicar si el objeto representado por this es menor, igual o mayor que el objeto que se pasa como parametro. A continuacion se presentan 3 ejemplos de clases que definen la funcion compareTo en base a una propiedad existente. Nota, en algunos paises existe el concepto de National Identification Number (CURP en México, RUN en Chile, DNI en España y Argentina o Tarjeta de Identidad en colombia, por ejemplo)


public class ExamplePerson1 implements Comparable<ExamplePerson1> {

 private String nin; // National Identification Number
 private String name;
 private String surname;

 ...

 @Override
 public int compareTo(ExamplePerson1 o) {
  return this.nin.compareTo(o.nin);
 }

}

public class ExamplePerson2 implements Comparable<ExamplePerson2> {

 private Integer id;
 private String name;
 private String surname;

 ...

 @Override
 public int compareTo(ExamplePerson2 o) {
  return this.id.compareTo(o.id);
 }

}

public class ExamplePerson3 implements Comparable<ExamplePerson3> {

 private int id;
 private String name;
 private String surname;

 ...

 @Override
 public int compareTo(ExamplePerson3 o) {
  return this.id < o.id ? -1 : (this.id == o.id ? 0 : 1);
 }

}

En el ejemplo 1 y 2, se emplean propiedades de tipo String e Integer las cuales implementan la interface Comparable, por lo que solo se regresa el resultado de llamar la funcion compareTo de la propiedad respectiva. En el ejemplo 3 se utiliza una propiedad de tipo nativo y se muestra un ejemplo de como puede implementarse la funcion, utilizando las clases que envuelven a los tipos nativos (wrappers), evitando el uso de autoboxing y autounboxing.

En estos ejemplos, solo se emplea una propiedad, para implementar la funcionalidad. La recomendacion, es utilizar solo los atributos que forman la llave primaria (el concepto de base de datos). En caso de requerir utilizar mas de una propiedad, se incremanta el grado de complejidad. Como se muestra en el siguiente ejemplo.

public class ExamplePerson4 implements Comparable<ExamplePerson4> {

 private Integer idPart1;
 private Integer idPart2;
 private String name;
 private String surname;

 ...

 @Override
 public int compareTo(ExamplePerson4 o) {
  int result = this.idPart1.compareTo(o.idPart1);
  if(result == 0) {
   result = this.idPart2.compareTo(o.idPart2);
  }
  return result;
 }

}

Como se observa, si la comparacion de la primer propiedad es cero, hay que comparar la segunda propiedad y asi sucesivamente, hasta encontrar una distinta o ya no tener mas propiedades.

--- Actualizacion ---

En ocasiones, es necesario evitar que la comparacion genere una excepcion del tipo NullPointerException, cuando el parametro que recibe la funcion compareTo, es null. Para procesar el null, es necesario insertar un if para validar si es null, en caso de ser null, se regresa el valor necesario (normalmente 0), en caso contrario, se regresa el valor calculado, utilizando el algoritmo descrito anteriormente.

public class ExamplePerson5 implements Comparable<ExamplePerson5> {

 private Integer id;
 private String name;
 private String surname;

 ...

 @Override
 public int compareTo(ExamplePerson5 o) {
  return o == null ? 0 : this.id.compareTo(o.id);
 }

}

Existe la posibilidad de implmentarlo utilizando un try-catch, en vez de utilizar un if. Si bien en apariencia no se ejecutan 2 operaciones, cuando se emplea el if (primero la comparacion contra null y posteriormente la evaluacion del if en si). En la practica no es optimo, dado que cuando se recibe un null, se genera una excepcion, que implicitamente es, la creacion del objeto excepcion, el llenado del stack (que en el caso de un programa muy sencillo son 3 o 4 elementos en el stack, pero en el caso de sistemas complejos, pueden ser mas de 50 elementos).

public class ExamplePerson6 implements Comparable<ExamplePerson6> {

 private Integer id;
 private String name;
 private String surname;

 ...

 @Override
 public int compareTo(ExamplePerson6 o) {
  try {
   return this.id.compareTo(o.id);
  } catch (NullPointerException e) {
   return 0;
  }
 }

}

En conclusion, siempre es recomendable utilizar un if, si se necesita regresar un valor en caso de que el parametro sea null y evitar el uso de un try-catch.

miércoles, 27 de abril de 2011

Diferencia entre 2 fechas (años cumplidos) en java

Obtener los años cumplidos, a partir de una fecha en particular, tiene sus aplicaciones (validar mayoria de edad en las personas, verificar si aun aplica una garantia, a partir de la fecha de compra, etc)

La aproximacion, es la siguiente:


 1 import java.util.Calendar;
 2 import java.util.Date;
 3 
 4 public class DatesDiff {
 5 
 6  public static int calculateAge(Date bornDate) {
 7   Calendar cal = Calendar.getInstance(); // current date
 8   int currYear = cal.get(Calendar.YEAR);
 9   int currMonth = cal.get(Calendar.MONTH);
10   int currDay = cal.get(Calendar.DAY_OF_MONTH);
11   cal.setTime(bornDate); // now born date
12   int years = currYear - cal.get(Calendar.YEAR);
13   int bornMonth = cal.get(Calendar.MONTH);
14   if (bornMonth == currMonth) { // same month
15    return cal.get(Calendar.DAY_OF_MONTH) <= currDay ? years
16      : years - 1;
17   } else {
18    return bornMonth < currMonth ? years - 1 : years;
19   }
20  }
21 
22 }


Explicación:
07-07: Se obtiene la fecha actual, al obtener una instancia del objeto Calendar
08-10: Se obtienen lo valores del año, mes y dia actual (cache)
11-11: Se actualiza el objeto cal, con la fecha de nacimiento
12-12: Se calcula la diferencia de años
13-13: Se obtiene el año de nacimiento (cache)
14-14: Se verifica si el mes actual coincide con el mes de nacimiento
15-15:  Si los meses coinciden, se valida si el los dias coinciden, de ser este el caso, se regresan la diferencia de años, en caso contrario (aun no se cumple otro año) se regresa la diferencia menos uno.
18-18: Si los meses no coinciden, se regresala diferencia de los años menos 1 si el mes de nacimiento es menor que el mes actual y la diferencia de años, en caso contrario

En este codigo, se utiliza un solo objeto Calendar en vez de 2 objetos distintos. En vez de utilizar un objeto Calendar nuevo, obtenemos los valores que se emplearan a posteriormente en variables de tipo nativo, las cuales radican en el pila (stack), en vez de el  (heap).

Una optimizacion posible, es parametrizar la fecha actual, para el tema de internacionalizacion, con respecto a las zonas horarias, ya que se da la posibilidad que tomando en cuenta la fecha de la zona horaria de un pais distnto puede ser que se cumpla o no un año mas.

jueves, 1 de julio de 2010

tablas de hash y su relacion con las funciones GetHashCode y Equals de C# y hashCode y equals de Java

La mayoria de los lenguajes de programacion implementan tablas de hash en alguna de sus variantes (Hashtable, HashMap en Java o Hastable en C#). Un problema asociado a esta funcionalidad, es la correcta implementacion de las funciones para generar el codigo el codigo de hash de la llave, que sirve para obtener el valor asociado a la misma dentro de la tabla de hash.

Como se vio en el post hashCode y equals funcion en Java o GetHashCode y Equals en C#, una tabla de hash, se puede comparar con un diccionario. Dado que la funcion que obtiene el codigo hash de un objeto (GetHashCode para C# y hashCode para Java) puede obtener el valor entre -231 y +231 y si el codigo de hash, es unico, entonces, este codigo puede fungir como una "llave" adicional, sobre todo, si se define correctamente y se implementa una funcion que genere este codigo lo mas rapido posible.

Los tiempos de respuesta y uso de memoria de una tabla de hash, dependen de como se implemente, asi como de una correcta definicion de la funcion que genere el codigo de hash. El secreto del desempeño de la tabla de hash, esta el uso de la aritmetica modular, utilizada para determinar que codigos de hash son similares.

Tanto en Java, como en C# el codigo de hash, esta representado por un numero entero de 32 bits (4 bytes), esto da la posibilidad de tener 232 (poco mas de 4 mil millones o millardos) posibles codigos de hash distintos. Como se vio en otro post, el codigo de hash, se emplea para "indexar" la informacion y realizar una busqueda ejecutando el menor numero de comparaciones posibles.


Para ver como funciona comparemos ahora la tabla de hash con un hospital, el cual tiene un archivo, donde por cuestiones de orden, se colocan los expedientes en cajones. Existen 27 cajones (utilizaremos el alfabeto ingles, el cual no tiene ñ, ch, ni acentos, dieresis u otras letras, simbolos, acentos, etc) cada cajon almacena los expedientes correspondientes a la letra inicial del nombre de la persona.

Suponiendo que el hospital es nuevo (new Hospital()), los 27 cajones estan vacios. cuando llega el primer paciente, se le genera un nuevo expediente y cuando se da de alta, se almacena su expediente en el archivo, si el paciente se llama Ricardo, el cajon que tiene una letra R al frente tendra un unico expediente y los otros 26 expedientes, seguiran vacios.

Si llega un nuevo paciente, llamado Juan, lo ideal, es que se busque si no tiene un expediente, entonces, buscando en el cajon donde se almacenan los expedientes que inician con J, nos damos cuenta de que no tiene expediente y creamos uno nuevo, para posteriormente almacenarlo.

Pero, este hospital es un poco desordenado al momento de almacenar los archivos, pues cuando llega otra persona con cuyo nombre es Ramon, el nuevo expediente, simplemente se pone encima del anterior y cuando llega Rosa, ocurre lo mismo, es decir, dentro del cada cajon, los expedientes, no se ordenan. Esto provoca que cuando llega una persona llamada Romario, se deba buscar el nombre en 3 expedientes distintos, puesto que al no estar ordenados alfabeticamente, hay que comparar el nombre para verificar que no es el expediente buscado. Por lo que es ligeramente mas tardado el proceso de busqueda del expediente de una persona cuyo nombre inicia con R que si inicia con J o si inicia con cualquier otra letra del abecedario. Ya se atendio Romario y se le genero un expediente. Extrañamente, viene una persona que se llama Ruperto (si, ya se, muchas R), esto provoca que el cajon donde estan estos expedientes, comienze a desbordarse y la busqueda cada vez tarde mas.

Esto que estoy describiendo, ocurre, cuando la funcion que genera el codigo de hash (la letra inicial del nombre, en este caso) esta incorrectamente definida y todos o la mayoria de los elementos que estamos almacenando tengan el mismo codigo de hash.

Para evitar esta situacion, se suelen definir funciones, en ocasiones extrañamente complejas, imaginemos por un momento, que cambiase las reglas para almacenar la informacion, ahora tendre las mismas 27 cajas, pero para determinar el cajon donde se almacenara el expediente, se sumara el valor ASCII de cada letra del nombre de la persona, el resultado se multiplicara por 13 (un numero primo por cierto) y se realizara la operacion modulo 27, al resultado de este, se le sumara 1, el resultado de esto, se convertira a una letra, 1 por A, 2 por B  ... y 27 por Z ... Despues de realizar todas estas operaciones , no duden que probablemente haya uno y solo un expediente por cajon,  pero ahora la generacion del codigo de hash se volvio costosa en tiempo.

Ahora, que ocurre cuando se agregan mas letras al abecedario, por ejemplo acentos y dieresis, asi como la letra ñ, pero sin agregar cajones a nuestro sistema de almacenamiento. La solucion es buscar similitudes o equivalencias, por ejemplo n es parecida a la letra ñ y las letras acentuadas o con dieresis a las mismas sin el acento o la dieresis. Esto es buscar similitudes o equivalencias. Para realizar estas operaciones de "equivalencias" las tablas de hash, utilizan aritmetica modular, es decir, dado que el universo para la generacion del codigo de hash, es basto, se emplea este tipo de aritmetica, para determinar que codigos de hash son similares y almacenarlos conjuntamente.