Convertir un tableau d'octets en nombre entier en Java et vice versa

139

Je souhaite stocker des données dans des tableaux d'octets en Java. Fondamentalement, juste des nombres qui peuvent prendre jusqu'à 2 octets par nombre.

J'aimerais savoir comment je peux convertir un entier en un tableau d'octets de 2 octets et vice versa. J'ai trouvé beaucoup de solutions sur Google, mais la plupart n'expliquent pas ce qui se passe dans le code. Il y a beaucoup de choses changeantes que je ne comprends pas vraiment, alors j'apprécierais une explication de base.

Chris
la source
4
Combien ne comprenez - vous au sujet de décalage de bits? On dirait que la question est vraiment "qu'est-ce que le changement de bits fait" plus que la conversion en tableaux d'octets, vraiment - si vous voulez vraiment comprendre comment la conversion fonctionnerait.
Jon Skeet
1
(Juste pour clarifier, je suis d'accord avec l'une ou l'autre des questions, mais cela vaut la peine de préciser à quelle question vous voulez vraiment répondre. Vous obtiendrez probablement une réponse qui vous sera plus utile de cette façon.)
Jon Skeet
Ok, j'ai compris votre point! Merci pour la remarque. Je sais quel changement de bit est-ce que je ne comprenais pas encore à quoi il sert dans la conversion de tableaux d'octets.
Chris
3
@prekageo et Jeff Mercado Merci pour vos deux réponses. prekageo a donné une bonne explication de la façon dont cela est fait, joli lien! Cela me rend beaucoup plus clair. Et la solution Jeff Mercados a résolu le problème que j'avais.
Chris

Réponses:

230

Utilisez les classes trouvées dans l' java.nioespace de noms, en particulier le ByteBuffer. Il peut faire tout le travail pour vous.

byte[] arr = { 0x00, 0x01 };
ByteBuffer wrapped = ByteBuffer.wrap(arr); // big-endian by default
short num = wrapped.getShort(); // 1

ByteBuffer dbuf = ByteBuffer.allocate(2);
dbuf.putShort(num);
byte[] bytes = dbuf.array(); // { 0, 1 }
Jeff Mercado
la source
2
Est-ce trop cher si le tableau d'octets contient seulement 1 ou 2 entiers? Je ne suis pas sûr du coût de construction d'un ByteBuffer.
Meow Cat 2012
À quelle fréquence travaillez-vous avec des données binaires en blocs de 2 à 4 octets? Vraiment? Une implémentation sensée fonctionnerait avec elle dans des blocs BUFSIZ (généralement 4 Ko) ou utiliserait d'autres bibliothèques d'E / S qui masquent ce détail. Il existe une bibliothèque entière dans le cadre qui est dédiée à vous aider à travailler sur des tampons de données. Vous ne vous rendez pas service à vous-même et aux autres responsables de votre code lorsque vous implémentez des opérations courantes sans raison valable (que ce soit perf ou autre opération critique). Ces tampons ne sont que des wrappers qui fonctionnent sur des tableaux, rien de plus.
Jeff Mercado
Comment se fait-il que vous puissiez instancier une classe abstraite?
1
@JaveneCPPMcGowan Il n'y a pas d'instanciation directe présente dans cette réponse. Si vous voulez dire les méthodes de fabrique wrapet allocate, elles ne renvoient pas une instance de la classe abstraite ByteBuffer.
Marko Topolnik
Pas une solution pour un pas de 3 octets. Nous pouvons Char, Short, Int. Je suppose que je pourrais ajouter 4 octets et supprimer le 4e à chaque fois, mais je préfère ne pas le faire.
John
128
byte[] toByteArray(int value) {
     return  ByteBuffer.allocate(4).putInt(value).array();
}

byte[] toByteArray(int value) {
    return new byte[] { 
        (byte)(value >> 24),
        (byte)(value >> 16),
        (byte)(value >> 8),
        (byte)value };
}

int fromByteArray(byte[] bytes) {
     return ByteBuffer.wrap(bytes).getInt();
}
// packing an array of 4 bytes to an int, big endian, minimal parentheses
// operator precedence: <<, &, | 
// when operators of equal precedence (here bitwise OR) appear in the same expression, they are evaluated from left to right
int fromByteArray(byte[] bytes) {
     return bytes[0] << 24 | (bytes[1] & 0xFF) << 16 | (bytes[2] & 0xFF) << 8 | (bytes[3] & 0xFF);
}

// packing an array of 4 bytes to an int, big endian, clean code
int fromByteArray(byte[] bytes) {
     return ((bytes[0] & 0xFF) << 24) | 
            ((bytes[1] & 0xFF) << 16) | 
            ((bytes[2] & 0xFF) << 8 ) | 
            ((bytes[3] & 0xFF) << 0 );
}

Lors du regroupement d'octets signés dans un entier, chaque octet doit être masqué car il est étendu par signe à 32 bits (plutôt que zéro) en raison de la règle de promotion arithmétique (décrite dans JLS, Conversions et Promotions).

Il y a un casse-tête intéressant lié à cela décrit dans Java Puzzlers ("Un grand plaisir dans chaque octet") par Joshua Bloch et Neal Gafter. Lors de la comparaison d'une valeur d'octet à une valeur int, l'octet est étendu par signe à un int, puis cette valeur est comparée à l'autre int

byte[] bytes = (…)
if (bytes[0] == 0xFF) {
   // dead code, bytes[0] is in the range [-128,127] and thus never equal to 255
}

Notez que tous les types numériques sont signés en Java, à l'exception du fait que char est un type entier non signé 16 bits.

Jarek Przygódzki
la source
Je pense que les & 0xFFs ne sont pas nécessaires.
Ori Popowski
11
@LeifEricson Je crois que les & 0xFFs sont nécessaires car il indique à la JVM de convertir l'octet signé en un entier avec uniquement ces bits définis. Sinon, l'octet -1 (0xFF) se transforme en int -1 (0xFFFFFFFF). Je peux me tromper et même si je le suis, cela ne fait pas mal et rend les choses plus claires.
coderforlife
4
& 0xFF est effectivement obligatoire. byte b = 0; b |= 0x88; System.out.println(Integer.toString(b, 16)); //Output: -78 System.out.println(Integer.toString(b & 0xFF, 16)); //Output: 88
HBN
1
@ptntialunrlsd Pas vraiment. Avant d' effectuer et opération sur byteavec 0xFF ( int), JVM chassera l' byteà intavec 1 étendu ou 0 extension de selon le premier bit le premier. Il n'y a pas d'octet non signé en Java, les bytes sont toujours signés.
Nier
2
Lorsque Parse int du tableau d'octets, attention à la taille du tableau d'octets, si elle est supérieure à 4 octets, selon la doc de ByteBuffer.getInt(): Reads the next four bytes at this buffer's current position, seuls les 4 premiers octets sera analysée, ce qui ne devrait pas être ce que vous voulez.
Bin
57

Vous pouvez également utiliser BigInteger pour les octets de longueur variable. Vous pouvez le convertir en long, int ou short, selon vos besoins.

new BigInteger(bytes).intValue();

ou pour désigner la polarité:

new BigInteger(1, bytes).intValue();

Pour récupérer des octets simplement:

new BigInteger(bytes).toByteArray()
Jamel Toms
la source
1
Notez que depuis la 1.8, ce n'est intValueExactpasintValue
Abhijit Sarkar le
5

Une implémentation de base serait quelque chose comme ceci:

public class Test {
    public static void main(String[] args) {
        int[] input = new int[] { 0x1234, 0x5678, 0x9abc };
        byte[] output = new byte[input.length * 2];

        for (int i = 0, j = 0; i < input.length; i++, j+=2) {
            output[j] = (byte)(input[i] & 0xff);
            output[j+1] = (byte)((input[i] >> 8) & 0xff);
        }

        for (int i = 0; i < output.length; i++)
            System.out.format("%02x\n",output[i]);
    }
}

Afin de comprendre les choses, vous pouvez lire cet article WP: http://en.wikipedia.org/wiki/Endianness

Le code source ci-dessus sera généré 34 12 78 56 bc 9a. Les 2 premiers octets ( 34 12) représentent le premier entier, etc. Le code source ci-dessus encode les entiers au format little endian.

prekageo
la source
2
/** length should be less than 4 (for int) **/
public long byteToInt(byte[] bytes, int length) {
        int val = 0;
        if(length>4) throw new RuntimeException("Too big to fit in int");
        for (int i = 0; i < length; i++) {
            val=val<<8;
            val=val|(bytes[i] & 0xFF);
        }
        return val;
    }
Dhaval Rami
la source
0

Quelqu'un avec une exigence où ils doivent lire à partir de bits, disons que vous devez lire à partir de seulement 3 bits mais vous avez besoin d'un entier signé, puis utilisez ce qui suit:

data is of type: java.util.BitSet

new BigInteger(data.toByteArray).intValue() << 32 - 3 >> 32 - 3

Le nombre magique 3peut être remplacé par le nombre de bits (et non d'octets) que vous utilisez.

Vishrant
la source
0

Comme souvent, la goyave a ce dont vous avez besoin.

Pour passer du tableau d'octets à int:, Ints.fromBytesArraydoc ici

Pour passer du tableau int au tableau d'octets:, Ints.toByteArraydoc ici

Jeremie
la source
-7

je pense que c'est le meilleur mode de diffusion en int

   public int ByteToint(Byte B){
        String comb;
        int out=0;
        comb=B+"";
        salida= Integer.parseInt(comb);
        out=out+128;
        return out;
    }

premier octet de conversion en chaîne

comb=B+"";

la prochaine étape est de passer à un int

out= Integer.parseInt(comb);

mais l'octet est en rage de -128 à 127 pour cette raison, je pense qu'il vaut mieux utiliser rage 0 à 255 et il vous suffit de faire ceci:

out=out+256;
Angel Salvador Ayala Ochoa
la source
C'est faux. Considérez l'octet 0x01. Votre méthode affichera 129, ce qui est faux. 0x01 doit afficher l'entier 1. Vous ne devez ajouter 128 que si l'entier obtenu de parseInt est négatif.
disklosr
Je voulais dire que vous devriez ajouter 256 et non 128. Impossible de le modifier par la suite.
disklosr
a changé le post pour ajouter le 256 car cela peut être utile à d'autres!
apmartin1991
Cela fait beaucoup de transtypage et crée de nouveaux objets (pensez à le faire sur des boucles for) qui peuvent dégrader les performances, veuillez vérifier la méthode Integer.toString () pour obtenir des conseils sur la façon d'analyser les nombres.
Marcos Vasconcelos
De plus, lors de la publication de code sur stackoverflow, le but est de publier du code qui a facilement du sens. Un code qui a facilement du sens doit avoir des identifiants compréhensibles . Et sur stackoverflow, compréhensible signifie nécessairement en anglais .
Mike Nakis