String -> byte[] -> String - Sieht jemand was ich nicht sehe?

White_Fox

Top Contributor
Guten Morgen allerseits

Folgendes: Ich habe eine Klasse "MemberfieldDeskriptor", diese Klasse enthält Informationen über eine Membervariable eines Objekts. Die Klasse soll außerdem in der Lage sein, die Informationen in ein byte[] zu verpacken, und diese Informationen auch aus einem byte[] wieder herauszulesen.

Jetzt habe ich folgendes Problem: Mein Unittest kommt mit diesem Fehler zurück:
JUnit hat gesagt.:
Failed: expected:<canonical class name[]> but was:<canonical class name[]>

Wenn ich mit dem Debugger an besagter Stelle anhalte, dann liefert ein result.equals(expResult) sogar tatsächlich false zurück.

Die Konvertierung von String nach byte[] und umgekehrt erfolgt so:
Java:
//String -> byte[]
byte[] classNameBytes;

classNameBytes = StandardCharsets.UTF_8.encode(className).array();

//byte[] -> String
this.className = new String(classNameBytes, StandardCharsets.UTF_8);

Hat jemand eine Idee, was da schiefläuft?
 
Kannst du mal angeben, bei welchem String das genau fehlschlägt?
Du hast also einen String className, bei welchem folgender Test false liefert?

Java:
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
class Main {
  public static void main(String[] args) {
    String className = "abz";
    byte[] classNameBytes= StandardCharsets.UTF_8.encode(className).array();
    System.out.println(Arrays.toString(classNameBytes));
    String className2 = new String(classNameBytes, StandardCharsets.UTF_8);
    System.out.println(Arrays.toString(className2.getBytes()));
    System.out.println(className.equals(className2));

  }
}
 
Der Test würde in deiner letzten System.out.println-Zeile fehlschlagen. Dort teste ich mit assertEquals() auf Gleichheit.

Im Ganzen sieht der Unittest wie folgt aus:
Java:
@Test
public void testFitUp() {
    MemberfieldDescriptor descriptor, instance;
    String name, className;
    String result, expResult;
    byte[] bytes, nextBytes;
    ByteBuffer buffer;
    MemberType resultType, expType;

    System.out.println("fitUp");

    name = "fieldname";
    className = "canonical class name";
    descriptor = new DescriptorImplementation(name, className);

    bytes = descriptor.stripDownToBytes();

    buffer = ByteBuffer.wrap(bytes);
    buffer.position(4);            //Der buffer enthält jetzt noch ein int vorne, das erst ausgeschnitten werden muß
    nextBytes = new byte[buffer.remaining()];
    buffer.get(nextBytes);
    instance = new DescriptorImplementation(nextBytes);

    expResult = descriptor.className();
    result = instance.className();
    assertEquals(expResult, result);    //Test schlägt hier mit der genannten Meldung fehl

    expResult = descriptor.name();
    result = instance.name();
    assertEquals(expResult, result);

    expType = descriptor.type();
    resultType = instance.type();
    assertEquals(expType, resultType);
}

Das byte[] wird mit der Methode stripDowntoBytes erzeugt, und mit der fitUp-Methode wieder in seinen ursprünglichen Zustand zurückversetzt:

Java:
byte[] stripDownToBytes(){
    ByteBuffer buffer;
    byte[] nameBytes;
    byte[] classNameBytes;
    byte[] valueBytes;

    nameBytes = StandardCharsets.UTF_8.encode(name).array();
    classNameBytes = StandardCharsets.UTF_8.encode(className).array();
    valueBytes = this.stripValueDownToBytes();

    buffer = ByteBuffer.allocate(
    4 +                            //mainlength
    4 +                            //MemberType
    4 + nameBytes.length +
    4 + classNameBytes.length +
    4 + valueBytes.length);

    buffer.putInt(buffer.limit());
    buffer.putInt(type().order());

    buffer.putInt(nameBytes.length);
    buffer.put(nameBytes);

    buffer.putInt(classNameBytes.length);
    buffer.put(classNameBytes);

    buffer.putInt(valueBytes.length);
    buffer.put(valueBytes);

    return buffer.array();
}

private void fitUp(byte[] bytes){
    ByteBuffer buffer;
    MemberType type;
    int nameLength;
    int classNameLength;
    int valueLength;
    byte[] nameBytes;
    byte[] classNameBytes;
    byte[] valueBytes;

    buffer = ByteBuffer.wrap(bytes);

    type = MemberType.getMemberTypeFromOrder(buffer.getInt());
    fitTypeUp(type);

    nameLength = buffer.getInt();
    nameBytes = new byte[nameLength];
    buffer.get(nameBytes);
    this.name = new String(nameBytes, StandardCharsets.UTF_8);

    classNameLength = buffer.getInt();
    classNameBytes = new byte[classNameLength];
    buffer.get(classNameBytes);
    this.className = new String(classNameBytes, StandardCharsets.UTF_8);

    valueLength = buffer.getInt();
    valueBytes = new byte[valueLength];
    buffer.get(valueBytes);
    this.fitValueUp(valueBytes);
}
 
Es gibt da einen Unterschied:
Java:
    public static void main(String[] args) {
        String className = "canonical class name";
        
        byte[] classNameBytes1 = StandardCharsets.UTF_8.encode(className).array();
        byte[] classNameBytes2 = className.getBytes(StandardCharsets.UTF_8);

        System.out.println(Arrays.toString(classNameBytes1));
        System.out.println(Arrays.toString(classNameBytes2));

Code:
[99, 97, 110, 111, 110, 105, 99, 97, 108, 32, 99, 108, 97, 115, 115, 32, 110, 97, 109, 101, 0, 0]
[99, 97, 110, 111, 110, 105, 99, 97, 108, 32, 99, 108, 97, 115, 115, 32, 110, 97, 109, 101]
[99, 97, 110, 111, 110, 105, 99, 97, 108, 32, 99, 108, 97, 115, 115, 32, 110, 97, 109, 101, 0, 0]
 
Hm...und wie krieg ich das gelöst?

Edit:
Warum ist da überhaupt ein Unterschied? Ich dachte, das ist doch beides UTF-8? Und da ist sowas wie Nullterminierung doch vorgesehen?
 
Hmm, hab mal mit dem String gespielt:

Bei String className = "class name"; ist das Resultat:
Code:
[99, 108, 97, 115, 115, 32, 110, 97, 109, 101, 0]
[99, 108, 97, 115, 115, 32, 110, 97, 109, 101]
[99, 108, 97, 115, 115, 32, 110, 97, 109, 101, 0]

und bei String className = "class nam";
Code:
[99, 108, 97, 115, 115, 32, 110, 97, 109]
[99, 108, 97, 115, 115, 32, 110, 97, 109]
[99, 108, 97, 115, 115, 32, 110, 97, 109]
 
Ich würde sagen, sobald der String länger ist als 9 Zeichen, wird eine 0 angehängt. Empirisch ermitteltes Ergebnis.

Und zwar jedesmal eine zusätzliche Null, wenn die 10 voll wird.
10 Zeichen => 1 Null
20 Zeichen => 2 Nullen
30 Zeichen => 3 Nullen

String className = "123456789012345678901234567890";
Code:
[49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 0, 0, 0]
[49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48]
[49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 0, 0, 0]

Verrückt, aber bestimmt gibt es einen guten Grund dafür. 😎
 
Zuletzt bearbeitet:
Hm...ich hab jetzt einfach mal die Methode, den String in bytes zu zerlegen, geändert. Jetzt funktioniert es (zumindest an dieser Stelle).

Aber dennoch: Ich finde das Verhalten merkwürdig, soll das so sein?
 
die array Methode von einem ByteBuffer (der von Charset.encode zurück kommt) aufzurufen macht vermutlich nicht das, was du denkst.
Returns the byte array that backs this buffer (optional operation).
Modifications to this buffer's content will cause the returned array's content to be modified, and vice versa.

Invoke the hasArray method before invoking this method in order to ensure that this buffer has an accessible backing array.
Und nirgendwo ist in der Spezifikation beschrieben, dass das Array des ByteBuffers nur so groß sein darf, wie nötig. Du könntest mal versuchen vorher compact aufzurufen.
 

Zurück
Oben