Konwersja między tablicami bajtowymi a łańcuchami szesnastkowymi w Javie
Przegląd
W tym poradniku przyjrzymy się różnym sposobom konwersji tablicy bajtowej na łańcuch szesnastkowy i odwrotnie.
Zrozumiemy również mechanizm konwersji i napiszemy naszą implementację, aby to osiągnąć.
Konwersja między bajtami i szesnastkami
Po pierwsze, spójrzmy na logikę konwersji między bajtami i liczbami szesnastkowymi.
2.1. Konwersja bajtów na liczby szesnastkowe
Bajty są 8-bitowymi podpisanymi liczbami całkowitymi w Javie. Dlatego musimy przekonwertować każdy 4-bitowy segment na heksadecymalny osobno i połączyć je. W konsekwencji, po konwersji otrzymamy dwa znaki szesnastkowe.
Na przykład, możemy zapisać 45 jako 0010 1101 w systemie binarnym, a odpowiednikiem szesnastkowym będzie „2d”:
0010 = 2 (base 10) = 2 (base 16)1101 = 13 (base 10) = d (base 16)Therefore: 45 = 0010 1101 = 0x2d
Zaimplementujmy tę prostą logikę w Javie:
public String byteToHex(byte num) { char hexDigits = new char; hexDigits = Character.forDigit((num >> 4) & 0xF, 16); hexDigits = Character.forDigit((num & 0xF), 16); return new String(hexDigits);}
Zrozummy teraz powyższy kod, analizując każdą operację. Po pierwsze, utworzyliśmy tablicę char o długości 2 do przechowywania danych wyjściowych:
char hexDigits = new char;
Następnie, wyizolowaliśmy bity wyższego rzędu poprzez przesunięcie w prawo 4 bitów. A następnie zastosowaliśmy maskę, aby wyizolować 4 bity niższego rzędu. Maskowanie jest wymagane, ponieważ liczby ujemne są wewnętrznie reprezentowane jako dwa dopełnienia liczby dodatniej:
hexDigits = Character.forDigit((num >> 4) & 0xF, 16);
Potem konwertujemy pozostałe 4 bity na szesnastkowy:
hexDigits = Character.forDigit((num & 0xF), 16);
Na koniec tworzymy obiekt String z tablicy char. A następnie zwracamy ten obiekt jako przekonwertowaną tablicę szesnastkową.
Teraz zrozumiemy, jak to będzie działać dla ujemnego bajtu -4:
hexDigits:1111 1100 >> 4 = 1111 1111 1111 1111 1111 1111 1111 11111111 1111 1111 1111 1111 1111 1111 1111 & 0xF = 0000 0000 0000 0000 0000 0000 0000 1111 = 0xfhexDigits:1111 1100 & 0xF = 0000 1100 = 0xcTherefore: -4 (base 10) = 1111 1100 (base 2) = fc (base 16)
Warto również zauważyć, że metoda Character.forDigit() zawsze zwraca małe litery.
2.2. Konwersja cyfry szesnastkowej na bajt
Teraz przekonwertujmy cyfrę szesnastkową na bajt. Jak wiemy, bajt zawiera 8 bitów. Dlatego potrzebujemy dwóch cyfr szesnastkowych, aby utworzyć jeden bajt.
Po pierwsze, przekonwertujemy każdą cyfrę szesnastkową na binarny odpowiednik osobno.
A następnie musimy połączyć dwa czterobitowe segmenty, aby uzyskać ekwiwalent bajtu:
Hexadecimal: 2d2 = 0010 (base 2)d = 1101 (base 2)Therefore: 2d = 0010 1101 (base 2) = 45
Teraz napiszmy tę operację w Javie:
public byte hexToByte(String hexString) { int firstDigit = toDigit(hexString.charAt(0)); int secondDigit = toDigit(hexString.charAt(1)); return (byte) ((firstDigit << 4) + secondDigit);}private int toDigit(char hexChar) { int digit = Character.digit(hexChar, 16); if(digit == -1) { throw new IllegalArgumentException( "Invalid Hexadecimal Character: "+ hexChar); } return digit;}
Zrozummy to, wykonując jedną operację na raz.
Po pierwsze, przekonwertowaliśmy znaki szesnastkowe na liczby całkowite:
int firstDigit = toDigit(hexString.charAt(0));int secondDigit = toDigit(hexString.charAt(1));
Następnie przesunęliśmy najbardziej znaczącą cyfrę o 4 bity w lewo. W rezultacie, reprezentacja binarna ma zera na czterech najmniej znaczących bitach.
Wtedy dodaliśmy do niej najmniej znaczącą cyfrę:
return (byte) ((firstDigit << 4) + secondDigit);
Przyjrzyjrzyjmy się teraz bliżej metodzie toDigit(). Do konwersji wykorzystujemy metodę Character.digit(). Jeśli wartość znaku przekazana do tej metody nie jest poprawną cyfrą w podanym radixie, zwracane jest -1.
Weryfikujemy wartość zwracaną i rzucamy wyjątek, jeśli przekazana została niepoprawna wartość.
Konwersja między tablicami bajtów a łańcuchami szesnastkowymi
W tym momencie wiemy, jak przekonwertować bajt na szesnastkowy i odwrotnie. Skalujmy ten algorytm i przekonwertujmy tablicę bajtów do/z szesnastkowego ciągu znaków.
3.1. Tablica bajtów do łańcucha szesnastkowego
Musimy wykonać pętlę przez tablicę i wygenerować parę szesnastkową dla każdego bajtu:
public String encodeHexString(byte byteArray) { StringBuffer hexStringBuffer = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { hexStringBuffer.append(byteToHex(byteArray)); } return hexStringBuffer.toString();}
Jak już wiemy, wyjście zawsze będzie pisane małymi literami.
3.2. Szesnastkowy String do Byte Array
Po pierwsze, musimy sprawdzić czy długość szesnastkowego Stringa jest liczbą parzystą. Dzieje się tak, ponieważ ciąg szesnastkowy o nieparzystej długości będzie miał nieprawidłową reprezentację bajtową.
Teraz wykonamy iterację po tablicy i przekonwertujemy każdą parę szesnastkową na bajt:
public byte decodeHexString(String hexString) { if (hexString.length() % 2 == 1) { throw new IllegalArgumentException( "Invalid hexadecimal String supplied."); } byte bytes = new byte; for (int i = 0; i < hexString.length(); i += 2) { bytes = hexToByte(hexString.substring(i, i + 2)); } return bytes;}
Użycie klasy BigInteger
Możemy stworzyć obiekt typu BigInteger przekazując signum i tablicę bajtów.
Teraz możemy wygenerować ciąg szesnastkowy za pomocą statycznej metody format zdefiniowanej w klasie String:
public String encodeUsingBigIntegerStringFormat(byte bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return String.format( "%0" + (bytes.length << 1) + "x", bigInteger);}
Podany format wygeneruje ciąg szesnastkowy z dopiskiem zero, z małymi literami. Możemy również wygenerować duże litery zastępując „x” przez „X”.
Alternatywnie, mogliśmy użyć metody toString() z klasy BigInteger. Subtelną różnicą użycia metody toString() jest to, że wyjście nie jest wypełnione zerami:
public String encodeUsingBigIntegerToString(byte bytes) { BigInteger bigInteger = new BigInteger(1, bytes); return bigInteger.toString(16);}
Przyjrzyjrzyjmy się teraz szesnastkowej konwersji String do Byte Array:
public byte decodeUsingBigInteger(String hexString) { byte byteArray = new BigInteger(hexString, 16) .toByteArray(); if (byteArray == 0) { byte output = new byte; System.arraycopy( byteArray, 1, output, 0, output.length); return output; } return byteArray;}
Metoda toByteArray() produkuje dodatkowy bit znaku. Napisaliśmy specjalny kod do obsługi tego dodatkowego bitu.
Więc, powinniśmy być świadomi tych szczegółów przed użyciem klasy BigInteger do konwersji.
Używanie klasy DataTypeConverter
Klasa DataTypeConverter jest dostarczana z biblioteką JAXB. Jest ona częścią biblioteki standardowej do Javy 8. Począwszy od Javy 9, musimy dodać moduł java.xml.bind do runtime’u w sposób jawny.
Przyjrzyjrzyjmy się implementacji z wykorzystaniem klasy DataTypeConverter:
public String encodeUsingDataTypeConverter(byte bytes) { return DatatypeConverter.printHexBinary(bytes);}public byte decodeUsingDataTypeConverter(String hexString) { return DatatypeConverter.parseHexBinary(hexString);}
Jak widać powyżej, użycie klasy DataTypeConverter jest bardzo wygodne. Dane wyjściowe metody printHexBinary() są zawsze pisane wielkimi literami. Klasa ta dostarcza zestaw metod print i parse do konwersji typów danych.
Przed wybraniem tego podejścia, musimy się upewnić, że klasa będzie dostępna w czasie wykonywania.
Użycie Apache’s Commons-Codec Library
Możemy użyć klasy Hex dostarczonej wraz z biblioteką Apache commons-codec:
public String encodeUsingApacheCommons(byte bytes) throws DecoderException { return Hex.encodeHexString(bytes);}public byte decodeUsingApacheCommons(String hexString) throws DecoderException { return Hex.decodeHex(hexString);}
Wyjście encodeHexString jest zawsze pisane małymi literami.
Używanie biblioteki Google Guava
Przyjrzyjrzyjmy się jak klasa BaseEncoding może być użyta do kodowania i dekodowania tablicy bajtów do szesnastkowego Stringa:
public String encodeUsingGuava(byte bytes) { return BaseEncoding.base16().encode(bytes);}public byte decodeUsingGuava(String hexString) { return BaseEncoding.base16() .decode(hexString.toUpperCase());}
BaseEncoding domyślnie koduje i dekoduje używając wielkich liter. Jeśli potrzebujemy użyć małych liter, należy utworzyć nową instancję kodowania za pomocą statycznej metody lowercase.
Podsumowanie
W tym artykule poznaliśmy algorytm konwersji pomiędzy tablicą bajtów a szesnastkowym Stringiem. Omówiliśmy również różne metody kodowania tablicy bajtów do łańcucha szesnastkowego i vice versa.
Nie zaleca się dodawania biblioteki tylko po to, aby korzystać z kilku metod użytkowych. Dlatego, jeśli nie korzystamy jeszcze z zewnętrznych bibliotek, powinniśmy skorzystać z omawianego algorytmu. Klasa DataTypeConverter jest kolejnym sposobem na kodowanie/dekodowanie pomiędzy różnymi typami danych.