Jest sobie protokół komunikacyjny ktorego warstwa fizyczna nie ma
znaczenia. Jest silnie zorientowany na unsigned char z C - od sum
kontrolnych CRC po bity flag w bajtach.
Czym mogę go oficjalnie obrabiać w Javie? Mam na mysli jaką wewnatrzną
reprezentację danych wybrać. Chodzi o czytelność - aby ktos zerkając w
kod nie widział udziwnień tylko rozsądne typy danych. RAM wolę
oszczędzać a i prędkośc było by miło mieć rozsądną.
Na razie zerkając w pare projektów widze głównie Stringa w tym
zastosowaniu. Czy słusznie? Zależy mi na swobonym manipulowaniu
zawartością bajtów tego protokołu w sposób czytelny i pokazujący
intencje. Ogolnie pytanie brzmi: jak elegancko obrabiac unsigned w Javie?
> Ogolnie pytanie brzmi: jak elegancko obrabiac unsigned w Javie?
byte[]
Całe słowa można obrabiać przy użyciu dowolnego typu całkowitego (int,
long), bo w Javie mają one ustalony format. Operacje bitowe też są.
Prawdopodobnie będziesz chciał użyć zarówno byte[] jak i typów
liczbowych, do różnych rzeczy.
String jako nośnik surowych danych nic nie wnosi.
BTW - byte[] występuje tu i ówdzie w API I/O (w szczególności w
java.nio w klasach obsługujących bufory), więc choćby już w tym
kontekście jest najbardziej naturalnym wyborem.
--
Maciej Sobczak
YAMI4 Tutorial:
http://www.youtube.com/view_play_list?p=3DEB30D174054E94
> byte[]
Hmmm owszem, byte, ale byte jest signed. To przeszkadza. Je�li bajt
zawiera liczb� unisgned 213 to zaczyna by� problem z g�upim napisaniem
instrukcji if ( b == 213 ). Zaczyna sie kombinowanie, zaciemnianie kodu
konwersjami.
> >>Ogolnie pytanie brzmi: jak elegancko obrabiac unsigned w Javie?
> > byte[]
>
> Hmmm owszem, byte, ale byte jest signed. To przeszkadza.
To nie ma żadnego znaczenia. W ogóle jest błędem myśleć o byte jak o
liczbie mającej jakiś zakres - to jest tylko kupka bitów. Można z
takich kupek zrobić większy obiekt albo wartość jakiegoś właściwego
typu, która to wartość będzie miała takie cechy jak zakres czy znak,
ale w odniesieniu do byte te pojęcia są złe.
> Je li bajt
> zawiera liczb unisgned 213 to zaczyna by problem z g upim napisaniem
> instrukcji if ( b == 213 ).
Bo to faktycznie jest głupia instrukcja.
Lepsza byłaby:
if (b == 0xd5)
Lepsza, bo odwraca uwagę czytelnika od liczbowego aspektu tego co tam
jest. Łatwiej też interpretować poszczególne bity.
> Zaczyna sie kombinowanie, zaciemnianie kodu
A jeszcze lepsze byłoby użycie nazwanej stałej.
Konwersje będą i tak potrzebne do obróbki (składania i rozkładania)
takich danych a problem zaciemnienia kodu rozwiązujemy za pomocą
enkapsulacji i pomocniczych funkcji.
Popieram kolegę. W tym wypadku - tylko byte[], chyba że to jest
faktycznie protokół tekstowy, a nie teksto-podobny.
String to ciąg znaków unicode, Java char to znak unicode, a C char to
jest nic innego jak 8 bitów.
Otóż nie musi to być 8 bitów - możesz znaleźć masę flame'ów na
pl.comp.lang.c dokładnie o tym :)
JD
Taaa...
> Lepsza, bo odwraca uwagďż˝ czytelnika od liczbowego aspektu tego co tam
> jest. �atwiej te� interpretowa� poszczeg�lne bity.
Ale to mi dopiero kod zaciemni�e�, skoro ja musz� por�wna� wla�nie z 213
dec bo akurat mam to bardziej czytelne z jakiejs przyczyny.
Z resztďż˝:
byte b = (byte) 0xd5;
if ( b == 0xd5 ) {
System.out.print( "dziala" );
}
Chyba jednak nie. Musze mie� if ( b == (byte) 0xd5 ). �atwo o tym
zapomnie� pisz�c kod bez s�owa sprzeciwu ze strony kompilatora. Dlatego
pytam o rozwi�zanie lepsze.
Jesli ktoďż˝ mi wmawia ze zakres typu byte nie ma znaczenia to ja
przepraszam, ale ma zasadnicze co wida� powy�ej. Mam do wyboru wpakowa�
to w co� wi�kszego albo stosowa� jakie� UnsignedToU2Byte i
U2ByteToUnsigned gdzie popadnie (o czym i tak zapomnďż˝ w pewnym momencie).
> Konwersje b�d� i tak potrzebne do obr�bki (sk�adania i rozk�adania)
> takich danych a problem zaciemnienia kodu rozwi�zujemy za pomoc�
> enkapsulacji i pomocniczych funkcji.
No wi�c w�a�nie o to pytam. Czy java posiada co� lepszego od
niebezpiecznego byte[] co jednoznacznie pokazuje intencje programisty
(przetwarzanie tablicy unsigned bajt�w), czy musz� szyc sobie zestaw
abstrakcji na ten typ.
Maciejowi Sobczakowi chodziło zapewne o to, że takie if (b == 0xd5) i
tak jest zaciemnione wystarczająco mocno. Równie dobrze możesz zapisać
if (b == (byte) 213) - zadziała, ale nadal nic to nie znaczy.
> Jesli ktoś mi wmawia ze zakres typu byte nie ma znaczenia to ja
> przepraszam, ale ma zasadnicze co widać powyżej. Mam do wyboru wpakować
> to w coś większego albo stosować jakieś UnsignedToU2Byte i
> U2ByteToUnsigned gdzie popadnie (o czym i tak zapomnę w pewnym momencie).
Po prostu - traktujesz byte jako bajt, nie jako liczbę. Tak samo
byte[] traktowane jest w Javie jako ciąg bajtów. To, jaką one mają
reprezentacje liczbową nie ma znaczenia, przecież to są surowe dane, a
nie liczby ze znakiem czy bez.
> > Konwersje będą i tak potrzebne do obróbki (składania i rozkładania)
> > takich danych a problem zaciemnienia kodu rozwiązujemy za pomocą
> > enkapsulacji i pomocniczych funkcji.
Maciej ma tutaj rację.
Skoro jednak tak bardzo zależy ci na tych if'ach, to zrób tak:
private static final byte ZNACZNIK_XYZ = (byte) 213;
[...]
if (b == ZNACZNIK_XYZ)
Teraz czytając kod mamy przynajmniej _blade_ pojęcie o tym o co w tym
warunku może chodzić.
> No więc właśnie o to pytam. Czy java posiada coś lepszego od
> niebezpiecznego byte[] co jednoznacznie pokazuje intencje programisty
> (przetwarzanie tablicy unsigned bajtów), czy muszę szyc sobie zestaw
> abstrakcji na ten typ.
Przejrzyj sobie Java IO API tak ogólnie. Jeśli chodzi o reprezentacje
danych i strumieni to są dwie opcje: albo strumienie są znakowe i
operują na char, char[] i String (Reader/Writer), albo mamy do
czynienia ze strumieniami lub danymi binarnymi, wtedy operuje się na
byte[] (InputStream/OutputStream). Trzecia kategoria to klasy
tłumaczące z bajtów na znaki i na odwrót - wtedy z kombinacji byte[] +
charset dostajemy char lub String i analogicznie, podając String i
charset dostajemy byte[].
Tak więc podsumowując: bajt to byte. Paczka danych to byte[]. Inaczej
nie próbuj, będziesz miał pod wiatr. Jeśli natomiast chcesz sobie
podejrzeć jaka liczba z zakresu od 0-255 kryje się pod zmienną typu
byte, zrób tak:
byte[] buf = ...
for (byte b : buf) {
System.out.println(0xFF & b);
// albo
int value = 0xFF & b;
System.out.println(value);
}
Przykładowo:
byte b = (byte) 213;
System.out.println(b);
System.out.println(0xFF & b);
Wynik:
-43
213
Jak to działa? Operacja powoduje rzutowanie byte (8 bitów) na int (32
bity), następnie wywalane są pierwsze 24 bity (0xFF == 0x000000FF),
tam akurat jest informacja o znaku i idzie ona do kosza (to tak w
uproszczeniu, rzeczywiście działa to trochę inaczej, ale to
nieistotne).
--
Witold Szczerba
Oczywi�cie...
...
static final byte SYSTEM_MODE13 = (byte) 0x7e;
static final byte USER_MODE1 = (byte) 0x7f;
static final byte USER_MODE2 = (byte) 0x80;
...
byte b = USER_MODE2;
if ( b >= USER_MODE1 ) {
System.out.print( "User mode!" );
}
Ups.
> if (b == ZNACZNIK_XYZ)
> Teraz czytaj�c kod mamy przynajmniej _blade_ poj�cie o tym o co w tym
> warunku mo�e chodzi�.
Przy > kompilator nie ma bladego pojecia. Wole zeby kompilator mial w
pierwszej kolejno�ci poj�cie co chce rozbic na tych _unsigned_ bajtach.
> Tak wi�c podsumowuj�c: bajt to byte.
Signed. Z wszelkimi niespodziankami jakie to niesie.
> Jak to dzia�a? Operacja powoduje rzutowanie byte (8 bit�w) na int (32
> bity), nast�pnie wywalane s� pierwsze 24 bity (0xFF == 0x000000FF),
Ale to nie jest problem zebym tak robi�, chodzi o to jak mog� tego u�y�
nie jako samor�bk� tylko jako co� oficjalnego. Mielenie plik�w binarnych
nie jest czymďż˝ nowym i jakoďż˝ sobie ludzie radzďż˝. Nie chce wymyslaďż˝
kwadratowego ko�a je�li co� (typ, kontener, koncepcja) jest uznawane za
standard w tych zastosowaniach. A program�w w Javie widzia�em za ma�o by
m�c sie wypowiada� czy dzia�aj�ce rozwi�zanie jest rownie� czytelnym
rozwi�zaniem.
Jesli uznamy ze byte[] jest standardem to niestety i tak musze go
opakowac w cos robi�cego konwersje na szersze typy (z powodow podanych
na poczatku tego postu). Mia�em nadzieje ze co� takiego juz po prostu jest.
> if ( b >= USER_MODE1 ) {
> System.out.print( "User mode!" );
> }
>
> Ups.
Ups, bo dałeś się nabrać, że byte to taka-wicie-rozumicie-liczba. Już
pisałem, że przypisywanie temu typowi jakichkolwiek cech liczbowych
jest złe. Jest projektowo złe.
Gdyby projektanci języka mieli więcej jaj, to by zabronili porównań
arytmetycznych (i w ogóle jakichkolwiek operacji arytmetycznych poza
ekstrakcją i składaniem bitów) i wtedy byś tego błędu nie mógł
popełnić.
> Przy > kompilator nie ma bladego pojecia. Wole zeby kompilator mial w
> pierwszej kolejności pojęcie co chce rozbic na tych _unsigned_ bajtach.
To nie są "unsigned bajty". To są kupki bitów. Jeśli chcesz im
przypisać jakieś cechy arytmetyczne, to przekształć najpierw te kupki
bitów w coś, co te cechy ma w sposób naturalny. Np. *skonwertuj* byte
na int. Słowo "skonwertuj" jest tu bardzo istotne, bo wskazuje na
zmianę dzidziny - i dokładnie o zmianę dziedziny chodzi, bo w
dziedzinie kupek bitów operacje arytmetyczne nie mają sensu.
Powyższe może się wydawać herezją, ale spróbuj przyjąć, że typ byte:
1. to kupka bitów
2. wspiera operacje porównania, przypisania oraz konwersję do/z int
oraz long
3. wspiera operacje bitowe &, |, oraz &= i |=
4. można go zapisać lub odczytać przez różne API I/O lub przepuścić
przez wybrane kodowanie znaków (co w sumie można zaliczyć do I/O na
buforze pamięci)
Jeżeli ograniczysz się do tych 4 punktów, to wszystko będzie OK.
> Mielenie plików binarnych
> nie jest czymś nowym i jakoś sobie ludzie radzą.
Dokładnie. Napisałem przyzwoity serializator/deserializator pewnego
protokołu binarnego i ani razu nie natknąłem się na żadną
niespodziankę. 4 punkty powyżej.
> Jesli uznamy ze byte[] jest standardem to niestety i tak musze go
> opakowac w cos robiącego konwersje na szersze typy (z powodow podanych
> na poczatku tego postu). Miałem nadzieje ze coś takiego juz po prostu jest.
W java.nio w okolicach buforów znajdziesz gotowe konwertery pracujące
na całych słowach.
BTW, dla uściślenia - To, czy byte jest signed czy unsigned można
rozpatrywać jedynie w kontekście efektu jego konwersji na int lub
long, bo tylko wtedy jest to obserwowalna własność tego typu (nawet
wtedy jest nieistotna, bo konwersję zwykle łączy się z obcięciem przez
iloczyn z 0xFF). Natomiast w samej dziedzinie byte pojęcie znaku jest
bez sensu, nawet jeśli język to pojęcie wspiera. Dlatego z
projektowego punktu widzenia byte sam w sobie nie jest ani signed ani
unsigned.
Typ byte jest zdefiniowany jako calkowity typ liczbowy ktory moze
przyjmowac dokladnie zdefiniowane wartosci numeryczne. Oczywiscie mozesz
traktowac byte jako kupke bitow, tyle ze kazdy inny typ liczbowy tez
mozesz tak traktowac - zalezy od aktualnych potrzeb.
> 2. wspiera operacje porównania,
Prawdopobonbnie miales na mysli stosowanie operatorow rownosci (==, !=)
bo operatory porowaniania (wedlug standardu sa to <, <=, >, >=) ciezko
stosowac do kupki bitow nie nadajac im interpretacji liczbowej.
> oraz konwersję do/z int
> oraz long
Oraz konwersje do kazdego inego typu liczbowego. Czyli zachowuje sie jak
kazdy inny typ liczbowy.
> 3. wspiera operacje bitowe &, |,
Nie wspiera. byte zostanie najpierw skonwertowany do int lub long. W
rezultacie nie mozna napisac:
byte b1 = ...;
byte b2 = ...;
byte b3 = b1 | b2;
> oraz &= i |=
To prawda. To juz jest poprawne;
byte b1 = ...;
byte b2 = ...;
b2 |= b1;
> 4. można go zapisać lub odczytać przez różne API I/O
Prawda. Ale nie cala prawda: java.io.InputStream.read() - jedna z
podtawowych metod z IO zwraca unsigned byte jako int. Pewnie im zakesu
wartosci w byte zabraklo.
A odpowiadajac na pytanie Sebastiana co jest co jest uznawane za typowe
podejscie. Mysle ze mozna zobaczyc jak sun to robi:
return (((long)readBuffer[0] << 56) +
((long)(readBuffer[1] & 255) << 48) +
((long)(readBuffer[2] & 255) << 40) +
((long)(readBuffer[3] & 255) << 32) +
((long)(readBuffer[4] & 255) << 24) +
((readBuffer[5] & 255) << 16) +
((readBuffer[6] & 255) << 8) +
((readBuffer[7] & 255) << 0));
readBuffer to oczywiscie byte[]
Sam bym zrobil podobnie (pewnie bym tylko wszedzie dopisal rzutowanie na
longa) - to jest dla mnie duzo czytelniejsze niz opakowanie w klika klas
z ktorch kazda obowiazkowo musi implementowac co najmniej jeden interfejs.
Big endian czy little endian? Czy też pisząc "warstwa fizyczna nie ma znaczenia" ten problem już
masz rozwiązany?
http://en.wikipedia.org/wiki/Endianness
http://mindprod.com/jgloss/endian.html
Może to coś da:
http://mindprod.com/jgloss/unsigned.html
i jeszcze to:
http://java.sun.com/j2se/1.4.2/docs/api/java/nio/ByteBuffer.html
Fajny temat.
--
fv
> > 2. wspiera operacje porównania,
>
> Prawdopobonbnie miales na mysli stosowanie operatorow rownosci (==, !=)
Tak.
> > oraz konwersję do/z int
> > oraz long
>
> Oraz konwersje do kazdego inego typu liczbowego. Czyli zachowuje sie jak
> kazdy inny typ liczbowy.
Właśnie ma się nie zachowywać, bo z tego zachowania tylko
niespodzianki wychodzą. :-)
> > 3. wspiera operacje bitowe &, |,
>
> Nie wspiera. byte zostanie najpierw skonwertowany do int lub long.
Prawda - tu się rozpędziłem. Chociaż byłoby przyjemne, gdyby te
operacje były możliwe w typie byte. Tak czy inaczej chodzi o to, że
jeśli to jest kupka bitów, to jedyne operacje jakie mają sens to te,
które operują na bitach.
> > 4. można go zapisać lub odczytać przez różne API I/O
>
> Prawda. Ale nie cala prawda: java.io.InputStream.read() - jedna z
> podtawowych metod z IO zwraca unsigned byte jako int. Pewnie im zakesu
> wartosci w byte zabraklo.
Raczej im zabrakło możliwości zwracania dwóch wartości z funkcji albo
przekazywania flag przez referencję. Zwracanie byte jako int to
kiepska próba umieszczenia w jednym typie dwóch kompletnie różnych
informacji.
> A odpowiadajac na pytanie Sebastiana co jest co jest uznawane za typowe
> podejscie. Mysle ze mozna zobaczyc jak sun to robi:
>
> return (((long)readBuffer[0] << 56) +
> ((long)(readBuffer[1] & 255) << 48) +
> ((long)(readBuffer[2] & 255) << 40) +
> ((long)(readBuffer[3] & 255) << 32) +
> ((long)(readBuffer[4] & 255) << 24) +
> ((readBuffer[5] & 255) << 16) +
> ((readBuffer[6] & 255) << 8) +
> ((readBuffer[7] & 255) << 0));
No właśnie. To jest typowa sieczka w implementacji niemal każdego
serializatora. :-)
Nie ma to znaczenia kiedy rozmawiamy o typie unsigned char z C.
> Czy też pisząc "warstwa fizyczna nie ma znaczenia" ten problem już
> masz rozwiązany?
Mam na mysli, ze nie ma znaczenia czy to plik, bajty po socketach czy
RS232. Mam bajt 0-255 i chce go w sposób mozliwe najbardziej czytelny
obrabiać w Javie. Czytelny dla innych programistów.
Z ciekawości zapytam, bo może mi coś umknęło: dlaczego unsigned char miałby być wyjątkiem?
>> Czy też pisząc "warstwa fizyczna nie ma znaczenia" ten problem już
>> masz rozwiązany?
>
> Mam na mysli, ze nie ma znaczenia czy to plik, bajty po socketach czy
> RS232.
Zwłaszcza że plik może mieć dowolny byte-order a przesył przez sieć ma network-byte-order (taki jak
java).
> Mam bajt 0-255 i chce go w sposób mozliwe najbardziej czytelny
> obrabiać w Javie. Czytelny dla innych programistów.
Ja tylko zwróciłem uwagę, że być może ważniejsza od jasności kodu będzie jego formalna poprawność ;-)
--
fv
Ponieważ nie działa na nim arytmetyka tak, jak nakazuje logika unsigned
(co pokazałem pare postów wyżej). Nie jestem zwolennikiem swiętej wojny
między signed i unsigned, ale prosty test z > którego kompilator nie
wykrywa jest po prostu niebezpieczny. Dlatego wole zapytac jak to zrobic
poprawnie, nawet jeśli zagadnienie pozornie jest na poziomie gimnazjum.
> Zwłaszcza że plik może mieć dowolny byte-order a przesył przez sieć ma network-byte-order (taki jak
> java).
Byte order nie jest definiowany w sposób globalny dla jakiegoś mojego
protokołu. W tym co obrabiam mam zarówno pojednycze bajty jak rownież
słowa o różnej długości, w tym pola bitowe. Dlatego dyskusja o endianie
w moim przypadku nie ma sensu.
> Ja tylko zwróciłem uwagę, że być może ważniejsza od jasności kodu będzie jego formalna poprawność ;-)
Własnie chce mieć jedno i drugie. Używanie byte[] jest jasne, ale
niebezpieczne. Używanie b1 << 8 || ... jest brzydkie i ... też
niebezpieczne. Pozostaje opakować kontener na bajty jakimiś konwerterami.
Ale.. ja nie o tym. Zrozumiałem to tak, że Pan twierdzi jakoby unsigned char wyglądał tak samo
bitowo w pamięci, niezależnie od tego o jakiej maszynie (assemblerze) rozmawiamy.
> Nie jestem zwolennikiem swiętej wojny
> między signed i unsigned, ale prosty test z > którego kompilator nie
> wykrywa jest po prostu niebezpieczny. Dlatego wole zapytac jak to zrobic
> poprawnie, nawet jeśli zagadnienie pozornie jest na poziomie gimnazjum.
Nabieram obaw...
> słowa o różnej długości, w tym pola bitowe. Dlatego dyskusja o endianie
> w moim przypadku nie ma sensu.
.. że ...
(proszę mnie źle nie zrozumieć, staram się pomóc) .. nie do końca rozumie Pan problem endianess i
byte-order.
Krótko: samo wczytanie danych może być problemem - może nastąpić błąd interpretacji. Czyli jeśli w C
było:
if (zmienna & 2 == 2) { // znaczy się sprawdzamy czy liczba parzysta
}
to ten sam zapis w Javie (czy też analogiczny - ładniejszy) może dać inny wynik ze względu na
byte-order. Podstawowym powodem jest to, że java wymusza swój byte order (z punktu widzenia
użytkownika) a C stosuje ten, który ma assembler danej maszyny. Natomiast dane zupełnie od tego nie
zależą. Dane to strumień bitów (a nie bajtów!). Operacje (w tym czytanie z pliku) już są na bajtach
czy słowach. Stąd problem.
Przepraszam za samo podejrzenie, jeśli to co napisałem jest oczywiste oraz za wymądrzanie się.
--
fv
>
> Nabieram obaw...
>
>> słowa o różnej długości, w tym pola bitowe. Dlatego dyskusja o endianie
>> w moim przypadku nie ma sensu.
>
> .. że ...
>
> (proszę mnie źle nie zrozumieć, staram się pomóc) .. nie do końca rozumie
> Pan problem endianess i byte-order.
>
>
> Krótko: samo wczytanie danych może być problemem - może nastąpić błąd
> interpretacji. Czyli jeśli w C było:
>
> if (zmienna & 2 == 2) { // znaczy się sprawdzamy czy liczba parzysta
> }
>
> to ten sam zapis w Javie (czy też analogiczny - ładniejszy) może dać inny
> wynik ze względu na byte-order.
Chyba sie mylisz. Nie mowimy o:
http://en.wikipedia.org/wiki/Bit_numbering
tylko o byte-order.
--
Michal
Unsigned char wygląda tak samo dla _statystycznie wszyskich_ maszyn
współczesnych. Signed też.
> nie do końca rozumie Pan problem endianess i
> byte-order.
Rozumiem go wystarczająco dobrze. Kilkanaście lat praktyki w asm
procesorach little - big endian w różnych architekturach dają mi
wystarczające powody by mieć pewność.
> if (zmienna & 2 == 2) { // znaczy się sprawdzamy czy liczba parzysta
> }
> to ten sam zapis w Javie (czy też analogiczny - ładniejszy) może dać inny wynik ze względu na
> byte-order.
Byte order jest nieistotny jesli mowimy o unsigned char. A tym jest
wątek. Przypominam też, że mówimy o unsigned char z C co było jasno
wskazane w wątku.
> Podstawowym powodem jest to, że java wymusza swój byte order
Byte order jak sama nazwa wskazuje jest byte a nie bit. W ramach jednego
bajtu fizyczna kolejnośc bitów (oraz ich interpretacja) jest raczej
identyczna na wszelakich znanych mi procesorach. Ponadto - big/little
endian nie ma znaczenia w moim wypadku - kolejność bajtów, zakresy pól
bitowych, szerokości słów sa ściśle określone protokołem.
_DLATEGO_ pytam o obrabianie unsigned char z C żeby _ODŁĄCZYĆ_ się od
pojęcia little/big endian.
>
> if (zmienna & 2 == 2) { // znaczy się sprawdzamy czy liczba parzysta
> }
>
> to ten sam zapis w Javie (czy też analogiczny - ładniejszy) może dać inny
> wynik
Czyzby zmienna i stala "2" sa inaczej reprezentowane na tym samym
procesorze?
--
Michal
W takim razie jeszcze raz przepraszam ;-)
--
fv