webentwicklung-frage-antwort-db.com.de

Erstellen Sie einen veränderbaren Java.lang.String

Es ist allgemein bekannt, dass Java Strings unveränderlich ist. Unveränderliche Zeichenketten sind seit ihrer Einführung eine großartige Ergänzung zu Java. Die Unveränderlichkeit ermöglicht einen schnellen Zugriff und viele Optimierungen, ist im Vergleich zu Zeichenfolgen im C-Stil erheblich weniger fehleranfällig und hilft, das Sicherheitsmodell durchzusetzen.

Es ist möglich, eine veränderliche zu erstellen, ohne Hacks zu verwenden, nämlich

  • Java.lang.reflect
  • Sun.misc.Unsafe
  • Klassen im Bootstrap-Classloader
  • JNI (oder JNA, da JNI erforderlich ist)

Aber ist es in einfachem Java möglich, dass der String jederzeit geändert werden kann? Die Frage ist wie ?

48
bestsss

Wenn Sie einen Java.lang.String mit dem Charset-Konstruktor erstellen, können Sie einen eigenen Charset einfügen, der Ihre eigene CharsetDecoder enthält. Die Variable CharsetDecoder erhält einen Verweis auf ein Objekt CharBuffer in der decodeLoop-Methode. Der CharBuffer umgibt das char [] des ursprünglichen String-Objekts. Da der CharsetDecoder einen Verweis darauf hat, können Sie das zugrunde liegende char [] mit dem CharBuffer ändern, sodass Sie einen veränderbaren String haben.

public class MutableStringTest {


    // http://stackoverflow.com/questions/11146255/how-to-create-mutable-Java-lang-string#11146288
    @Test
    public void testMutableString() throws Exception {
        final String s = createModifiableString();
        System.out.println(s);
        modify(s);
        System.out.println(s);
    }

    private final AtomicReference<CharBuffer> cbRef = new AtomicReference<CharBuffer>();
    private String createModifiableString() {
        Charset charset = new Charset("foo", null) {
            @Override
            public boolean contains(Charset cs) {
                return false;
            }

            @Override
            public CharsetDecoder newDecoder() {
                CharsetDecoder cd = new CharsetDecoder(this, 1.0f, 1.0f) {
                    @Override
                    protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
                        cbRef.set(out);
                        while(in.remaining()>0) {
                            out.append((char)in.get());
                        }
                        return CoderResult.UNDERFLOW;
                    }
                };
                return cd;
            }

            @Override
            public CharsetEncoder newEncoder() {
                return null;
            }
        };
        return new String("abc".getBytes(), charset);
    }
    private void modify(String s) {
        CharBuffer charBuffer = cbRef.get();
        charBuffer.position(0);
        charBuffer.put("xyz");
    }

}

Ausführen des Codes wird gedruckt

abc
zzz

Ich kann decodeLoop () nicht richtig implementieren, aber das interessiert mich jetzt nicht :)

79
mhaller

Die Frage wurde von @mhaller gut beantwortet. Ich würde sagen, das sogenannte Puzzle war ziemlich einfach, und wenn man sich nur die verfügbaren C-Tors von String ansieht, sollte man in der Lage sein, das Wie Teil a herauszufinden 

Komplettlösung

C-tor von Interesse ist unten, wenn Sie einbrechen/crack/suchen nach Sicherheitslücke suchen, suchen Sie immer nach nicht endgültigen willkürlichen Klassen. Der Fall ist hier Java.nio.charset.Charset


//String
public String(byte bytes[], int offset, int length, Charset charset) {
    if (charset == null)
        throw new NullPointerException("charset");
    checkBounds(bytes, offset, length);
    char[] v = StringCoding.decode(charset, bytes, offset, length);
    this.offset = 0;
    this.count = v.length;
    this.value = v;
}
Der c-tor bietet eine vermeintlich schnelle Möglichkeit, byte[] in String zu konvertieren, indem der Zeichensatz und nicht der Chartset-Name übergeben wird. Außerdem kann ein beliebiges Charset-Objekt zum Erstellen von String übergeben werden. Charset main routing konvertiert den Inhalt von Java.nio.ByteBuffer in CharBuffer. Der CharBuffer kann einen Verweis auf char [] enthalten und ist über array() verfügbar. Auch der CharBuffer kann vollständig geändert werden.


    //StringCoding
    static char[] decode(Charset cs, byte[] ba, int off, int len) {
        StringDecoder sd = new StringDecoder(cs, cs.name());
        byte[] b = Arrays.copyOf(ba, ba.length);
        return sd.decode(b, off, len);
    }

    //StringDecoder
    char[] decode(byte[] ba, int off, int len) {
        int en = scale(len, cd.maxCharsPerByte());
        char[] ca = new char[en];
        if (len == 0)
            return ca;
        cd.reset();
        ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
        CharBuffer cb = CharBuffer.wrap(ca);
        try {
            CoderResult cr = cd.decode(bb, cb, true);
            if (!cr.isUnderflow())
                cr.throwException();
            cr = cd.flush(cb);
            if (!cr.isUnderflow())
                cr.throwException();
        } catch (CharacterCodingException x) {
            // Substitution is always enabled,
            // so this shouldn't happen
            throw new Error(x);
        }
        return safeTrim(ca, cb.position(), cs);
    }

Um zu verhindern, dass char[] geändert wird, kopieren die Java-Entwickler das Array wie jede andere String-Konstruktion (zum Beispiel public String(char value[])). Es gibt jedoch eine Ausnahme: Wenn kein SecurityManager installiert ist, wird das Zeichen [] nicht kopiert.

    //Trim the given char array to the given length
    //
    private static char[] safeTrim(char[] ca, int len, Charset cs) {
        if (len == ca.length 
                && (System.getSecurityManager() == null
                || cs.getClass().getClassLoader0() == null))
            return ca;
        else
            return Arrays.copyOf(ca, len);
    }
 </ pre>

Wenn also kein SecurityManager vorhanden ist, ist es absolut möglich, einen modifizierbaren CharBuffer/char [] zu haben, auf den ein String verweist. 

Mittlerweile sieht alles gut aus - außer der byte[] wird auch kopiert (der Fettdruck oben). Dies ist .__, wo Java-Entwickler faul und massiv falsch waren.

Die Kopie ist notwendig, um zu verhindern, dass das Rogue Charset (Beispiel oben) das Quellbyte [] ändern kann. Stellen Sie sich jedoch den Fall vor, dass der Puffer byte[] um 512 KB liegt, der wenige Zeichenfolgen enthält. Beim Versuch, ein paar kleine Diagramme zu erstellen - new String(buf, position, position+32,charset) - führt dies zu einer massiven 512-KB-Byte-Kopie. Wenn der Puffer etwa 1 KB groß wäre, werden die Auswirkungen nie wirklich wahrgenommen. Bei großen Puffern ist der Performance-Hit jedoch sehr groß. Der einfache Fix wäre, den entsprechenden Teil zu kopieren.

... oder gut die Designer von Java.nio haben darüber nachgedacht, indem sie schreibgeschützte Puffer einführen. Das Aufrufen von ByteBuffer.asReadOnlyBuffer() wäre ausreichend gewesen (wenn der Charset.getClassLoader ()! = Null) * Manchmal haben sogar die Leute, die an Java.lang arbeiten, es völlig falsch.

* Class.getClassLoader () gibt null für Bootstrap-Klassen zurück, d. H. Diejenigen, die mit der JVM selbst geliefert werden.

9

Ich würde sagen, StringBuilder (oder StringBuffer für Multithread-Verwendung). Ja, am Ende bekommst du einen unveränderlichen String. Aber so soll es gehen.

Zum Beispiel können Sie Strings in einer Schleife am besten anhängen, indem Sie StringBuilder verwenden. Java selbst verwendet StringBuilder, wenn Sie "fu" + variable + "ba" verwenden.

http://docs.Oracle.com/javase/6/docs/api/Java/lang/StringBuilder.html

append (blub) .append (5) .appen ("dfgdfg"). toString ();

5
keiki
// How to achieve String Mutability

import Java.lang.reflect.Field; 

public class MutableString {

    public static void main(String[] args) { 
        String s = "Hello"; 

        mutate(s);
        System.out.println(s); 

    } 

    public static void mutate(String s) {
        try {

            String t = "Hello world";
            Field val = String.class.getDeclaredField("value"); 
            Field count = String.class.getDeclaredField("count"); 
            val.setAccessible(true); 
            count.setAccessible(true); 

            count.setInt (s, t.length ());
            val.set (s, val.get(t));
        } 
        catch (Exception e) { e.printStackTrace(); }
    } 

}
2
jitendrak

Das Rad nicht neu erfinden. Apache Commons bietet genau das.

MutableObject<String> mutableString = new new MutableObject<>();
0
Roland Ettinger