Verständnisproblem bei Encodings

Studdi23

Grünschnabel
Hallo zusammen,

bei mir herrscht derzeit Konfusion in Sachen Java Encodings. Und zwar möchte ich das Eurosymbol in ein Byte Array umwandeln und erhalte je nachdem welche Funktion ich anwende verschiedene Resultate. Laut ASCII Tabelle (z.B.dieser hier http://www.torsten-horn.de/techdocs/ascii.htm) hat das € Zeichen auf Windows Systemen mit "Windows-1252" Encoding den Dezimalwert 128 bzw.Hexadezimalwert 80. Um zu verifizieren das diese Codierung bei mir Standard ist habe ich folgenden Java-Code benutzt:
HTML:
System.out.println(Charset.defaultCharset()); /*Ausgabe:  windows-1252*/
System.out.println(System.getProperty("file.encoding")); /*Ausgabe:  Cp1252*/
Zur Konvertierung von Strings in Byte Arrays habe ich zunächst die übliche String-Methode "getBytes()" getestet, die einmal mit und ohne Encoding Parameter genutzt werden kann. Da "Windows-1252" als Standard definiert ist sind die beiden folgenden Aufrufe equivalent:
Code:
byte[] bytes = "€".getBytes("windows-1252");
System.out.println(Arrays.toString(bytes)); /*Ausgabe:  [-128]*/
bytes = "€".getBytes();
System.out.println(Arrays.toString(bytes)); /*Ausgabe:   [-128]*/
Die Ausgabe ist verständlich. Da ein Byte nur aus 8 bit besteht und in Java das 8te bit für Vorzeichen reserviert ist (also den Bereich -128 bis 127 abdeckt) ist -128 die 8 bit Darstellung der Zahl 128. Dementsprechen klappt auch die Rückumwandlung:
Code:
System.out.println(new String(bytes)); /*Ausgabe:   €*/
Soweit so gut. Nun habe ich noch eine andere Variante im Internet gefunden, die das selbe tut wie getBytes() jedoch performanter sein soll.
Code:
public static byte[] stringToBytesUTFCustom(String str) {
 char[] buffer = str.toCharArray();
 byte[] b = new byte[buffer.length << 1];
 for(int i = 0; i < buffer.length; i++) {
  int bpos = i << 1;
  b[bpos] = (byte) ((buffer[i]&0xFF00)>>8);
  b[bpos + 1] = (byte) (buffer[i]&0x00FF);
 }
 return b;
}
Hier lautet allerdings das Resultat [32, -84], jedoch klappt die Rückumwandlung mit folgender Methode ebenfalls.
Code:
public static void bytesToStringUTFCustom()
  {
        byte[] bytes = stringToBytesUTFCustom("€");
	char[] buffer = new char[bytes.length >> 1];
	for (int i = 0; i < buffer.length; i++)
	{
	  int bpos = i << 1;
	  char c = (char) (((bytes[bpos] & 0x00FF) << 8) + (bytes[bpos + 1] & 0x00FF));
	  buffer[i] = c;
	}
	System.out.println(new String(buffer)); 
  }
Um den Rückgabewert der stringToBytesUTFCustom() Methode besser nachvollziehen zu können, habe ich mir mal folgende Inhalte in der for-Schleife ausgeben lassen:
Code:
System.out.println((int)buffer[i]); /*Ausgabe: 8364*/
System.out.println((int)buffer[i]  & 0xFF00); /*Ausgabe: 8192*/
Statt 8364 hätte ich hier wiederum den Wert 128 aus der ASCII Tabelle erwartet. Durch die Bit-Manipulationsoperationen in der Methode erhält man dann schließlich [32, -84], wobei es sich hier scheinbar um die Byte-Werte der Unicode-Codierung des Eurosymbols handelt, was in hexadezimaler Schreibweise dem Wert "20AC" entspricht. Danach habe ich herausgefunden, daß sich mittels folgender Schreibweise ebenfalls das €-Zeichen ausgeben läßt.
Code:
System.out.println("\u20AC");
Nachdem ich anschließend mit den Encodings in Java etwas weiter herumexperimentiert habe, konnte ich feststellen, daß UTF-16 fast die gleiche Ausgabe liefert, bis auf die Tatsache das hier 2 bytes mehr zurückgegeben werden.
Code:
System.out.println(Arrays.toString("€".getBytes("utf-16"))); /*Ausgabe: [-2, -1, 32, -84]*/
Eine Rücktransformation erzielt man dann mit folgendem Aufruf:
Code:
System.out.println(new String("€".getBytes("utf-16"),"utf-16")); /*Ausgabe: €*/

Meine Frage ist nun, warum erhalte ich beim Casten des €-Zeichen von char nach int den Integerwert 8364 und nicht 128? Benutzt Java intern UTF-16 für die Zeichencodierung? Wieso bekomme ich dann bei Verwendung dieses Encodings ein Array mit 4 Bytes und nicht 2?

P.S.: Sorry das der Post so lang geworden ist, aber ich wollte alle meine Erkenntnisse zusammentragen, damit sich mein Problem besser herauskristallisiert :)
 

sheel

I love Asm
Hi

den ersten Teil hast du schon richtig erkannt:
Java ist es egal, was der Standard vom Betriebssystem ist und verwendet einfach UTF16 intern.

Kurz etwas dazu: In UTF16 hat jeder Buchstabe 2 oder 4 Byte, der Typ "char" in Java hat 2 Byte,
jeder Buchstabe besteht also aus 1 oder 2 char´s.
Diese char´s werden durch die performante Methode abgefragt, ohne jede Änderung.
Nur, weil sie je 2 Byte haben und man ja Byte will,
braucht es die Nachverarbeitung mit dem & und >> noch.

Wenn man die "richtige" Konvertierungsmethode mit UTF16 verwendet
sollte ziemlich das Selbe herauskommen, nur kann man eben dort
auch andere Encoding angeben.

Zu den zwei zusätzlichen Bytes:
UTF16 gibts in zwei Varianten (Endianess betreffend):
Es besteht ja immer aus Paaren von zusammengehörenden Bytes,
bei denen auch die Reihenfolge wichtig ist; und die Reihenfolge kann man auch umdrehen.
Was bei UTF16BE die Bytes 1 2 3 4 5 6 ergibt wäre mit UTF16LE eben 2 1 4 3 6 5.

Unterscheiden, welche der Varianten es ist, ist rein durch die Bytes
generell nicht möglich. Wenn man beim Lesen von Bytedaten die falsche Reihenfolge annimmt
sinds dadurch andere Buchstaben als die, die man eigentlich haben will.
Deswegen kann man (optional) eine sogenannte Byte order mark (BOM) einfügen:
Dafür reservierte Byte-Paare -2 -1 bzw. -1 -2 am Anfang eines UTF16-Texts,
die die Bytereihenfolge erkennbar machen.
Zum Verarbeiten/Ausgeben etc. von Text kann man die zwei Byte dann ignorieren
(und die eingebauten Sachen von Java sollten das auch alle machen).

PS: Ganz egal, was man programmiert, Nicht-UTF-irgendwas wie Windows1252 zu verwenden
ist 2014 einfach nur noch sinnloses "Ich will später Probleme mit meinem Programm"-schreien.
Wenn es wirklich keine andere Möglichkeit gibt, weil man alte Libs etc. verwenden muss,
gut, aber sonst...Wenn einem die 2 Byte statt 1 für ganz normale Buchstaben
in UTF16 nicht passen nimmt man eben UTF8 (was sowieso irgendwie immer besser ist).
 

Studdi23

Grünschnabel
Hervorragende Erklärung, die an Klarheit nichts zu wünschen übrig läßt!

Wenn einem die 2 Byte statt 1 für ganz normale Buchstaben
in UTF16 nicht passen nimmt man eben UTF8 (was sowieso irgendwie immer besser ist).

Welches Encoding zu bevorzugen ist wäre meine nächste Frage gewesen und wurde sogleich mitbeantwortet :)
Herzlichen Dank!