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.


---