Konwertuj tablicę bajtów na liczbę całkowitą w Javie i odwrotnie

139

Chcę przechowywać niektóre dane w tablicach bajtów w Javie. Zasadniczo tylko liczby, które mogą zająć do 2 bajtów na liczbę.

Chciałbym wiedzieć, jak mogę przekonwertować liczbę całkowitą na 2-bajtową tablicę bajtów i odwrotnie. Znalazłem wiele rozwiązań googlujących, ale większość z nich nie wyjaśnia, co dzieje się w kodzie. Jest wiele zmiennych rzeczy, których tak naprawdę nie rozumiem, więc doceniłbym podstawowe wyjaśnienie.

Chris
źródło
4
Ile nie rozumiesz o bitowego przesunięcia? Wygląda na to, że tak naprawdę pytanie brzmi „co powoduje przesunięcie bitowe” bardziej niż konwersja na tablice bajtowe, tak naprawdę - jeśli naprawdę chcesz zrozumieć, jak przebiegałaby konwersja.
Jon Skeet
1
(Dla wyjaśnienia, nie przeszkadza mi żadne pytanie, ale warto wyjaśnić, na które pytanie naprawdę chcesz odpowiedzieć. Prawdopodobnie otrzymasz odpowiedź, która będzie dla ciebie bardziej przydatna.)
Jon Skeet
Okej, rozumiem! Dzięki za uwagę. Wiem, do czego służy przesunięcie bitowe, po prostu nie rozumiałem jeszcze, do czego służy przy konwersji tablic bajtowych.
Chris,
3
@prekageo i Jeff Mercado Dzięki za dwie odpowiedzi. prekageo dobrze wyjaśnił, jak to się robi, fajny link! To sprawia, że ​​jest to dla mnie dużo jaśniejsze. Rozwiązanie Jeffa Mercadosa rozwiązało problem, który miałem.
Chris,

Odpowiedzi:

230

Użyj klas znajdujących się w java.nioprzestrzeni nazw, w szczególności klasy ByteBuffer. Może wykonać całą pracę za Ciebie.

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
źródło
2
Czy to zbyt drogie, jeśli tablica bajtów zawiera tylko 1 lub 2 liczby całkowite? Nie jestem pewien co do kosztu budowy pliku ByteBuffer.
Meow Cat 2012
Jak często pracujesz z danymi binarnymi w 2-4 bajtowych fragmentach? Naprawdę? Rozsądna implementacja działałaby z nią w fragmentach BUFSIZ (zwykle 4kb) lub korzystała z innych bibliotek IO, które ukrywają ten szczegół. W ramach tej platformy znajduje się cała biblioteka, która jest przeznaczona do pracy z buforami danych. Robisz niedźwiedzią przysługę sobie i innym opiekunom swojego kodu, gdy wdrażasz typowe operacje bez dobrego powodu (czy to perf, czy inna krytyczna operacja). Te bufory są po prostu opakowaniami, które działają na tablicach, nic więcej.
Jeff Mercado
Jak możesz utworzyć instancję klasy abstrakcyjnej?
1
@JaveneCPPMcGowan W tej odpowiedzi nie ma bezpośredniej instancji. Jeśli masz na myśli metody fabryczne wrapi allocate, nie zwracają one wystąpienia klasy abstrakcyjnej ByteBuffer.
Marko Topolnik
Nie jest rozwiązaniem dla 3-bajtowego kroku. Możemy dostać Char, Short, Int. Przypuszczam, że mógłbym dopełnić do 4 bajtów i za każdym razem odrzucić czwarty, ale wolałbym tego nie robić.
Jan
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 );
}

Podczas pakowania bajtów ze znakiem w int, każdy bajt musi być zamaskowany, ponieważ jest rozszerzany do 32 bitów (a nie rozszerzany o zero) ze względu na arytmetyczną regułę promocji (opisaną w JLS, Conversions and Promotions).

Jest z tym związana interesująca łamigłówka opisana w Java Puzzlers („A Big Delight in Every Byte”) autorstwa Joshua Blocha i Neala Gaftera. Podczas porównywania wartości bajtu z wartością int, bajt jest rozszerzany ze znakiem do int, a następnie ta wartość jest porównywana z drugą wartością int

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

Należy zauważyć, że wszystkie typy liczbowe są podpisane w języku Java, z wyjątkiem typu char będącego 16-bitową liczbą całkowitą bez znaku.

Jarek Przygódzki
źródło
Myślę, że te & 0xFFsą niepotrzebne.
Ori Popowski,
11
@LeifEricson Uważam, że & 0xFFs są konieczne, ponieważ nakazuje JVM konwersję bajtu ze znakiem na liczbę całkowitą z ustawionymi tylko tymi bitami. W przeciwnym razie bajt -1 (0xFF) zamieni się w int -1 (0xFFFFFFFF). Mogę się mylić, a nawet jeśli jestem, to nie boli i wyjaśnia sprawy.
coderforlife
4
& 0xFF jest rzeczywiście obowiązkowe. 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 Właściwie nie. Przed wykonaniem & operacji na byte0xFF ( int), JVM rzuci byteto intz rozszerzeniem na 1 lub rozszerzeniem 0 zgodnie z początkowym bitem. W Javie nie ma bajtu bez znaku , bytesą one zawsze podpisane.
Nier
2
Kiedy parse int z tablicy bajtów, należy zwrócić uwagę na wielkość tablicy bajtów, jeśli jest większy niż 4 bajty, zgodnie z DOC ByteBuffer.getInt(): Reads the next four bytes at this buffer's current positiontylko pierwsze 4 bajty będą przetwarzane, która nie powinna być, co chcesz.
Bin
56

Możesz również użyć BigInteger dla bajtów o zmiennej długości. Możesz przekształcić go w długi, int lub krótki, w zależności od potrzeb.

new BigInteger(bytes).intValue();

lub do oznaczenia polaryzacji:

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

Aby odzyskać bajty, wystarczy:

new BigInteger(bytes).toByteArray()
Jamel Toms
źródło
1
Zauważ, że od 1.8, to intValueExactnieintValue
Abhijit Sarkar
5

Podstawowa implementacja wyglądałaby tak:

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]);
    }
}

Aby zrozumieć rzeczy, możesz przeczytać ten artykuł WP: http://en.wikipedia.org/wiki/Endianness

Zostanie wyświetlony powyższy kod źródłowy 34 12 78 56 bc 9a. Pierwsze 2 bajty ( 34 12) reprezentują pierwszą liczbę całkowitą itd. Powyższy kod źródłowy koduje liczby całkowite w formacie little endian.

prekageo
źródło
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
źródło
0

Ktoś z wymaganiem, w którym muszą czytać z bitów, powiedzmy, że musisz czytać tylko z 3 bitów, ale potrzebujesz liczby całkowitej ze znakiem, a następnie użyj następującego:

data is of type: java.util.BitSet

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

Magiczną liczbę 3można zastąpić liczbą używanych bitów (nie bajtów) .

Vishrant
źródło
0

Jak często, guawa ma to, czego potrzebujesz.

Aby przejść od tablicy bajtów do int Ints.fromBytesArray:, doc tutaj

Aby przejść od int do tablicy bajtów:, Ints.toByteArraydoc tutaj

jeremie
źródło
-7

Myślę, że to najlepszy tryb do przesyłania na int

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

najpierw zamień bajt na String

comb=B+"";

następnym krokiem jest zamiana na int

out= Integer.parseInt(comb);

ale bajt jest w wściekłości od -128 do 127 z tego powodu, myślę, że lepiej jest użyć wściekłości od 0 do 255 i wystarczy zrobić to:

out=out+256;
Anioł Salvador Ayala Ochoa
źródło
To jest źle. Rozważmy bajt 0x01. Twoja metoda zwróci 129, co jest błędne. 0x01 powinno wypisać liczbę całkowitą 1. Należy dodać 128 tylko wtedy, gdy liczba całkowita otrzymana z parseInt jest ujemna.
disklosr
Chodziło mi o to, że powinieneś dodać 256, a nie 128. Nie mogłem tego później edytować.
disklosr
zmieniony post, aby dodać 256, ponieważ może to być przydatne dla innych!
apmartin1991
To powoduje dużo rzutowania i tworzy nowe obiekty (pomyśl o zrobieniu tego na pętlach for), które mogą obniżyć wydajność, sprawdź metodę Integer.toString (), aby uzyskać wskazówki dotyczące parsowania liczb.
Marcos Vasconcelos
również, gdy publikujesz kod na stackoverflow, chodzi o to, aby kod pocztowy miał sens. Kod, który ma łatwy sens, musi mieć zrozumiałe identyfikatory. A w przypadku stackoverflow, zrozumiałe koniecznie oznacza po angielsku .
Mike Nakis