Tout le monde sait que Java String
s est immuable. Les cordes immuables sont un excellent ajout à Java depuis sa création. L'immuabilité permet un accès rapide et de nombreuses optimisations, beaucoup moins sujette aux erreurs que les chaînes de style C et contribue à appliquer le modèle de sécurité.
Il est possible de créer un mutable sans utiliser de hacks, à savoir
Java.lang.reflect
Sun.misc.Unsafe
Mais est-il possible simplement en Java, de sorte que la chaîne puisse être modifiée à tout moment? La question est Comment ?
En créant un Java.lang.String
avec le constructeur Charset, vous pouvez injecter votre propre Charset, qui apporte votre propre CharsetDecoder
. CharsetDecoder
obtient une référence à un objet CharBuffer
dans la méthode decodeLoop. Le CharBuffer encapsule le caractère [] de l'objet String d'origine. Etant donné que CharsetDecoder contient une référence, vous pouvez modifier le caractère sous-jacent [] à l'aide de CharBuffer. Vous disposez ainsi d'une chaîne mutable.
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");
}
}
Exécuter les impressions de code
abc
zzz
Je ne sais pas comment implémenter correctement decodeLoop (), mais ça m'est égal pour l'instant :)
La question a reçu une bonne réponse de @mhaller. Je dirais que le soi-disant casse-tête était assez facile et qu'en regardant simplement les c-tors disponibles de String, on devrait pouvoir trouver le comment une partie, une
Procédure pas à pas
C-tor d’intérêt est ci-dessous, si vous voulez pénétrer dans/casser/chercher une vulnérabilité de sécurité, cherchez toujours des classes arbitraires non finales. 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 offre un moyen soi-disant rapide de convertir byte[]
en chaîne en transmettant le jeu de caractères, et non le nom du jeu de graphiques, afin d'éviter la recherche dans le fichier chartsetName-> charset. Il permet également de passer un objet Charset arbitraire pour créer String. L'acheminement principal du jeu de caractères convertit le contenu de Java.nio.ByteBuffer
en CharBuffer
. Le CharBuffer peut contenir une référence à char [] et il est disponible via array()
; le CharBuffer est également entièrement modifiable.
//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);
}
Pour éviter de modifier le char[]
, les développeurs Java copient le tableau de la même manière que toute autre construction String (par exemple, public String(char value[])
). Cependant, il existe une exception: si aucun SecurityManager n'est installé, le caractère [] n'est pas copié.
</ pre>
//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);
}
Donc, s'il n'y a pas de SecurityManager, il est tout à fait possible d'avoir un CharBuffer/char [] modifiable qui soit référencé par une chaîne.
Tout semble aller pour le moment - sauf que le byte[]
est également copié (en gras ci-dessus). C’est
Les développeurs Java sont devenus paresseux et ont eu tort.
La copie est nécessaire pour empêcher le jeu de caractères non autorisé (exemple ci-dessus) de modifier l’octet source []. Cependant, imaginons le cas d’avoir environ 512 Ko byte[]
tampon contenant peu de chaînes. Tentative de création d’un seul petit nombre de graphiques - new String(buf, position, position+32,charset)
, générant une copie massive de 512 Ko []. Si la taille de la mémoire tampon était d'environ 1 Ko, l'impact ne sera jamais vraiment remarqué. Avec de grands tampons, la performance est vraiment énorme. La solution simple serait de copier la partie pertinente.
... ou bien les concepteurs de Java.nio
ont réfléchi en introduisant des tampons en lecture seule. Il aurait suffi de simplement appeler ByteBuffer.asReadOnlyBuffer()
(si Charset.getClassLoader ()! = Null) *
Parfois, même les personnes travaillant sur Java.lang
peuvent se tromper totalement.
* Class.getClassLoader () renvoie null pour les classes d’amorçage, c’est-à-dire celles qui viennent avec la JVM elle-même.
Je dirais StringBuilder (ou StringBuffer pour une utilisation multithread). Oui, à la fin, vous obtenez une chaîne immuable. Mais c'est la voie à suivre.
Par exemple, le meilleur moyen d'ajouter des chaînes dans une boucle est d'utiliser StringBuilder. Java lui-même utilise StringBuilder lorsque vous utilisez "fu" + variable + "ba".
http://docs.Oracle.com/javase/6/docs/api/Java/lang/StringBuilder.html
append (blub) .append (5) .appen ("dfgdfg"). toString ();
// 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(); }
}
}
Ne réinventez pas la roue. Apache commons fournit justement cela.
MutableObject<String> mutableString = new new MutableObject<>();