50 votes

Créer un java.lang.String mutable

C'est la connaissance commune que Java Strings sont immuables. Les Cordes immuables sont excellent ajout à java depuis sa création. L'immutabilité permet un accès rapide et beaucoup d'optimisations, de manière significative moins de risques d'erreurs par rapport à C-style de chaînes de caractères, et permet d'appliquer le modèle de sécurité.

Il est possible de créer une mutable l'une sans l'aide de hacks, à savoir

  • java.lang.refect
  • sun.misc.Unsafe
  • Les Classes de bootstrap chargeur de classe
  • JNI (ou de la JNA comme il exige la JNI)

Mais est-il possible de tout simplement Java, de sorte que la chaîne de caractères peut être modifiée à tout moment? La question est Comment?

85voto

mhaller Points 10002

La création d'un java.lang.String avec le jeu de caractères constructeur, on peut injecter votre propre jeu de caractères, qui apporte à votre propre CharsetDecoder. L' CharsetDecoder obtient une référence à un CharBuffer objet dans le decodeLoop méthode. Le CharBuffer enveloppe le char[] de la Chaîne d'origine de l'objet. Depuis le CharsetDecoder a une référence à elle, vous pouvez changer les sous-jacents char[] à l'aide de la CharBuffer, donc vous avez une mutable Chaîne.

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");
    }

}

L'exécution du code imprime

abc
zzz

Je ne sais pas comment implémenter correctement decodeLoop(), mais je ne m'inquiète pas tout de suite :)

9voto

Mechanical snail Points 8589

La question a reçu une bonne réponse par @mhaller. Je dirais que le soi-disant-puzzle a été assez facile et simplement en regardant la disposition des c-teurs de la Chaîne, on devrait être en mesure de trouver le comment de la partie, un

Procédure pas à pas

C-tor d'intérêt est ci-dessous, si vous êtes à la break-in/fissure/recherche de vulnérabilité de la sécurité, toujours regarder pour les non-final arbitraire des classes. Le cas ici est - 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;
}

Le c-tor propose soi-disant-moyen rapide de convertir byte[] à la Chaîne en passant par le Charset pas le chartset nom pour éviter la recherche de chartsetName->jeu de caractères. Il permet également de passer l'arbitraire d'un jeu de caractères de l'objet pour créer des chaînes. Jeu de caractères de routage principal convertit le contenu de java.nio.ByteBuffer de CharBuffer. Le CharBuffer peut contenir une référence à char[] et il est disponible via array(), aussi la CharBuffer est entièrement modifiable.


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

Afin d'éviter d'altérer l' byte[] b = Arrays.copyOf(ba, ba.length); les développeurs java copier le tableau comme n'importe quelle autre Chaîne de construction (par exemple, 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(); ). Il existe toutefois une exception: si aucun SecurityManager est installé, le char[] n'est pas copié.

ByteBuffer bb = ByteBuffer.wrap(ba, off, len);

Donc, si il n'y a pas de SecurityManager il est tout à fait possible d'avoir une modifiables CharBuffer/char[] qui est référencé par une Chaîne.

Tout semble parfait maintenant - à l'exception de l' 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); } est également copié (les caractères gras ci-dessus). C'est où les développeurs java est allé paresseux et massivement mal.

La copie est nécessaire pour empêcher le voleur jeu de caractères (exemple ci-dessus) pour être en mesure de modifier la source de byte[]. Cependant, imaginez le cas d'avoir autour de 512KO char[] de la mémoire tampon qui contient peu de Chaîne. La tentative de créer un seul petit, quelques graphiques - public String(char value[]) énorme avec 512KO byte[] copie. Si la mémoire tampon ont été de 1 ko ou alors, l'impact ne sera jamais vraiment remarqué. Avec des tampons de grande taille, le gain de performance est vraiment énorme, cependant. La seule solution serait de copier la partie pertinente.

...ou bien les concepteurs de l'


    //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);
    }
pensé par l'introduction en lecture seule Tampons. Simplement en appelant byte[] aurait été suffisant (si le jeu de caractères.getClassLoader()!=null)* Parfois, même les gars qui travaillent sur byte[] pouvez l'obtenir tout à fait tort.

*Classe.getClassLoader() renvoie la valeur null pour les classes de bootstrap, c'est à dire ceux à venir avec la JVM elle-même.

5voto

keiki Points 763

Je dirais StringBuilder (ou StringBuffer pour multithread utilisation). Oui à la fin, vous obtenez une immuable de la Chaîne. Mais c'est le chemin à parcourir.

Par exemple, la meilleure façon d'ajouter des Cordes dans une boucle est d'utiliser StringBuilder. Java utilise StringBuilder lorsque vous utilisez le "fu" + variable + "ba".

http://docs.oracle.com/javase/6/docs/api/java/lang/StringBuilder.html

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X