José Arrarte – Blog de notas Un bloc de notas online sobre Java, tecnología, Uruguay, etc…

29jul/103

Array de bytes y la clase java.nio.ByteBuffer

Un buffer es una secuencia finita de elementos -en este caso, tipos primitivos-. La clase abstracta java.nio.Buffer y sus respectivas subclases concretas (java.nio.ByteBuffer, java.nio.IntBuffer, java.nio.FloatBuffer, etc.) sirven como contenedores para datos de tipos primitivos. Estos buffers forman parte de la API NIO junto a las classes Channel, Selector, entre otras.

Estas clases, en particular java.nio.ByteBuffer, son de especial utilidad en aplicaciones en las cuales tenemos que generar array de bytes y que no justifica usar frameworks demasiado refinados para dicha tarea.

Un java.nio.Buffer tiene algunas propiedades escenciales:

  • Capacidad: la cantidad de elementos máxima del buffer. Siempre tiene un valor positivo, y no cambia durante la vida del objeto. Debe ser conocido al momento de inicializar el buffer.
  • Límite: según la documentación de java.nio.Buffer, el límite es "el índice del primer elemento que no debería ser leído ni escrito". Es un valor entre la posición y la capacidad del buffer. La idea es utilizarlo como marcador para que, luego de terminada la escritura hacia el buffer, sepamos hasta donde lo podemos leer para obtener el array generado.
  • Posición: la posición actual dentro del bufffer. Es el índice del próximo elemento a leer o escribir.

Funcionalidades básicas

Todas estas funcionalidades están muy detalladas en los javadocs de las clases, pero es interesante repasar las más útiles:

  • Operaciones de escritura: la API provee las distintas variantes de operaciones put, permitiendo agregar bytes y tipos más grandes como ser int, long, float, arrays de bytes o incluso el contenido de otro ByteBuffer. Para los tipos de datos que ocupen más de un byte, se considerará la codificación (endianness) seteado utilizando el método order(). Tenemos métodos put para agregar bytes en posiciones relativas (en la posición actual) o absolutas (en una posición determinada)
  • Operaciones de lectura: al igual que con las operaciones put, las operaciones get permiten recuperar datos del buffer. Tenemos operaciones para recuperar diferentes tipos de datos (siempre teniendo en cuenta la codificación -endianness- cuando los tipos de datos son multi-byte), para recuperarlos desde la posición actual o desde una posición determinada. También podemos cargar un array de bytes con una sección del buffer.
  • clear(): prepara el buffer para una nueva secuencia de operaciones de escritura, seteando el límite con igual valor que la capacidad, y la posición actual en cero.
  • flip(): prepara el buffer para una secuencia de lecturas, seteando el límite igual a la posición actual, y la posición a cero. Más allá de la capacidad máxima del buffer, permite hacer operaciones sobre la porción que utilizamos anteriormente.
  • rewind(): prepara el buffer para una relectura de la información que contiene, seteando la posición en cero sin cambiar el valor del límite.
  • mark() / reset(): mark() setea la marca del buffer en la posición actual. al invocar reset(), cambiamos de posición en el buffer a la última invocación de mark().
  • slice(): crea un nuevo ByteBuffer a partir de la posición actual y hasta el fin del array. Los buffers comparten el array subyacente, por lo que un cambio en los datos se verá reflejado en ambos.
  • array(): retorna el buffer en forma de array.
  • Chaining (encadenamiento) de invocaciones: los métodos que no deben retornar valores al invocarlos, retornan una referencia a sí mismo, de manera de poder encadenar invocaciones al buffer. El encadenamiento no aporta nuevas funcionalidades, simplemente mejora la usabilidad.

Ejemplo de uso

Imaginemos que necesitamos generar un array de bytes para enviar a un sistema externo, que contiene la siguiente información:

CampoTipo de datoLargoFormato
Largo del mensajeshort2 bytesLittle Endian
Idlong8 bytesBig Endian
Largo del nombreshort2 bytesLittle Endian
NombreStringMáximo 30 caracteres-
Edadbyte1 byte-
Salarioint4 bytesBig Endian

Debemos codificar el largo total del mensaje y el largo del nombre como little-endian (es decir, el byte menos significativo va en la primer posición, y el byte más significativo va en la última) en lugar de big-endian -a veces también llamado network order, por ser la codificación definida en el protocolo IP-.

Aprovecharemos las funcionalidades de la clase ByteBuffer para resolver este problema. En particular, ByteBuffer nos permite setear la representación de los enteros de varios bytes en el array, utilizando el método order(ByteOrder bo). Una solución que resuelve la generación del array podría ser la siguiente:

package com.josearrarte.demo;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class ByteBufferDemo {

	private static final int SIZEOF_BYTE = 1;
	private static final int SIZEOF_SHORT = 2;
	private static final int SIZEOF_INT = 4;
	private static final int SIZEOF_LONG = 8;

	public static void main(String[] args) {

		byte[] byteArray = buildByteArray(12345678L, "Pedro Picapiedra", (byte)46, 25000);

		String hexString = getHexString(byteArray);
		System.out.println(hexString);
	}

	private static byte[] buildByteArray(long id, String name, byte age, int salary) {

		int capacity = 2 * SIZEOF_SHORT +	// largos
			SIZEOF_LONG + 					// id
			name.length() + 				// nombre
			SIZEOF_BYTE + 					// edad
			SIZEOF_INT;						// salario

		ByteBuffer buffer = ByteBuffer.allocate(capacity);

		// largo de datos
		buffer.order(ByteOrder.LITTLE_ENDIAN);
		buffer.putShort((short)capacity);

		// id
		buffer.order(ByteOrder.BIG_ENDIAN);
		buffer.putLong(id);

		// largo del campo nombre
		buffer.order(ByteOrder.LITTLE_ENDIAN).putShort((short)name.length());

		// nombre
		buffer.order(ByteOrder.BIG_ENDIAN).put(name.getBytes());

		// edad
		buffer.put(age).putInt(salary);

		return buffer.array();

	}

	private static String getHexString(byte[] array) {
		final char[] chars = { '0', '1', '2', '3', '4',
				'5', '6', '7', '8', '9',
				'A', 'B', 'C', 'D', 'E', 'F' };

		StringBuilder strBuilder = new StringBuilder();

		for (int i = 0; i < array.length; i++) {
			byte b = array[i];

			byte lowNibble = (byte) (b & 0x0F);
			byte highNibble = (byte) ((b & 0xF0) >>> 4);

			strBuilder.append(chars[highNibble]);
			strBuilder.append(chars[lowNibble]);
			strBuilder.append(' ');
		}

		return strBuilder.toString();
	}
}

La salida del programa es:

21 00 00 00 00 00 00 BC 61 4E 10 00 50 65 64 72 6F 20 50 69 63 61 70 69 65 64 72 61 2E 00 00 61 A8

Podemos desglosar la salida para cada dato:

  • Largo de datos: 21 00 (33)
  • Id: 00 00 00 00 00 BC 61 4E (12345678)
  • Largo del campo de nombre: 10 00 (16)
  • Nombre: 50 65 64 72 6F 20 50 69 63 61 70 69 65 64 72 61 ("Pedro Picapiedra")
  • Edad: 2E (46)
  • Salario: 00 00 61 A8 (25000)

Notemos la facilidad ByteBuffer que nos da a la hora de tener que intercambiar los formatos de enteros y de mezclar distintos tipos primitivos en un mismo array. No tuvimos necesidad de ir calculando las posiciones dentro del array, ni calcular la representación de enteros en formato little-endian o big-endian.

Referencias

API de java.nio.ByteBuffer
API de java.nio.Buffer
http://en.wikipedia.org/wiki/Endianness
http://en.wikipedia.org/wiki/New_I/O

Etiquetado con: 3 Comentarios
26jul/100

Descargar archivos de sitios de file-sharing por consola en Linux

Quienes hemos utilizado el excepcional JDownloader sabemos la facilidad que nos proporciona a la hora de descargar una lista de archivos que son secciones de un archivo comprimido. Tengo una PC bastante antigüa (Pentium III con 256MB de RAM) que utilizo como servidor de descargas con MLDonkey sobre Gentoo Linux, pero me hacía falta encontrar un sustituto para el JDownloader que no necesitara un ambiente gráfico para correr.

PlowShare es un conjunto de scripts para el sistema operativo Linux con el fin de permitir administrar las descargas y subidas en sitios de file-sharing, del estilo de RapidShare, MegaUpload, HotFile, etc. Automatiza el reconocimiento de caracteres -CAPTCHA- y los tiempos de espera para comenzar la descarga. Además, es una aplicación de consola con requerimientos de recursos excepcionalmente bajos.

Los scripts son plowdown.sh, plowup.sh, plowdel.sh y plowlist.sh. Seguramente el más utilizado por el común de la gente sea plowdown, y éstas son sus opciones:

server ~ # plowdown
Usage: plowdown [OPTIONS] [MODULE_OPTIONS] URL|FILE [URL|FILE ...]

  Download files from file sharing servers.

  Available modules: rapidshare megaupload 2shared badongo mediafire 4shared zshare depositfiles storage_to uploaded_to uploading netload_in usershare sendspace x7_to hotfile divshare dl_free_fr humyo filefactory data_hu

Global options:

  -h, --help: Show help info
  --version: Return plowdown version
  -v LEVEL, --verbose=LEVEL: Set output verbose level: 0=none, 1=err, 2=notice (default), 3=dbg
  -q, --quiet: Alias for -v0
  -c, --check-link: Check if a link exists and return
  -m, --mark-downloaded: Mark downloaded links in (regular) FILE arguments
  -x, --no-overwrite: Do not overwrite existing files
  --get-module: Get module(s) for URL(s)
  -o DIRECTORY, --output-directory=DIRECTORY: Directory where files will be saved
  --temp-directory=DIRECTORY: Directory where files are temporarily downloaded
  -r SPEED, --limit-rate=SPEED: Limit speed to bytes/sec (suffixes: k=Kb, m=Mb, g=Gb)
  -i IFACE, --interface=IFACE: Force IFACE interface
  -t SECS, --timeout=SECS: Timeout after SECS seconds of waits
  --max-retries=N: Set maximum retries for loops
  --run-download=COMMAND: run down command (interpolations: %filename, %cookies, %url)

Options for module <rapidshare>:

  -a USER:PASSWORD, --auth=USER:PASSWORD: Use Premium-Zone account

Options for module <megaupload>:

  -a USER:PASSWORD, --auth=USER:PASSWORD: Free-membership or Premium account
  -p PASSWORD, --link-password=PASSWORD: Used in password-protected files

Options for module <x7_to>:

  -b USER:PASSWORD, --auth-free=USER:PASSWORD: Use Free account

Al script lo podemos invocar con la URL de un archivo a descargar, una lista de URLs de archivos a descargar o inclusive con la ruta a un archivo de texto que contiene la lista de links (uno por línea). En los últimos dos casos el script descargará los archivos en orden, siendo posible configurar que marque con el símbolo '#' al principio de la línea del archivo cada archivo descargado correctamente (ver --mark-downloaded más abajo).

Algunas de las opciones importantes son:

  • -o (--output-directory): determina el directorio de salida. Es el lugar en donde se ubicarán el o los archvos descargados.
  • -r (--limit-rate): configura la velocidad de transferencia máxima, por ejemplo, -r=50kb, --limit-rate=1m.
  • -c (--check-link): no descarga el o los archivos, sólo verifica que los links sean válidos.
  • -m (--mark-downloaded): cuando se desea descargar todos los links listados en un archivo de texto, marca los archivos descargados correctamente con un '#' al principio de la línea.

Veamos un ejemplo de invocación para descargar un archivo desde MegaUpload:

server ~ # plowdown -v 2 -o /tmp http://www.megaupload.com/?d=977IJRE0
Install aview or libcaca to display captcha image
Waiting 47 seconds... 37.. 27.. 17.. 7.. 0
File URL: http://www937.megaupload.com/files/927a093e5f4d388420ef7ecfcecd73c0/Weeds 5x01 - Seba357.rar
Filename: Weeds 5x01 - Seba357.rar
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
 51  233M   51  120M    0     0   209k      0  0:18:57  0:09:47  0:09:10  212k

En http://code.google.com/p/plowshare/wiki/Readme tenemos otros ejemplos de uso para descargar, subir, borrar y comprobar links en sitios de file-sharing.

El proyecto PlowShare está hosteado en Google Code. La página del proyecto es http://code.google.com/p/plowshare/.

Gentoo Linux tiene el paquete disponible en portage bajo el nombre net-misc/plowshare. Archlinux y (e incluso Mac OSX) también tiene paquete de instalación, para el resto de los sabores de Linux tenemos la posibilidad de instalarlo desde el código fuente.

Etiquetado con: No hay comentarios
10jul/101

Orgullo Celeste

Fuente: sitio de FIFA

Hoy Uruguay cerró su gran actuación en el mundial de fútbol en Sudáfrica, con un cuarto puesto luego de perder contra la selección alemana.

Sin ánimo de subirme al tren exitista luego de la gran actuación de la Celeste, deseo hacer público este sentimiento de orgullo por la entrega, por la pasión demostrada, por la unión del grupo, por la humildad y la nobleza de las declaraciones ante la prensa, tanto luego de ganar como luego de perder. También tengo un sentimiendo de que este logro no fue por suerte o de regalo; el sentimiento de que luego de ver al plantel luchar hasta el último minuto -literalmente-, un lugar mejor era también bien merecido.

Sin dudas el temple y las ganas esta selección es un ejemplo a seguir, ese sentimiendo de que ya no es regla que el más chico no puede llegar a cosas grandes, de que por ser uruguayos no podemos estar entre los mejores del mundo y destacarnos en algo. Es responsabilidad de nosotros dejarnos contagiarnos por esta actitud, y tomar este mundial como ejemplo para animarnos a llegar a más.

Salud Celeste, ojalá que todas estas alegrías sean sólo el comienzo de una nueva etapa de nuestra historia en el fútbol mundial.

Etiquetado con: 1 Comentario