Translate

terça-feira, 20 de agosto de 2013

MemCopy - Copiando bytes de forma fácil

Não é muito incomum para o programador Java fazer cópias de trechos de números para um vetor de bytes de tamanhos: 8 bits, 16 bits, 24 bits, 32 bits ou 64 bits para gerar e: guardar o resultado em arquivo binário, enviar mensagem binária para um socket, porta serial ou porta paralela.

Nota: Em C isso é relativamente fácil de fazer por causa de sua vocação original baseada na manipulação de memória, por isso em C tem a função memcpy designada para isso é extremamente versátil.

Normalmente para se fazer isso o programador já converteu números decimais para inteiros: short, int, long ou BigDecimal com uma operação de multiplicação.

Alerta: A multiplicação convencional com operador "*" utilizado para ponto flutuante pode resultar em perda de precisão não só em Java, mas em qualquer linguagem sobre a plataforma Intel por constatação ao deparar com este mesmo problema em C ANSI, C++ e depois em Java sempre na plataforma Intel. Logo mais falarei de como resolver o problema num outro artigo. É importante deixar claro que a divisão com o operador "/" funciona perfeitamente.

A classe a seguir é uma síntese prática do memcpy do C, porém com algumas melhorias porque ela oferece:
  1. tamanhos de cópias pré-estabelecidas conforme as práticas mais comuns no dia-a-dia do programador;
  2. ainda para cada função já possui assinaturas capazes de lidar com os tipos inteiros mais comuns e;
  3. permite a reversão de bytes para casos específicos onde o destino I/O possui um ordem binária reversa a sua.
Vamos para a prática.

A classe MemCopy

import java.math.BigInteger;
import java.util.Arrays;

public class MemCopy {

public static byte copy8(byte[] value) {

byte result = 0;

if (value != null && value.length > 0) {
result = value[0];
}
return result;
}

public static byte copy8(long value) {

return (byte) (value & 0x00000000000000FF);

}

public static byte copy8(int value) {

return (byte) (value & 0x000000FF);

}

public static byte copy8(short value) {

return (byte) (value & 0x00FF);

}

public static byte[] copy16(byte[] value, boolean reverseBytes) {

byte [] result = new byte[2];

if (value == null || value.length < 2) {
return null;
}

if (reverseBytes) {
result[0] = value[0];
result[1] = value[1];
} else {
result[0] = value[1];
result[1] = value[0];
}
return result;
}
public static byte[] copy16(long value, boolean reverseBytes) {
return copy16((short) (value & 0x000000000000FFFFL), reverseBytes);
}

public static byte[] copy16(int value, boolean reverseBytes) {
return copy16((short) (value & 0x0000FFFF), reverseBytes);
}
public static byte[] copy16(short value, boolean reverseBytes) {

byte [] result = new byte[2];
byte [] aux = new byte[2];

aux[0] = (byte) (value & 0x00FF);
aux[1] = (byte) ((value >> 8) & 0x00FF) ;

if (reverseBytes) {
result[1] = aux[0]; 
result[0] = aux[1]; 
} else {
result = aux;
}

return result;

}

public static byte[] copy24(byte[] value, boolean reverseBytes) {

byte [] result = new byte[3];

if (value == null || value.length < 3) {
return null;
}

if (reverseBytes) {
result[0] = value[2];
result[1] = value[1];
result[2] = value[0];
} else {
result[0] = value[0];
result[1] = value[1];
result[2] = value[2];
}
return result;
}
public static byte[] copy24(long value, boolean reverseBytes) {

byte [] result = new byte[3];
byte [] aux = new byte[3];

aux[0] = (byte) (value & 0x000000FF);
aux[1] = (byte) ((value >> 8) & 0x000000FF) ;
aux[2] = (byte) ((value >> 16) & 0x000000FF) ;

if (reverseBytes) {
result[1] = aux[0]; 
result[0] = aux[1]; 
} else {
result = aux;
}

return result;

}

public static byte[] copy32(byte[] value, boolean reverseBytes) {

byte [] result = new byte[4];
if (value == null || value.length < 4) {
return null;
}

if (reverseBytes) {
result[0] = value[3];
result[1] = value[2];
result[2] = value[1];
result[3] = value[0];
} else {
result[0] = value[0];
result[1] = value[1];
result[2] = value[2];
result[3] = value[3];
}
return result;
}
public static byte[] copy32(long value, boolean reverseBytes) {
return copy32((int) (value & 0xFFFFFFFF), reverseBytes);
}

public static byte[] copy32(int value, boolean reverseBytes) {

byte [] result = new byte[4];
byte [] aux = new byte[4];

aux[0] = (byte) (value & 0x000000FF);
aux[1] = (byte) ((value >> 8) & 0x000000FF) ;
aux[2] = (byte) ((value >> 16) & 0x000000FF);
aux[3] = (byte) ((value >> 24) & 0x000000FF);

if (reverseBytes) {
result[3] = aux[0]; 
result[2] = aux[1]; 
result[1] = aux[2]; 
result[0] = aux[3]; 
} else {
result = aux;
}

return result;
}

public static byte[] copy64(byte[] value, boolean reverseBytes) {

byte [] result = new byte[8];

if (value == null || value.length < 8) {
return null;
}

if (reverseBytes) {
result[0] = value[7];
result[1] = value[6];
result[2] = value[5];
result[3] = value[4];
result[4] = value[3];
result[5] = value[2];
result[6] = value[1];
result[7] = value[0];
} else {
result[0] = value[0];
result[1] = value[1];
result[2] = value[2];
result[3] = value[3];
result[4] = value[4];
result[5] = value[5];
result[6] = value[6];
result[7] = value[7];
}
return result;
}
public static byte[] copy64(long value, boolean reverseBytes) {

byte [] result = new byte[8];
byte [] aux = new byte[8];

aux[0] = (byte) (value & 0x00000000000000FFL);
aux[1] = (byte) ((value >> 8) & 0x00000000000000FFL);
aux[2] = (byte) ((value >> 16) & 0x00000000000000FFL);
aux[3] = (byte) ((value >> 24) & 0x00000000000000FFL);
aux[4] = (byte) ((value >> 32) & 0x00000000000000FFL);
aux[5] = (byte) ((value >> 40) & 0x00000000000000FFL);
aux[6] = (byte) ((value >> 48) & 0x00000000000000FFL);
aux[7] = (byte) ((value >> 56) & 0x00000000000000FFL);

if (reverseBytes) {
result[7] = aux[0]; 
result[6] = aux[1]; 
result[5] = aux[2]; 
result[4] = aux[3]; 
result[3] = aux[4]; 
result[2] = aux[5]; 
result[1] = aux[6]; 
result[0] = aux[7]; 
} else {
result = aux;
}

return result;

}

public static byte[] copy64(BigInteger value, boolean reverseBytes) {

if (value == null)
return null;

byte [] aux = value.toByteArray();
if (aux == null || aux.length == 0)
return null;

byte [] result = new byte[8];

Arrays.fill(result, (byte) 0);

if (aux.length < 8) {
System.arraycopy(aux, 0, result, result.length-aux.length, aux.length);
} else {
System.arraycopy(aux, 0, result, 0, result.length);
}

aux = new byte[8];
System.arraycopy(result, 0, aux, 0, aux.length);
if (!reverseBytes) {
result[7] = aux[0]; 
result[6] = aux[1]; 
result[5] = aux[2]; 
result[4] = aux[3]; 
result[3] = aux[4]; 
result[2] = aux[5]; 
result[1] = aux[6]; 
result[0] = aux[7]; 
} else {
result = aux;
}

return result;

}
}

Teste da classe

O teste da classe requer as funções de conversão de array de bytes para BigInteger, porque é o maior tipo entre os testes e isso confere a capacidade de realizar os testes rápidos com todos os tamanhos de cópias, e ainda requer a função para converter array de bytes para hexadecimal para comprovar os testes.

import java.math.BigInteger;

public class TestMemCopy {

public static BigInteger byteArrayToBigInteger(byte[] b, boolean reverseBytes) {

if (b == null || b.length == 0) {
return null;
}

byte aux[] = new byte[b.length];

if (!reverseBytes) {
for (int i = 0; i < b.length; i++) {
aux[aux.length-1-i] = b[i];
}
} else {
System.arraycopy(b, 0, aux, 0, b.length);
}

return new BigInteger(aux);

}

public static String byteArrayToHex(byte[] bytes, boolean useSpace) {

StringBuffer retorno = new StringBuffer();;

if (bytes==null || bytes.length==0) {
return retorno.toString();
}

// Com espaço
if (useSpace) {

for (int i=0; i<bytes.length; i++) {
byte valor = bytes[i];
int d1 = valor & 0xF;
d1 += (d1 < 10) ? 48 : 55;
int d2 = (valor & 0xF0) >> 4;
d2 += (d2 < 10) ? 48 : 55;
retorno.append((char) d2);
retorno.append((char) d1);
retorno.append(" ");
}

// Sem espaço
} else {

for (int i=0; i<bytes.length; i++) {
byte valor = bytes[i];
int d1 = valor & 0xF;
d1 += (d1 < 10) ? 48 : 55;
int d2 = (valor & 0xF0) >> 4;
d2 += (d2 < 10) ? 48 : 55;
retorno.append((char) d2);
retorno.append((char) d1);
}

}

return retorno.toString();

}
public static void main(String[] args) {

/*******************************
 * Copiando 1 byte de short
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = {0};
short decoded = 0;

// Teste short
short input = (short) 102;
encoded[0] = MemCopy.copy8(input);
decoded = (short) (encoded[0] & 0xFF);

System.out.println("Copiando 1 byte de short");
System.out.println(String.format("Original (short): %d, Encoding (Hex): %s, Decoding (short): %d\n", input, byteArrayToHex(encoded, true) ,decoded));
}

/*******************************
 * Copiando 2 bytes de short - Sem reversão de bytes
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = null;
BigInteger decoded = null;

// Teste short
short input = (short) 32767;
encoded = MemCopy.copy16(input, false);
decoded = byteArrayToBigInteger(encoded, false);

System.out.println("Copiando 2 bytes de short - Sem reversão de bytes");
System.out.println(String.format("Original (short): %d, Encoding (Hex): %s, Decoding (BigInteger): %d\n", input, byteArrayToHex(encoded, true) ,decoded));
}

/*******************************
 * Copiando 2 bytes de short - Com reversão de bytes
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = null;
BigInteger decoded = null;

// Teste short
short input = (short) 23761;
encoded = MemCopy.copy16(input, true);
decoded = byteArrayToBigInteger(encoded, true);

System.out.println("Copiando 2 bytes de short - Com reversão de bytes");
System.out.println(String.format("Original (short): %d, Encoding (Hex): %s, Decoding (BigInteger): %d\n", input, byteArrayToHex(encoded, true) ,decoded));
}

/*******************************
 * Copiando 4 bytes de int - Sem reversão de bytes
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = {0, 0};
BigInteger decoded = null;

// Teste short
int input = 2147483647;
encoded = MemCopy.copy32(input, false);
decoded = byteArrayToBigInteger(encoded, false);

System.out.println("Copiando 4 bytes de int - Sem reversão de bytes");
System.out.println(String.format("Original (int): %d, Encoding (Hex): %s, Decoding (BigInteger): %d\n", input, byteArrayToHex(encoded, true) ,decoded));
}

/*******************************
 * Copiando 4 bytes de int - Com reversão de bytes
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = null;
BigInteger decoded = null;

// Teste short
int input = 1157483800;
encoded = MemCopy.copy32(input, true);
decoded = byteArrayToBigInteger(encoded, true);

System.out.println("Copiando 4 bytes de int - Com reversão de bytes");
System.out.println(String.format("Original (int): %d, Encoding (Hex): %s, Decoding (BigInteger): %d\n", input, byteArrayToHex(encoded, true) ,decoded));

}

/*******************************
 * Copiando 8 bytes de long - Sem reversão de bytes
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = {0, 0};
BigInteger decoded = null;

// Teste short
long input =  922337203685478L; 
encoded = MemCopy.copy64(input, false);
decoded = byteArrayToBigInteger(encoded, false);

System.out.println("Copiando 8 bytes de long - Sem reversão de bytes");
System.out.println(String.format("Original (long): %d, Encoding (Hex): %s, Decoding (BigInteger): %d\n", input, byteArrayToHex(encoded, true) ,decoded));
}

/*******************************
 * Copiando 8 bytes de long - Com reversão de bytes
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = null;
BigInteger decoded = null;

// Teste short
long input =  722337203685478L; 
encoded = MemCopy.copy64(input, true);
decoded = byteArrayToBigInteger(encoded, true);

System.out.println("Copiando 8 bytes de long - Com reversão de bytes");
System.out.println(String.format("Original (long): %d, Encoding (Hex): %s, Decoding (BigInteger): %d\n", input, byteArrayToHex(encoded, true) ,decoded));

}

/*******************************
 * Copiando 8 bytes de long - Com reversão de bytes
 ******************************/
{
// Variáveis para realizar os testes
byte encoded[] = null;
BigInteger decoded = null;

// Teste short
long input =  722337203685478L; 
encoded = MemCopy.copy64(input, true);
decoded = byteArrayToBigInteger(encoded, true);

System.out.println("Copiando 8 bytes de long - Com reversão de bytes");
System.out.println(String.format("Original (long): %d, Encoding (Hex): %s, Decoding (BigInteger): %d\n", input, byteArrayToHex(encoded, true) ,decoded));

}

}

}

Resultado

Copiando 1 byte de short
Original (short): 102, Encoding (Hex): 66 , Decoding (short): 102

Copiando 2 bytes de short - Sem reversão de bytes
Original (short): 32767, Encoding (Hex): FF 7F , Decoding (BigInteger): 32767

Copiando 2 bytes de short - Com reversão de bytes
Original (short): 23761, Encoding (Hex): 5C D1 , Decoding (BigInteger): 23761

Copiando 4 bytes de int - Sem reversão de bytes
Original (int): 2147483647, Encoding (Hex): FF FF FF 7F , Decoding (BigInteger): 2147483647

Copiando 4 bytes de int - Com reversão de bytes
Original (int): 1157483800, Encoding (Hex): 44 FD CD 18 , Decoding (BigInteger): 1157483800

Copiando 8 bytes de long - Sem reversão de bytes
Original (long): 922337203685478, Encoding (Hex): 66 88 63 5D DC 46 03 00 , Decoding (BigInteger): 922337203685478

Copiando 8 bytes de long - Com reversão de bytes
Original (long): 722337203685478, Encoding (Hex): 00 02 90 F6 3C 6F 08 66 , Decoding (BigInteger): 722337203685478


Conforme descrito no alerta, no próximo artigo vamos discutir a respeito da multiplicação de flutuantes e como evitar de vez a perda de precisão nesta operação.

Até mais.





Nenhum comentário:

Postar um comentário